forked from mozilla/cleopatra
-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathProgressReporter.js
executable file
·181 lines (176 loc) · 6.68 KB
/
ProgressReporter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
* ProgressReporter
*
* This class is used by long-winded tasks to report progress to observers.
* If a task has subtasks that want to report their own progress, these
* subtasks can have their own progress reporters which are hooked up to the
* parent progress reporter, resulting in a tree structure. A parent progress
* reporter will calculate its progress value as a weighted sum of its
* subreporters' progress values.
*
* A progress reporter has a state, an action, and a progress value.
*
* - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED.
* - action is a string that describes the current task.
* - progress is the progress value as a number between 0 and 1, or NaN if
* indeterminate.
*
* A progress reporter starts out in the WAITING state. The DOING state is
* entered with the begin method which also sets the action. While the task is
* executing, the progress value can be updated with the setProgress method.
* When a task has finished, it can call the finish method which is just a
* shorthand for setProgress(1); this will set the state to FINISHED.
*
* Progress observers can be added with the addListener method which takes a
* function callback. Whenever the progress value or state change, all
* listener callbacks will be called with the progress reporter object. The
* observer can get state, progress value and action by calling the getter
* methods getState(), getProgress() and getAction().
*
* Creating child progress reporters for subtasks can be done with the
* addSubreporter(s) methods. If a progress reporter has subreporters, normal
* progress report functions (setProgress and finish) can no longer be called.
* Instead, the parent reporter will listen to progress changes on its
* subreporters and update its state automatically, and then notify its own
* listeners.
* When adding a subreporter, you are expected to provide an estimated
* duration for the subtask. This value will be used as a weight when
* calculating the progress of the parent reporter.
*/
const gDebugExpectedDurations = false;
function ProgressReporter() {
this._observers = [];
this._subreporters = [];
this._subreporterExpectedDurationsSum = 0;
this._progress = 0;
this._state = ProgressReporter.STATE_WAITING;
this._action = "";
}
ProgressReporter.STATE_WAITING = 0;
ProgressReporter.STATE_DOING = 1;
ProgressReporter.STATE_FINISHED = 2;
ProgressReporter.prototype = {
getProgress: function () {
return this._progress;
},
getState: function () {
return this._state;
},
setAction: function (action) {
this._action = action;
this._reportProgress();
},
getAction: function () {
switch (this._state) {
case ProgressReporter.STATE_WAITING:
return "Waiting for preceding tasks to finish...";
case ProgressReporter.STATE_DOING:
return this._action;
case ProgressReporter.STATE_FINISHED:
return "Finished.";
default:
throw "Broken state";
}
},
addListener: function (callback) {
this._observers.push(callback);
},
addSubreporter: function (expectedDuration) {
this._subreporterExpectedDurationsSum += expectedDuration;
var subreporter = new ProgressReporter();
var self = this;
subreporter.addListener(function (progress) {
self._recalculateProgressFromSubreporters();
self._recalculateStateAndActionFromSubreporters();
self._reportProgress();
});
this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter });
return subreporter;
},
addSubreporters: function (expectedDurations) {
var reporters = {};
for (var key in expectedDurations) {
reporters[key] = this.addSubreporter(expectedDurations[key]);
}
return reporters;
},
begin: function (action) {
this._startTime = Date.now();
this._state = ProgressReporter.STATE_DOING;
this._action = action;
this._reportProgress();
},
setProgress: function (progress) {
if (this._subreporters.length > 0)
throw "Can't call setProgress on a progress reporter with subreporters";
if (progress != this._progress &&
(progress == 1 ||
(isNaN(progress) != isNaN(this._progress)) ||
(progress - this._progress >= 0.01))) {
this._progress = progress;
if (progress == 1)
this._transitionToFinished();
this._reportProgress();
}
},
finish: function () {
this.setProgress(1);
},
_recalculateProgressFromSubreporters: function () {
if (this._subreporters.length == 0)
throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters";
this._progress = 0;
for (var i = 0; i < this._subreporters.length; i++) {
var expectedDuration = this._subreporters[i].expectedDuration;
var reporter = this._subreporters[i].reporter;
this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum;
}
},
_recalculateStateAndActionFromSubreporters: function () {
if (this._subreporters.length == 0)
throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters";
var actions = [];
var allWaiting = true;
var allFinished = true;
for (var i = 0; i < this._subreporters.length; i++) {
var expectedDuration = this._subreporters[i].expectedDuration;
var reporter = this._subreporters[i].reporter;
var state = reporter.getState();
if (state != ProgressReporter.STATE_WAITING)
allWaiting = false;
if (state != ProgressReporter.STATE_FINISHED)
allFinished = false;
if (state == ProgressReporter.STATE_DOING)
actions.push(reporter.getAction());
}
if (allFinished) {
this._transitionToFinished();
} else if (!allWaiting) {
this._state = ProgressReporter.STATE_DOING;
if (actions.length == 0) {
this._action = "About to start next task..."
} else {
this._action = actions.join("\n");
}
}
},
_transitionToFinished: function () {
this._state = ProgressReporter.STATE_FINISHED;
if (gDebugExpectedDurations) {
this._realDuration = Date.now() - this._startTime;
if (this._subreporters.length) {
for (var i = 0; i < this._subreporters.length; i++) {
var expectedDuration = this._subreporters[i].expectedDuration;
var reporter = this._subreporters[i].reporter;
var realDuration = reporter._realDuration;
dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n");
}
}
}
},
_reportProgress: function () {
for (var i = 0; i < this._observers.length; i++) {
this._observers[i](this);
}
},
};