зеркало из https://github.com/mozilla/gecko-dev.git
Bug 836443 - Automatically stop and restart downloads. r=enn
This commit is contained in:
Родитель
9d8c642be5
Коммит
9981fae81f
|
@ -203,7 +203,7 @@ Download.prototype = {
|
||||||
*
|
*
|
||||||
* This property is relevant while the download is in progress, and also if it
|
* This property is relevant while the download is in progress, and also if it
|
||||||
* failed or has been canceled. If the download has been completed
|
* failed or has been canceled. If the download has been completed
|
||||||
* successfully, this property is not relevant anymore.
|
* successfully, this property is always false.
|
||||||
*
|
*
|
||||||
* Whether partial data can actually be retained depends on the saver and the
|
* Whether partial data can actually be retained depends on the saver and the
|
||||||
* download source, and may not be known before the download is started.
|
* download source, and may not be known before the download is started.
|
||||||
|
@ -382,6 +382,7 @@ Download.prototype = {
|
||||||
// Update the status properties for a successful download.
|
// Update the status properties for a successful download.
|
||||||
this.progress = 100;
|
this.progress = 100;
|
||||||
this.succeeded = true;
|
this.succeeded = true;
|
||||||
|
this.hasPartialData = false;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Fail with a generic status code on cancellation, so that the caller
|
// Fail with a generic status code on cancellation, so that the caller
|
||||||
// is forced to actually check the status properties to see if the
|
// is forced to actually check the status properties to see if the
|
||||||
|
@ -622,6 +623,47 @@ Download.prototype = {
|
||||||
return this._deferSucceeded.promise;
|
return this._deferSucceeded.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state of a finished, failed, or canceled download based on the
|
||||||
|
* current state in the file system. If the download is in progress or it has
|
||||||
|
* been finalized, this method has no effect, and it returns a resolved
|
||||||
|
* promise.
|
||||||
|
*
|
||||||
|
* This allows the properties of the download to be updated in case the user
|
||||||
|
* moved or deleted the target file or its associated ".part" file.
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
* @resolves When the operation has completed.
|
||||||
|
* @rejects Never.
|
||||||
|
*/
|
||||||
|
refresh: function ()
|
||||||
|
{
|
||||||
|
return Task.spawn(function () {
|
||||||
|
if (!this.stopped || this._finalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current progress from disk if we retained partial data.
|
||||||
|
if (this.hasPartialData && this.target.partFilePath) {
|
||||||
|
let stat = yield OS.File.stat(this.target.partFilePath);
|
||||||
|
|
||||||
|
// Ignore the result if the state has changed meanwhile.
|
||||||
|
if (!this.stopped || this._finalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bytes transferred and the related progress properties.
|
||||||
|
this.currentBytes = stat.size;
|
||||||
|
if (this.totalBytes > 0) {
|
||||||
|
this.hasProgress = true;
|
||||||
|
this.progress = Math.floor(this.currentBytes /
|
||||||
|
this.totalBytes * 100);
|
||||||
|
}
|
||||||
|
this._notifyChange();
|
||||||
|
}
|
||||||
|
}.bind(this)).then(null, Cu.reportError);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the "finalize" method has been called. This prevents the download
|
* True if the "finalize" method has been called. This prevents the download
|
||||||
* from starting again after having been stopped.
|
* from starting again after having been stopped.
|
||||||
|
@ -761,20 +803,54 @@ Download.prototype = {
|
||||||
serializable.saver = saver;
|
serializable.saver = saver;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.launcherPath) {
|
if (!this.stopped) {
|
||||||
serializable.launcherPath = this.launcherPath;
|
serializable.stopped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.launchWhenSucceeded) {
|
if (this.error && ("message" in this.error)) {
|
||||||
serializable.launchWhenSucceeded = true;
|
serializable.error = { message: this.error.message };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.contentType) {
|
// These are serialized unless they are false, null, or empty strings.
|
||||||
serializable.contentType = this.contentType;
|
let propertiesToSerialize = [
|
||||||
|
"succeeded",
|
||||||
|
"canceled",
|
||||||
|
"startTime",
|
||||||
|
"totalBytes",
|
||||||
|
"hasPartialData",
|
||||||
|
"tryToKeepPartialData",
|
||||||
|
"launcherPath",
|
||||||
|
"launchWhenSucceeded",
|
||||||
|
"contentType",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let property of propertiesToSerialize) {
|
||||||
|
if (this[property]) {
|
||||||
|
serializable[property] = this[property];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializable;
|
return serializable;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value that changes only when one of the properties of a Download
|
||||||
|
* object that should be saved into a file also change. This excludes
|
||||||
|
* properties whose value doesn't usually change during the download lifetime.
|
||||||
|
*
|
||||||
|
* This function is used to determine whether the download should be
|
||||||
|
* serialized after a property change notification has been received.
|
||||||
|
*
|
||||||
|
* @return String representing the relevant download state.
|
||||||
|
*/
|
||||||
|
getSerializationHash: function ()
|
||||||
|
{
|
||||||
|
// The "succeeded", "canceled", "error", and startTime properties are not
|
||||||
|
// taken into account because they all change before the "stopped" property
|
||||||
|
// changes, and are not altered in other cases.
|
||||||
|
return this.stopped + "," + this.totalBytes + "," + this.hasPartialData +
|
||||||
|
"," + this.contentType;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -816,16 +892,28 @@ Download.fromSerializable = function (aSerializable) {
|
||||||
}
|
}
|
||||||
download.saver.download = download;
|
download.saver.download = download;
|
||||||
|
|
||||||
if ("launchWhenSucceeded" in aSerializable) {
|
let propertiesToDeserialize = [
|
||||||
download.launchWhenSucceeded = !!aSerializable.launchWhenSucceeded;
|
"startTime",
|
||||||
|
"totalBytes",
|
||||||
|
"hasPartialData",
|
||||||
|
"tryToKeepPartialData",
|
||||||
|
"launcherPath",
|
||||||
|
"launchWhenSucceeded",
|
||||||
|
"contentType",
|
||||||
|
];
|
||||||
|
|
||||||
|
// If the download should not be restarted automatically, update its state to
|
||||||
|
// reflect success or failure during a previous session.
|
||||||
|
if (!("stopped" in aSerializable) || aSerializable.stopped) {
|
||||||
|
propertiesToDeserialize.push("succeeded");
|
||||||
|
propertiesToDeserialize.push("canceled");
|
||||||
|
propertiesToDeserialize.push("error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("contentType" in aSerializable) {
|
for (let property of propertiesToDeserialize) {
|
||||||
download.contentType = aSerializable.contentType;
|
if (property in aSerializable) {
|
||||||
}
|
download[property] = aSerializable[property];
|
||||||
|
}
|
||||||
if ("launcherPath" in aSerializable) {
|
|
||||||
download.launcherPath = aSerializable.launcherPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return download;
|
return download;
|
||||||
|
@ -1190,6 +1278,13 @@ DownloadCopySaver.prototype = {
|
||||||
*/
|
*/
|
||||||
_canceled: false,
|
_canceled: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String corresponding to the entityID property of the nsIResumableChannel
|
||||||
|
* used to execute the download, or null if the channel was not resumable or
|
||||||
|
* the saver was instructed not to keep partially downloaded data.
|
||||||
|
*/
|
||||||
|
entityID: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements "DownloadSaver.execute".
|
* Implements "DownloadSaver.execute".
|
||||||
*/
|
*/
|
||||||
|
@ -1428,8 +1523,13 @@ DownloadCopySaver.prototype = {
|
||||||
*/
|
*/
|
||||||
toSerializable: function ()
|
toSerializable: function ()
|
||||||
{
|
{
|
||||||
// Simplify the representation since we don't have other details for now.
|
// Simplify the representation if we don't have other details.
|
||||||
return "copy";
|
if (!this.entityID) {
|
||||||
|
return "copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: "copy",
|
||||||
|
entityID: this.entityID };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1443,8 +1543,11 @@ DownloadCopySaver.prototype = {
|
||||||
* @return The newly created DownloadCopySaver object.
|
* @return The newly created DownloadCopySaver object.
|
||||||
*/
|
*/
|
||||||
DownloadCopySaver.fromSerializable = function (aSerializable) {
|
DownloadCopySaver.fromSerializable = function (aSerializable) {
|
||||||
// We don't have other state details for now.
|
let saver = new DownloadCopySaver();
|
||||||
return new DownloadCopySaver();
|
if ("entityID" in aSerializable) {
|
||||||
|
saver.entityID = aSerializable.entityID;
|
||||||
|
}
|
||||||
|
return saver;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1574,6 +1677,13 @@ DownloadLegacySaver.prototype = {
|
||||||
*/
|
*/
|
||||||
copySaver: null,
|
copySaver: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String corresponding to the entityID property of the nsIResumableChannel
|
||||||
|
* used to execute the download, or null if the channel was not resumable or
|
||||||
|
* the saver was instructed not to keep partially downloaded data.
|
||||||
|
*/
|
||||||
|
entityID: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements "DownloadSaver.execute".
|
* Implements "DownloadSaver.execute".
|
||||||
*/
|
*/
|
||||||
|
@ -1674,7 +1784,7 @@ DownloadLegacySaver.prototype = {
|
||||||
// thus it cannot be rebuilt during deserialization. To support resuming
|
// thus it cannot be rebuilt during deserialization. To support resuming
|
||||||
// across different browser sessions, this object is transformed into a
|
// across different browser sessions, this object is transformed into a
|
||||||
// DownloadCopySaver for the purpose of serialization.
|
// DownloadCopySaver for the purpose of serialization.
|
||||||
return "copy";
|
return DownloadCopySaver.prototype.toSerializable.call(this);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,31 @@ XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
|
||||||
createBundle("chrome://mozapps/locale/downloads/downloads.properties");
|
createBundle("chrome://mozapps/locale/downloads/downloads.properties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
|
||||||
|
"initWithCallback");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the delay between a change to the downloads data and the related
|
||||||
|
* save operation. This value is the result of a delicate trade-off, assuming
|
||||||
|
* the host application uses the browser history instead of the download store
|
||||||
|
* to save completed downloads.
|
||||||
|
*
|
||||||
|
* If a download takes less than this interval to complete (for example, saving
|
||||||
|
* a page that is already displayed), then no input/output is triggered by the
|
||||||
|
* download store except for an existence check, resulting in the best possible
|
||||||
|
* efficiency.
|
||||||
|
*
|
||||||
|
* Conversely, if the browser is closed before this interval has passed, the
|
||||||
|
* download will not be saved. This prevents it from being restored in the next
|
||||||
|
* session, and if there is partial data associated with it, then the ".part"
|
||||||
|
* file will not be deleted when the browser starts again.
|
||||||
|
*
|
||||||
|
* In all cases, for best efficiency, this value should be high enough that the
|
||||||
|
* input/output for opening or closing the target file does not overlap with the
|
||||||
|
* one for saving the list of downloads.
|
||||||
|
*/
|
||||||
|
const kSaveDelayMs = 1500;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//// DownloadIntegration
|
//// DownloadIntegration
|
||||||
|
|
||||||
|
@ -105,7 +130,7 @@ this.DownloadIntegration = {
|
||||||
* @param aList
|
* @param aList
|
||||||
* DownloadList object to be populated with the download objects
|
* DownloadList object to be populated with the download objects
|
||||||
* serialized from the previous session. This list will be persisted
|
* serialized from the previous session. This list will be persisted
|
||||||
* to disk during the session lifetime or when the session terminates.
|
* to disk during the session lifetime.
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
* @resolves When the list has been populated.
|
* @resolves When the list has been populated.
|
||||||
|
@ -124,7 +149,39 @@ this.DownloadIntegration = {
|
||||||
this._store = new DownloadStore(aList, OS.Path.join(
|
this._store = new DownloadStore(aList, OS.Path.join(
|
||||||
OS.Constants.Path.profileDir,
|
OS.Constants.Path.profileDir,
|
||||||
"downloads.json"));
|
"downloads.json"));
|
||||||
return this._store.load();
|
this._store.onsaveitem = this.shouldPersistDownload.bind(this);
|
||||||
|
|
||||||
|
// Load the list of persistent downloads, then add the DownloadAutoSaveView
|
||||||
|
// even if the load operation failed.
|
||||||
|
return this._store.load().then(null, Cu.reportError).then(() => {
|
||||||
|
new DownloadAutoSaveView(aList, this._store);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a Download object from the list of persistent downloads
|
||||||
|
* should be saved into a file, so that it can be restored across sessions.
|
||||||
|
*
|
||||||
|
* This function allows filtering out downloads that the host application is
|
||||||
|
* not interested in persisting across sessions, for example downloads that
|
||||||
|
* finished successfully.
|
||||||
|
*
|
||||||
|
* @param aDownload
|
||||||
|
* The Download object to be inspected. This is originally taken from
|
||||||
|
* the global DownloadList object for downloads that were not started
|
||||||
|
* from a private browsing window. The item may have been removed
|
||||||
|
* from the list since the save operation started, though in this case
|
||||||
|
* the save operation will be repeated later.
|
||||||
|
*
|
||||||
|
* @return True to save the download, false otherwise.
|
||||||
|
*/
|
||||||
|
shouldPersistDownload: function (aDownload)
|
||||||
|
{
|
||||||
|
// In the default implementation, we save all the downloads currently in
|
||||||
|
// progress, as well as stopped downloads for which we retained partially
|
||||||
|
// downloaded data. Stopped downloads for which we don't need to track the
|
||||||
|
// presence of a ".part" file are only retained in the browser history.
|
||||||
|
return aDownload.hasPartialData || !aDownload.stopped;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -493,6 +550,10 @@ this.DownloadIntegration = {
|
||||||
* @resolves When the views and observers are added.
|
* @resolves When the views and observers are added.
|
||||||
*/
|
*/
|
||||||
addListObservers: function DI_addListObservers(aList, aIsPrivate) {
|
addListObservers: function DI_addListObservers(aList, aIsPrivate) {
|
||||||
|
if (this.dontLoad) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
DownloadObserver.registerView(aList, aIsPrivate);
|
DownloadObserver.registerView(aList, aIsPrivate);
|
||||||
if (!DownloadObserver.observersAdded) {
|
if (!DownloadObserver.observersAdded) {
|
||||||
DownloadObserver.observersAdded = true;
|
DownloadObserver.observersAdded = true;
|
||||||
|
@ -504,7 +565,10 @@ this.DownloadIntegration = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let DownloadObserver = {
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// DownloadObserver
|
||||||
|
|
||||||
|
this.DownloadObserver = {
|
||||||
/**
|
/**
|
||||||
* Flag to determine if the observers have been added previously.
|
* Flag to determine if the observers have been added previously.
|
||||||
*/
|
*/
|
||||||
|
@ -657,3 +721,134 @@ let DownloadObserver = {
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||||
Ci.nsISupportsWeakReference])
|
Ci.nsISupportsWeakReference])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// DownloadAutoSaveView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This view can be added to a DownloadList object to trigger a save operation
|
||||||
|
* in the given DownloadStore object when a relevant change occurs.
|
||||||
|
*
|
||||||
|
* @param aStore
|
||||||
|
* The DownloadStore object used for saving.
|
||||||
|
*/
|
||||||
|
function DownloadAutoSaveView(aList, aStore) {
|
||||||
|
this._store = aStore;
|
||||||
|
this._downloadsMap = new Map();
|
||||||
|
|
||||||
|
// We set _initialized to true after adding the view, so that onDownloadAdded
|
||||||
|
// doesn't cause a save to occur.
|
||||||
|
aList.addView(this);
|
||||||
|
this._initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadAutoSaveView.prototype = {
|
||||||
|
/**
|
||||||
|
* True when the initial state of the downloads has been loaded.
|
||||||
|
*/
|
||||||
|
_initialized: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DownloadStore object used for saving.
|
||||||
|
*/
|
||||||
|
_store: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This map contains only Download objects that should be saved to disk, and
|
||||||
|
* associates them with the result of their getSerializationHash function, for
|
||||||
|
* the purpose of detecting changes to the relevant properties.
|
||||||
|
*/
|
||||||
|
_downloadsMap: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is set to true when the save operation should be triggered. This is
|
||||||
|
* required so that a new operation can be scheduled while the current one is
|
||||||
|
* in progress, without re-entering the save method.
|
||||||
|
*/
|
||||||
|
_shouldSave: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nsITimer used for triggering the save operation after a delay, or null if
|
||||||
|
* saving has finished and there is no operation scheduled for execution.
|
||||||
|
*
|
||||||
|
* The logic here is different from the DeferredTask module in that multiple
|
||||||
|
* requests will never delay the operation for longer than the expected time
|
||||||
|
* (no grace delay), and the operation is never re-entered during execution.
|
||||||
|
*/
|
||||||
|
_timer: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer callback used to serialize the list of downloads.
|
||||||
|
*/
|
||||||
|
_save: function ()
|
||||||
|
{
|
||||||
|
Task.spawn(function () {
|
||||||
|
// Any save request received during execution will be handled later.
|
||||||
|
this._shouldSave = false;
|
||||||
|
|
||||||
|
// Execute the asynchronous save operation.
|
||||||
|
try {
|
||||||
|
yield this._store.save();
|
||||||
|
} catch (ex) {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle requests received during the operation.
|
||||||
|
this._timer = null;
|
||||||
|
if (this._shouldSave) {
|
||||||
|
this.saveSoon();
|
||||||
|
}
|
||||||
|
}.bind(this)).then(null, Cu.reportError);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the list of downloads changed, this triggers the asynchronous
|
||||||
|
* serialization of the list of downloads.
|
||||||
|
*/
|
||||||
|
saveSoon: function ()
|
||||||
|
{
|
||||||
|
this._shouldSave = true;
|
||||||
|
if (!this._timer) {
|
||||||
|
this._timer = new Timer(this._save.bind(this), kSaveDelayMs,
|
||||||
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// DownloadList view
|
||||||
|
|
||||||
|
onDownloadAdded: function (aDownload)
|
||||||
|
{
|
||||||
|
if (DownloadIntegration.shouldPersistDownload(aDownload)) {
|
||||||
|
this._downloadsMap.set(aDownload, aDownload.getSerializationHash());
|
||||||
|
if (this._initialized) {
|
||||||
|
this.saveSoon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDownloadChanged: function (aDownload)
|
||||||
|
{
|
||||||
|
if (!DownloadIntegration.shouldPersistDownload(aDownload)) {
|
||||||
|
if (this._downloadsMap.has(aDownload)) {
|
||||||
|
this._downloadsMap.delete(aDownload);
|
||||||
|
this.saveSoon();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = aDownload.getSerializationHash();
|
||||||
|
if (this._downloadsMap.get(aDownload) != hash) {
|
||||||
|
this._downloadsMap.set(aDownload, hash);
|
||||||
|
this.saveSoon();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDownloadRemoved: function (aDownload)
|
||||||
|
{
|
||||||
|
if (this._downloadsMap.has(aDownload)) {
|
||||||
|
this._downloadsMap.delete(aDownload);
|
||||||
|
this.saveSoon();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -171,6 +171,11 @@ DownloadList.prototype = {
|
||||||
* // Called after aDownload is removed from the list.
|
* // Called after aDownload is removed from the list.
|
||||||
* },
|
* },
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* @note The onDownloadAdded notifications are sent synchronously. This
|
||||||
|
* allows for a complete initialization of the view used for detecting
|
||||||
|
* changes to downloads to be persisted, before other callers get a
|
||||||
|
* chance to modify them.
|
||||||
*/
|
*/
|
||||||
addView: function DL_addView(aView)
|
addView: function DL_addView(aView)
|
||||||
{
|
{
|
||||||
|
|
|
@ -88,6 +88,12 @@ DownloadStore.prototype = {
|
||||||
*/
|
*/
|
||||||
path: "",
|
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.
|
* Loads persistent downloads from the file to the list.
|
||||||
*
|
*
|
||||||
|
@ -111,7 +117,23 @@ DownloadStore.prototype = {
|
||||||
// Create live downloads based on the static snapshot.
|
// Create live downloads based on the static snapshot.
|
||||||
for (let downloadData of storeData.list) {
|
for (let downloadData of storeData.list) {
|
||||||
try {
|
try {
|
||||||
this.list.add(yield Downloads.createDownload(downloadData));
|
let download = yield Downloads.createDownload(downloadData);
|
||||||
|
try {
|
||||||
|
if (("stopped" in downloadData) && !downloadData.stopped) {
|
||||||
|
// Try to restart the download if it was in progress during the
|
||||||
|
// previous session.
|
||||||
|
download.start();
|
||||||
|
} 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.
|
||||||
|
yield download.refresh();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Add the download to the list if we succeeded in creating it,
|
||||||
|
// after we have updated its initial state.
|
||||||
|
this.list.add(download);
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// If an item is unrecognized, don't prevent others from being loaded.
|
// If an item is unrecognized, don't prevent others from being loaded.
|
||||||
Cu.reportError(ex);
|
Cu.reportError(ex);
|
||||||
|
@ -139,6 +161,9 @@ DownloadStore.prototype = {
|
||||||
let atLeastOneDownload = false;
|
let atLeastOneDownload = false;
|
||||||
for (let download of downloads) {
|
for (let download of downloads) {
|
||||||
try {
|
try {
|
||||||
|
if (!this.onsaveitem(download)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
storeData.list.push(download.toSerializable());
|
storeData.list.push(download.toSerializable());
|
||||||
atLeastOneDownload = true;
|
atLeastOneDownload = true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче