Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/* global H5PAdminIntegration H5PUtils */
2
 
3
(function ($, Version) {
4
  var info, $log, $container, librariesCache = {}, scriptsCache = {};
5
 
6
  // Initialize
7
  $(document).ready(function () {
8
    // Get library info
9
    info = H5PAdminIntegration.libraryInfo;
10
 
11
    // Get and reset container
12
    const $wrapper = $('#h5p-admin-container').html('');
13
    $log = $('<ul class="content-upgrade-log"></ul>').appendTo($wrapper);
14
    $container = $('<div><p>' + info.message + '</p></div>').appendTo($wrapper);
15
 
16
    // Make it possible to select version
17
    var $version = $(getVersionSelect(info.versions)).appendTo($container);
18
 
19
    // Add "go" button
20
    $('<button/>', {
21
      class: 'h5p-admin-upgrade-button',
22
      text: info.buttonLabel,
23
      click: function () {
24
        // Start new content upgrade
25
        new ContentUpgrade($version.val());
26
      }
27
    }).appendTo($container);
28
  });
29
 
30
  /**
31
   * Generate html for version select.
32
   *
33
   * @param {Object} versions
34
   * @returns {String}
35
   */
36
  var getVersionSelect = function (versions) {
37
    var html = '';
38
    for (var id in versions) {
39
      html += '<option value="' + id + '">' + versions[id] + '</option>';
40
    }
41
    if (html !== '') {
42
      html = '<select>' + html + '</select>';
43
      return html;
44
    }
45
  };
46
 
47
  /**
48
   * Displays a throbber in the status field.
49
   *
50
   * @param {String} msg
51
   * @returns {_L1.Throbber}
52
   */
53
  function Throbber(msg) {
54
    var $throbber = H5PUtils.throbber(msg);
55
    $container.html('').append($throbber);
56
 
57
    /**
58
     * Makes it possible to set the progress.
59
     *
60
     * @param {String} progress
61
     */
62
    this.setProgress = function (progress) {
63
      $throbber.text(msg + ' ' + progress);
64
    };
65
  }
66
 
67
  /**
68
   * Start a new content upgrade.
69
   *
70
   * @param {Number} libraryId
71
   * @returns {_L1.ContentUpgrade}
72
   */
73
  function ContentUpgrade(libraryId) {
74
    var self = this;
75
 
76
    // Get selected version
77
    self.version = new Version(info.versions[libraryId]);
78
    self.version.libraryId = libraryId;
79
 
80
    // Create throbber with loading text and progress
81
    self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
82
 
83
    self.started = new Date().getTime();
84
    self.io = 0;
85
 
86
    // Track number of working
87
    self.working = 0;
88
 
89
    var start = function () {
90
      // Get the next batch
91
      self.nextBatch({
92
        libraryId: libraryId,
93
        token: info.token
94
      });
95
    };
96
 
97
    if (window.Worker !== undefined) {
98
      // Prepare our workers
99
      self.initWorkers();
100
      start();
101
    }
102
    else {
103
      // No workers, do the job ourselves
104
      self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start);
105
    }
106
  }
107
 
108
  /**
109
   * Initialize workers
110
   */
111
  ContentUpgrade.prototype.initWorkers = function () {
112
    var self = this;
113
 
114
    // Determine number of workers (defaults to 4)
115
    var numWorkers = (window.navigator !== undefined && window.navigator.hardwareConcurrency ? window.navigator.hardwareConcurrency : 4);
116
    self.workers = new Array(numWorkers);
117
 
118
    // Register message handlers
119
    var messageHandlers = {
120
      done: function (result) {
121
        self.workDone(result.id, result.params, this);
122
      },
123
      error: function (error) {
124
        self.printError(error.err);
125
        self.workDone(error.id, null, this);
126
      },
127
      loadLibrary: function (details) {
128
        var worker = this;
129
        self.loadLibrary(details.name, new Version(details.version), function (err, library) {
130
          if (err) {
131
            // Reset worker?
132
            return;
133
          }
134
 
135
          worker.postMessage({
136
            action: 'libraryLoaded',
137
            library: library
138
          });
139
        });
140
      }
141
    };
142
 
143
    for (var i = 0; i < numWorkers; i++) {
144
      self.workers[i] = new Worker(info.scriptBaseUrl + '/h5p-content-upgrade-worker.js' + info.buster);
145
      self.workers[i].onmessage = function (event) {
146
        if (event.data.action !== undefined && messageHandlers[event.data.action]) {
147
          messageHandlers[event.data.action].call(this, event.data);
148
        }
149
      };
150
    }
151
  };
152
 
153
  /**
154
   * Get the next batch and start processing it.
155
   *
156
   * @param {Object} outData
157
   */
158
  ContentUpgrade.prototype.nextBatch = function (outData) {
159
    var self = this;
160
 
161
    // Track time spent on IO
162
    var start = new Date().getTime();
163
    $.post(info.infoUrl, outData, function (inData) {
164
      self.io += new Date().getTime() - start;
165
      if (!(inData instanceof Object)) {
166
        // Print errors from backend
167
        return self.setStatus(inData);
168
      }
169
      if (inData.left === 0) {
170
        var total = new Date().getTime() - self.started;
171
 
172
        if (window.console && console.log) {
173
          console.log('The upgrade process took ' + (total / 1000) + ' seconds. (' + (Math.round((self.io / (total / 100)) * 100) / 100) + ' % IO)' );
174
        }
175
 
176
        // Terminate workers
177
        self.terminate();
178
 
179
        // Nothing left to process
180
        return self.setStatus(info.done);
181
      }
182
 
183
      self.left = inData.left;
184
      self.token = inData.token;
185
 
186
      // Start processing
187
      self.processBatch(inData.params, inData.skipped);
188
    });
189
  };
190
 
191
  /**
192
   * Set current status message.
193
   *
194
   * @param {String} msg
195
   */
196
  ContentUpgrade.prototype.setStatus = function (msg) {
197
    $container.html(msg);
198
  };
199
 
200
  /**
201
   * Process the given parameters.
202
   *
203
   * @param {Object} parameters
204
   */
205
  ContentUpgrade.prototype.processBatch = function (parameters, skipped) {
206
    var self = this;
207
 
208
    // Track upgraded params
209
    self.upgraded = {};
210
    self.skipped = skipped;
211
 
212
    // Track current batch
213
    self.parameters = parameters;
214
 
215
    // Create id mapping
216
    self.ids = [];
217
    for (var id in parameters) {
218
      if (parameters.hasOwnProperty(id)) {
219
        self.ids.push(id);
220
      }
221
    }
222
 
223
    // Keep track of current content
224
    self.current = -1;
225
 
226
    if (self.workers !== undefined) {
227
      // Assign each worker content to upgrade
228
      for (var i = 0; i < self.workers.length; i++) {
229
        self.assignWork(self.workers[i]);
230
      }
231
    }
232
    else {
233
 
234
      self.assignWork();
235
    }
236
  };
237
 
238
  /**
239
   *
240
   */
241
  ContentUpgrade.prototype.assignWork = function (worker) {
242
    var self = this;
243
 
244
    var id = self.ids[self.current + 1];
245
    if (id === undefined) {
246
      return false; // Out of work
247
    }
248
    self.current++;
249
    self.working++;
250
 
251
    if (worker) {
252
      worker.postMessage({
253
        action: 'newJob',
254
        id: id,
255
        name: info.library.name,
256
        oldVersion: info.library.version,
257
        newVersion: self.version.toString(),
258
        params: self.parameters[id]
259
      });
260
    }
261
    else {
262
      new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) {
263
        self.loadLibrary(name, version, function (err, library) {
264
          if (library.upgradesScript) {
265
            self.loadScript(library.upgradesScript, function (err) {
266
              if (err) {
267
                err = info.errorScript.replace('%lib', name + ' ' + version);
268
              }
269
              next(err, library);
270
            });
271
          }
272
          else {
273
            next(null, library);
274
          }
275
        });
276
 
277
      }, function done(err, result) {
278
        if (err) {
279
          self.printError(err);
280
          result = null;
281
        }
282
 
283
        self.workDone(id, result);
284
      });
285
    }
