michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "DownloadImport", michael@0: ]; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Downloads", michael@0: "resource://gre/modules/Downloads.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "OS", michael@0: "resource://gre/modules/osfile.jsm") michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Sqlite", michael@0: "resource://gre/modules/Sqlite.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", michael@0: "resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: /** michael@0: * These values come from the previous interface michael@0: * nsIDownloadManager, which has now been deprecated. michael@0: * These are the only types of download states that michael@0: * we will import. michael@0: */ michael@0: const DOWNLOAD_NOTSTARTED = -1; michael@0: const DOWNLOAD_DOWNLOADING = 0; michael@0: const DOWNLOAD_PAUSED = 4; michael@0: const DOWNLOAD_QUEUED = 5; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// DownloadImport michael@0: michael@0: /** michael@0: * Provides an object that has a method to import downloads michael@0: * from the previous SQLite storage format. michael@0: * michael@0: * @param aList A DownloadList where each successfully michael@0: * imported download will be added. michael@0: * @param aPath The path to the database file. michael@0: */ michael@0: this.DownloadImport = function (aList, aPath) michael@0: { michael@0: this.list = aList; michael@0: this.path = aPath; michael@0: } michael@0: michael@0: this.DownloadImport.prototype = { michael@0: /** michael@0: * Imports unfinished downloads from the previous SQLite storage michael@0: * format (supporting schemas 7 and up), to the new Download object michael@0: * format. Each imported download will be added to the DownloadList michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation has completed (i.e., every download michael@0: * from the previous database has been read and added to michael@0: * the DownloadList) michael@0: */ michael@0: import: function () { michael@0: return Task.spawn(function task_DI_import() { michael@0: let connection = yield Sqlite.openConnection({ path: this.path }); michael@0: michael@0: try { michael@0: let schemaVersion = yield connection.getSchemaVersion(); michael@0: // We don't support schemas older than version 7 (from 2007) michael@0: // - Version 7 added the columns mimeType, preferredApplication michael@0: // and preferredAction in 2007 michael@0: // - Version 8 added the column autoResume in 2007 michael@0: // (if we encounter version 7 we will treat autoResume = false) michael@0: // - Version 9 is the last known version, which added a unique michael@0: // GUID text column that is not used here michael@0: if (schemaVersion < 7) { michael@0: throw new Error("Unable to import in-progress downloads because " michael@0: + "the existing profile is too old."); michael@0: } michael@0: michael@0: let rows = yield connection.execute("SELECT * FROM moz_downloads"); michael@0: michael@0: for (let row of rows) { michael@0: try { michael@0: // Get the DB row data michael@0: let source = row.getResultByName("source"); michael@0: let target = row.getResultByName("target"); michael@0: let tempPath = row.getResultByName("tempPath"); michael@0: let startTime = row.getResultByName("startTime"); michael@0: let state = row.getResultByName("state"); michael@0: let referrer = row.getResultByName("referrer"); michael@0: let maxBytes = row.getResultByName("maxBytes"); michael@0: let mimeType = row.getResultByName("mimeType"); michael@0: let preferredApplication = row.getResultByName("preferredApplication"); michael@0: let preferredAction = row.getResultByName("preferredAction"); michael@0: let entityID = row.getResultByName("entityID"); michael@0: michael@0: let autoResume = false; michael@0: try { michael@0: autoResume = (row.getResultByName("autoResume") == 1); michael@0: } catch (ex) { michael@0: // autoResume wasn't present in schema version 7 michael@0: } michael@0: michael@0: if (!source) { michael@0: throw new Error("Attempted to import a row with an empty " + michael@0: "source column."); michael@0: } michael@0: michael@0: let resumeDownload = false; michael@0: michael@0: switch (state) { michael@0: case DOWNLOAD_NOTSTARTED: michael@0: case DOWNLOAD_QUEUED: michael@0: case DOWNLOAD_DOWNLOADING: michael@0: resumeDownload = true; michael@0: break; michael@0: michael@0: case DOWNLOAD_PAUSED: michael@0: resumeDownload = autoResume; michael@0: break; michael@0: michael@0: default: michael@0: // We won't import downloads in other states michael@0: continue; michael@0: } michael@0: michael@0: // Transform the data michael@0: let targetPath = NetUtil.newURI(target) michael@0: .QueryInterface(Ci.nsIFileURL).file.path; michael@0: michael@0: let launchWhenSucceeded = (preferredAction != Ci.nsIMIMEInfo.saveToDisk); michael@0: michael@0: let downloadOptions = { michael@0: source: { michael@0: url: source, michael@0: referrer: referrer michael@0: }, michael@0: target: { michael@0: path: targetPath, michael@0: partFilePath: tempPath, michael@0: }, michael@0: saver: { michael@0: type: "copy", michael@0: entityID: entityID michael@0: }, michael@0: startTime: new Date(startTime / 1000), michael@0: totalBytes: maxBytes, michael@0: hasPartialData: !!tempPath, michael@0: tryToKeepPartialData: true, michael@0: launchWhenSucceeded: launchWhenSucceeded, michael@0: contentType: mimeType, michael@0: launcherPath: preferredApplication michael@0: }; michael@0: michael@0: // Paused downloads that should not be auto-resumed are considered michael@0: // in a "canceled" state. michael@0: if (!resumeDownload) { michael@0: downloadOptions.canceled = true; michael@0: } michael@0: michael@0: let download = yield Downloads.createDownload(downloadOptions); michael@0: michael@0: yield this.list.add(download); michael@0: michael@0: if (resumeDownload) { michael@0: download.start(); michael@0: } else { michael@0: yield download.refresh(); michael@0: } michael@0: michael@0: } catch (ex) { michael@0: Cu.reportError("Error importing download: " + ex); michael@0: } michael@0: } michael@0: michael@0: } catch (ex) { michael@0: Cu.reportError(ex); michael@0: } finally { michael@0: yield connection.close(); michael@0: } michael@0: }.bind(this)); michael@0: } michael@0: } michael@0: