toolkit/components/jsdownloads/src/DownloadStore.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/jsdownloads/src/DownloadStore.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,193 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/**
    1.11 + * Handles serialization of Download objects and persistence into a file, so
    1.12 + * that the state of downloads can be restored across sessions.
    1.13 + *
    1.14 + * The file is stored in JSON format, without indentation.  With indentation
    1.15 + * applied, the file would look like this:
    1.16 + *
    1.17 + * {
    1.18 + *   "list": [
    1.19 + *     {
    1.20 + *       "source": "http://www.example.com/download.txt",
    1.21 + *       "target": "/home/user/Downloads/download.txt"
    1.22 + *     },
    1.23 + *     {
    1.24 + *       "source": {
    1.25 + *         "url": "http://www.example.com/download.txt",
    1.26 + *         "referrer": "http://www.example.com/referrer.html"
    1.27 + *       },
    1.28 + *       "target": "/home/user/Downloads/download-2.txt"
    1.29 + *     }
    1.30 + *   ]
    1.31 + * }
    1.32 + */
    1.33 +
    1.34 +"use strict";
    1.35 +
    1.36 +this.EXPORTED_SYMBOLS = [
    1.37 +  "DownloadStore",
    1.38 +];
    1.39 +
    1.40 +////////////////////////////////////////////////////////////////////////////////
    1.41 +//// Globals
    1.42 +
    1.43 +const Cc = Components.classes;
    1.44 +const Ci = Components.interfaces;
    1.45 +const Cu = Components.utils;
    1.46 +const Cr = Components.results;
    1.47 +
    1.48 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.49 +
    1.50 +XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
    1.51 +                                  "resource://gre/modules/Downloads.jsm");
    1.52 +XPCOMUtils.defineLazyModuleGetter(this, "OS",
    1.53 +                                  "resource://gre/modules/osfile.jsm")
    1.54 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.55 +                                  "resource://gre/modules/Task.jsm");
    1.56 +
    1.57 +XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
    1.58 +  return new TextDecoder();
    1.59 +});
    1.60 +
    1.61 +XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
    1.62 +  return new TextEncoder();
    1.63 +});
    1.64 +
    1.65 +////////////////////////////////////////////////////////////////////////////////
    1.66 +//// DownloadStore
    1.67 +
    1.68 +/**
    1.69 + * Handles serialization of Download objects and persistence into a file, so
    1.70 + * that the state of downloads can be restored across sessions.
    1.71 + *
    1.72 + * @param aList
    1.73 + *        DownloadList object to be populated or serialized.
    1.74 + * @param aPath
    1.75 + *        String containing the file path where data should be saved.
    1.76 + */
    1.77 +this.DownloadStore = function (aList, aPath)
    1.78 +{
    1.79 +  this.list = aList;
    1.80 +  this.path = aPath;
    1.81 +}
    1.82 +
    1.83 +this.DownloadStore.prototype = {
    1.84 +  /**
    1.85 +   * DownloadList object to be populated or serialized.
    1.86 +   */
    1.87 +  list: null,
    1.88 +
    1.89 +  /**
    1.90 +   * String containing the file path where data should be saved.
    1.91 +   */
    1.92 +  path: "",
    1.93 +
    1.94 +  /**
    1.95 +   * This function is called with a Download object as its first argument, and
    1.96 +   * should return true if the item should be saved.
    1.97 +   */
    1.98 +  onsaveitem: () => true,
    1.99 +
   1.100 +  /**
   1.101 +   * Loads persistent downloads from the file to the list.
   1.102 +   *
   1.103 +   * @return {Promise}
   1.104 +   * @resolves When the operation finished successfully.
   1.105 +   * @rejects JavaScript exception.
   1.106 +   */
   1.107 +  load: function DS_load()
   1.108 +  {
   1.109 +    return Task.spawn(function task_DS_load() {
   1.110 +      let bytes;
   1.111 +      try {
   1.112 +        bytes = yield OS.File.read(this.path);
   1.113 +      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
   1.114 +        // If the file does not exist, there are no downloads to load.
   1.115 +        return;
   1.116 +      }
   1.117 +
   1.118 +      let storeData = JSON.parse(gTextDecoder.decode(bytes));
   1.119 +
   1.120 +      // Create live downloads based on the static snapshot.
   1.121 +      for (let downloadData of storeData.list) {
   1.122 +        try {
   1.123 +          let download = yield Downloads.createDownload(downloadData);
   1.124 +          try {
   1.125 +            if (!download.succeeded && !download.canceled && !download.error) {
   1.126 +              // Try to restart the download if it was in progress during the
   1.127 +              // previous session.
   1.128 +              download.start();
   1.129 +            } else {
   1.130 +              // If the download was not in progress, try to update the current
   1.131 +              // progress from disk.  This is relevant in case we retained
   1.132 +              // partially downloaded data.
   1.133 +              yield download.refresh();
   1.134 +            }
   1.135 +          } finally {
   1.136 +            // Add the download to the list if we succeeded in creating it,
   1.137 +            // after we have updated its initial state.
   1.138 +            yield this.list.add(download);
   1.139 +          }
   1.140 +        } catch (ex) {
   1.141 +          // If an item is unrecognized, don't prevent others from being loaded.
   1.142 +          Cu.reportError(ex);
   1.143 +        }
   1.144 +      }
   1.145 +    }.bind(this));
   1.146 +  },
   1.147 +
   1.148 +  /**
   1.149 +   * Saves persistent downloads from the list to the file.
   1.150 +   *
   1.151 +   * If an error occurs, the previous file is not deleted.
   1.152 +   *
   1.153 +   * @return {Promise}
   1.154 +   * @resolves When the operation finished successfully.
   1.155 +   * @rejects JavaScript exception.
   1.156 +   */
   1.157 +  save: function DS_save()
   1.158 +  {
   1.159 +    return Task.spawn(function task_DS_save() {
   1.160 +      let downloads = yield this.list.getAll();
   1.161 +
   1.162 +      // Take a static snapshot of the current state of all the downloads.
   1.163 +      let storeData = { list: [] };
   1.164 +      let atLeastOneDownload = false;
   1.165 +      for (let download of downloads) {
   1.166 +        try {
   1.167 +          if (!this.onsaveitem(download)) {
   1.168 +            continue;
   1.169 +          }
   1.170 +          storeData.list.push(download.toSerializable());
   1.171 +          atLeastOneDownload = true;
   1.172 +        } catch (ex) {
   1.173 +          // If an item cannot be converted to a serializable form, don't
   1.174 +          // prevent others from being saved.
   1.175 +          Cu.reportError(ex);
   1.176 +        }
   1.177 +      }
   1.178 +
   1.179 +      if (atLeastOneDownload) {
   1.180 +        // Create or overwrite the file if there are downloads to save.
   1.181 +        let bytes = gTextEncoder.encode(JSON.stringify(storeData));
   1.182 +        yield OS.File.writeAtomic(this.path, bytes,
   1.183 +                                  { tmpPath: this.path + ".tmp" });
   1.184 +      } else {
   1.185 +        // Remove the file if there are no downloads to save at all.
   1.186 +        try {
   1.187 +          yield OS.File.remove(this.path);
   1.188 +        } catch (ex if ex instanceof OS.File.Error &&
   1.189 +                 (ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
   1.190 +          // On Windows, we may get an access denied error instead of a no such
   1.191 +          // file error if the file existed before, and was recently deleted.
   1.192 +        }
   1.193 +      }
   1.194 +    }.bind(this));
   1.195 +  },
   1.196 +};

mercurial