286
  };
287
 
288
  /**
289
   *
290
   */
291
  ContentUpgrade.prototype.workDone = function (id, result, worker) {
292
    var self = this;
293
 
294
    self.working--;
295
    if (result === null) {
296
      self.skipped.push(id);
297
    }
298
    else {
299
      self.upgraded[id] = result;
300
    }
301
 
302
    // Update progress message
303
    self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %');
304
 
305
    // Assign next job
306
    if (self.assignWork(worker) === false && self.working === 0) {
307
      // All workers have finsihed.
308
      self.nextBatch({
309
        libraryId: self.version.libraryId,
310
        token: self.token,
311
        skipped: JSON.stringify(self.skipped),
312
        params: JSON.stringify(self.upgraded)
313
      });
314
    }
315
  };
316
 
317
  /**
318
   *
319
   */
320
  ContentUpgrade.prototype.terminate = function () {
321
    var self = this;
322
 
323
    if (self.workers) {
324
      // Stop all workers
325
      for (var i = 0; i < self.workers.length; i++) {
326
        self.workers[i].terminate();
327
      }
328
    }
329
  };
330
 
331
  var librariesLoadedCallbacks = {};
332
 
333
  /**
334
   * Load library data needed for content upgrade.
335
   *
336
   * @param {String} name
337
   * @param {Version} version
338
   * @param {Function} next
339
   */
