gecko-dev/toolkit/components/downloads/DownloadStore.jsm
Kris Maglione e930b89c34 Bug 1514594: Part 3 - Change ChromeUtils.import API.
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8

This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:

  ChromeUtils.import("resource://gre/modules/Services.jsm");

is approximately the same as the following, in the new model:

  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs

This was done using the followng script:

https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16750

--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
2019-01-17 10:18:31 -08:00

187 lines
5.7 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Handles serialization of Download objects and persistence into a file, so
* that the state of downloads can be restored across sessions.
*
* The file is stored in JSON format, without indentation. With indentation
* applied, the file would look like this:
*
* {
* "list": [
* {
* "source": "http://www.example.com/download.txt",
* "target": "/home/user/Downloads/download.txt"
* },
* {
* "source": {
* "url": "http://www.example.com/download.txt",
* "referrer": "http://www.example.com/referrer.html"
* },
* "target": "/home/user/Downloads/download-2.txt"
* }
* ]
* }
*/
"use strict";
var EXPORTED_SYMBOLS = [
"DownloadStore",
];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
return new TextDecoder();
});
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function() {
return new TextEncoder();
});
/**
* Handles serialization of Download objects and persistence into a file, so
* that the state of downloads can be restored across sessions.
*
* @param aList
* DownloadList object to be populated or serialized.
* @param aPath
* String containing the file path where data should be saved.
*/
var DownloadStore = function(aList, aPath) {
this.list = aList;
this.path = aPath;
};
this.DownloadStore.prototype = {
/**
* DownloadList object to be populated or serialized.
*/
list: null,
/**
* String containing the file path where data should be saved.
*/
path: "",
/**
* This function is called with a Download object as its first argument, and
* should return true if the item should be saved.
*/
onsaveitem: () => true,
/**
* Loads persistent downloads from the file to the list.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
load: function DS_load() {
return (async () => {
let bytes;
try {
bytes = await OS.File.read(this.path);
} catch (ex) {
if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
throw ex;
}
// If the file does not exist, there are no downloads to load.
return;
}
let storeData = JSON.parse(gTextDecoder.decode(bytes));
// Create live downloads based on the static snapshot.
for (let downloadData of storeData.list) {
try {
let download = await Downloads.createDownload(downloadData);
try {
if (!download.succeeded && !download.canceled && !download.error) {
// Try to restart the download if it was in progress during the
// previous session. Ignore errors.
download.start().catch(() => {});
} else {
// If the download was not in progress, try to update the current
// progress from disk. This is relevant in case we retained
// partially downloaded data.
await download.refresh();
}
} finally {
// Add the download to the list if we succeeded in creating it,
// after we have updated its initial state.
await this.list.add(download);
}
} catch (ex) {
// If an item is unrecognized, don't prevent others from being loaded.
Cu.reportError(ex);
}
}
})();
},
/**
* Saves persistent downloads from the list to the file.
*
* If an error occurs, the previous file is not deleted.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
save: function DS_save() {
return (async () => {
let downloads = await this.list.getAll();
// Take a static snapshot of the current state of all the downloads.
let storeData = { list: [] };
let atLeastOneDownload = false;
for (let download of downloads) {
try {
if (!this.onsaveitem(download)) {
continue;
}
let serializable = download.toSerializable();
if (!serializable) {
// This item cannot be persisted across sessions.
continue;
}
storeData.list.push(serializable);
atLeastOneDownload = true;
} catch (ex) {
// If an item cannot be converted to a serializable form, don't
// prevent others from being saved.
Cu.reportError(ex);
}
}
if (atLeastOneDownload) {
// Create or overwrite the file if there are downloads to save.
let bytes = gTextEncoder.encode(JSON.stringify(storeData));
await OS.File.writeAtomic(this.path, bytes,
{ tmpPath: this.path + ".tmp" });
} else {
// Remove the file if there are no downloads to save at all.
try {
await OS.File.remove(this.path);
} catch (ex) {
if (!(ex instanceof OS.File.Error) ||
!(ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
throw ex;
}
// On Windows, we may get an access denied error instead of a no such
// file error if the file existed before, and was recently deleted.
}
}
})();
},
};