diff options
Diffstat (limited to 'toolkit/mozapps/webextensions/DeferredSave.jsm')
-rw-r--r-- | toolkit/mozapps/webextensions/DeferredSave.jsm | 275 |
1 files changed, 0 insertions, 275 deletions
diff --git a/toolkit/mozapps/webextensions/DeferredSave.jsm b/toolkit/mozapps/webextensions/DeferredSave.jsm deleted file mode 100644 index 89f82b265..000000000 --- a/toolkit/mozapps/webextensions/DeferredSave.jsm +++ /dev/null @@ -1,275 +0,0 @@ -/* 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/. */ - -"use strict"; - -const Cu = Components.utils; -const Cc = Components.classes; -const Ci = Components.interfaces; - -Cu.import("resource://gre/modules/osfile.jsm"); -/* globals OS*/ -Cu.import("resource://gre/modules/Promise.jsm"); - -// Make it possible to mock out timers for testing -var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - -this.EXPORTED_SYMBOLS = ["DeferredSave"]; - -// If delay parameter is not provided, default is 50 milliseconds. -const DEFAULT_SAVE_DELAY_MS = 50; - -Cu.import("resource://gre/modules/Log.jsm"); -// Configure a logger at the parent 'DeferredSave' level to format -// messages for all the modules under DeferredSave.* -const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave"; -var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID); -parentLogger.level = Log.Level.Warn; -var formatter = new Log.BasicFormatter(); -// Set parent logger (and its children) to append to -// the Javascript section of the Browser Console -parentLogger.addAppender(new Log.ConsoleAppender(formatter)); -// Set parent logger (and its children) to -// also append to standard out -parentLogger.addAppender(new Log.DumpAppender(formatter)); - -// Provide the ability to enable/disable logging -// messages at runtime. -// If the "extensions.logging.enabled" preference is -// missing or 'false', messages at the WARNING and higher -// severity should be logged to the JS console and standard error. -// If "extensions.logging.enabled" is set to 'true', messages -// at DEBUG and higher should go to JS console and standard error. -Cu.import("resource://gre/modules/Services.jsm"); - -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; - -/** -* Preference listener which listens for a change in the -* "extensions.logging.enabled" preference and changes the logging level of the -* parent 'addons' level logger accordingly. -*/ -var PrefObserver = { - init: function() { - Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { - let debugLogEnabled = false; - try { - debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); - } - catch (e) { - } - if (debugLogEnabled) { - parentLogger.level = Log.Level.Debug; - } - else { - parentLogger.level = Log.Level.Warn; - } - } - } -}; - -PrefObserver.init(); - -/** - * A module to manage deferred, asynchronous writing of data files - * to disk. Writing is deferred by waiting for a specified delay after - * a request to save the data, before beginning to write. If more than - * one save request is received during the delay, all requests are - * fulfilled by a single write. - * - * @constructor - * @param aPath - * String representing the full path of the file where the data - * is to be written. - * @param aDataProvider - * Callback function that takes no argument and returns the data to - * be written. If aDataProvider returns an ArrayBufferView, the - * bytes it contains are written to the file as is. - * If aDataProvider returns a String the data are UTF-8 encoded - * and then written to the file. - * @param [optional] aDelay - * The delay in milliseconds between the first saveChanges() call - * that marks the data as needing to be saved, and when the DeferredSave - * begins writing the data to disk. Default 50 milliseconds. - */ -this.DeferredSave = function(aPath, aDataProvider, aDelay) { - // Create a new logger (child of 'DeferredSave' logger) - // for use by this particular instance of DeferredSave object - let leafName = OS.Path.basename(aPath); - let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName; - this.logger = Log.repository.getLogger(logger_id); - - // @type {Deferred|null}, null when no data needs to be written - // @resolves with the result of OS.File.writeAtomic when all writes complete - // @rejects with the error from OS.File.writeAtomic if the write fails, - // or with the error from aDataProvider() if that throws. - this._pending = null; - - // @type {Promise}, completes when the in-progress write (if any) completes, - // kept as a resolved promise at other times to simplify logic. - // Because _deferredSave() always uses _writing.then() to execute - // its next action, we don't need a special case for whether a write - // is in progress - if the previous write is complete (and the _writing - // promise is already resolved/rejected), _writing.then() starts - // the next action immediately. - // - // @resolves with the result of OS.File.writeAtomic - // @rejects with the error from OS.File.writeAtomic - this._writing = Promise.resolve(0); - - // Are we currently waiting for a write to complete - this.writeInProgress = false; - - this._path = aPath; - this._dataProvider = aDataProvider; - - this._timer = null; - - // Some counters for telemetry - // The total number of times the file was written - this.totalSaves = 0; - - // The number of times the data became dirty while - // another save was in progress - this.overlappedSaves = 0; - - // Error returned by the most recent write (if any) - this._lastError = null; - - if (aDelay && (aDelay > 0)) - this._delay = aDelay; - else - this._delay = DEFAULT_SAVE_DELAY_MS; -} - -this.DeferredSave.prototype = { - get dirty() { - return this._pending || this.writeInProgress; - }, - - get lastError() { - return this._lastError; - }, - - // Start the pending timer if data is dirty - _startTimer: function() { - if (!this._pending) { - return; - } - - this.logger.debug("Starting timer"); - if (!this._timer) - this._timer = MakeTimer(); - this._timer.initWithCallback(() => this._deferredSave(), - this._delay, Ci.nsITimer.TYPE_ONE_SHOT); - }, - - /** - * Mark the current stored data dirty, and schedule a flush to disk - * @return A Promise<integer> that will be resolved after the data is written to disk; - * the promise is resolved with the number of bytes written. - */ - saveChanges: function() { - this.logger.debug("Save changes"); - if (!this._pending) { - if (this.writeInProgress) { - this.logger.debug("Data changed while write in progress"); - this.overlappedSaves++; - } - this._pending = Promise.defer(); - // Wait until the most recent write completes or fails (if it hasn't already) - // and then restart our timer - this._writing.then(count => this._startTimer(), error => this._startTimer()); - } - return this._pending.promise; - }, - - _deferredSave: function() { - let pending = this._pending; - this._pending = null; - let writing = this._writing; - this._writing = pending.promise; - - // In either the success or the exception handling case, we don't need to handle - // the error from _writing here; it's already being handled in another then() - let toSave = null; - try { - toSave = this._dataProvider(); - } - catch (e) { - this.logger.error("Deferred save dataProvider failed", e); - writing.then(null, error => {}) - .then(count => { - pending.reject(e); - }); - return; - } - - writing.then(null, error => { return 0; }) - .then(count => { - this.logger.debug("Starting write"); - this.totalSaves++; - this.writeInProgress = true; - - OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"}) - .then( - result => { - this._lastError = null; - this.writeInProgress = false; - this.logger.debug("Write succeeded"); - pending.resolve(result); - }, - error => { - this._lastError = error; - this.writeInProgress = false; - this.logger.warn("Write failed", error); - pending.reject(error); - }); - }); - }, - - /** - * Immediately save the dirty data to disk, skipping - * the delay of normal operation. Note that the write - * still happens asynchronously in the worker - * thread from OS.File. - * - * There are four possible situations: - * 1) Nothing to flush - * 2) Data is not currently being written, in-memory copy is dirty - * 3) Data is currently being written, in-memory copy is clean - * 4) Data is being written and in-memory copy is dirty - * - * @return Promise<integer> that will resolve when all in-memory data - * has finished being flushed, returning the number of bytes - * written. If all in-memory data is clean, completes with the - * result of the most recent write. - */ - flush: function() { - // If we have pending changes, cancel our timer and set up the write - // immediately (_deferredSave queues the write for after the most - // recent write completes, if it hasn't already) - if (this._pending) { - this.logger.debug("Flush called while data is dirty"); - if (this._timer) { - this._timer.cancel(); - this._timer = null; - } - this._deferredSave(); - } - - return this._writing; - } -}; |