340
  ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
341
    var self = this;
342
 
343
    var key = name + '/' + version.major + '/' + version.minor;
344
 
345
    if (librariesCache[key] === true) {
346
      // Library is being loaded, que callback
347
      if (librariesLoadedCallbacks[key] === undefined) {
348
        librariesLoadedCallbacks[key] = [next];
349
        return;
350
      }
351
      librariesLoadedCallbacks[key].push(next);
352
      return;
353
    }
354
    else if (librariesCache[key] !== undefined) {
355
      // Library has been loaded before. Return cache.
356
      next(null, librariesCache[key]);
357
      return;
358
    }
359
 
360
    // Track time spent loading
361
    var start = new Date().getTime();
362
    librariesCache[key] = true;
363
    $.ajax({
364
      dataType: 'json',
365
      cache: true,
366
      url: info.libraryBaseUrl + '/' + key
367
    }).fail(function () {
368
      self.io += new Date().getTime() - start;
369
      next(info.errorData.replace('%lib', name + ' ' + version));
370
    }).done(function (library) {
371
      self.io += new Date().getTime() - start;
372
      librariesCache[key] = library;
373
      next(null, library);
374
 
375
      if (librariesLoadedCallbacks[key] !== undefined) {
376
        for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) {
377
          librariesLoadedCallbacks[key][i](null, library);
378
        }
379
      }
380
      delete librariesLoadedCallbacks[key];
381
    });
382
  };
383
 
384
  /**
385
   * Load script with upgrade hooks.
386
   *
387
   * @param {String} url
388
   * @param {Function} next
389
   */
390
  ContentUpgrade.prototype.loadScript = function (url, next) {
391
    var self = this;
392
 
393
    if (scriptsCache[url] !== undefined) {
394
      next();
395
      return;
396
    }
397
 
398
    // Track time spent loading
399
    var start = new Date().getTime();
400
    $.ajax({
401
      dataType: 'script',
402
      cache: true,
403
      url: url
404
    }).fail(function () {
405
      self.io += new Date().getTime() - start;
406
      next(true);
407
    }).done(function () {
408
      scriptsCache[url] = true;
409
      self.io += new Date().getTime() - start;
410
      next();
411
    });
412
  };
413
 
414
  /**
415
   *
416
   */
417
  ContentUpgrade.prototype.printError = function (error) {
418
    var self = this;
419
 
420
    switch (error.type) {
421
      case 'errorParamsBroken':
422
        error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
423
        break;
424
 
425
      case 'libraryMissing':
426
        error = info.errorLibrary.replace('%lib', error.library);
427
        break;
428
 
429
      case 'scriptMissing':
430
        error = info.errorScript.replace('%lib', error.library);
431
        break;
432
 
433
      case 'errorTooHighVersion':
434
        error = info.errorContent.replace('%id', error.id) + ' ' + info.errorTooHighVersion.replace('%used', error.used).replace('%supported', error.supported);
435
        break;
436
 
437
      case 'errorNotSupported':
438
        error = info.errorContent.replace('%id', error.id) + ' ' + info.errorNotSupported.replace('%used', error.used);
439
        break;
440
    }
441
 
442
    $('<li>' + info.error + '<br/>' + error + '</li>').appendTo($log);
443
  };
444
 
445
})(H5P.jQuery, H5P.Version);