mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-22 23:27:30 +09:00
[AllAM] De-duplicate ChromeManifestParser.jsm and DeferedSave.jsm
This commit is contained in:
parent
34b241fdfd
commit
f8662e9c1f
5 changed files with 34 additions and 467 deletions
|
|
@ -45,7 +45,7 @@ this.ChromeManifestParser = {
|
||||||
* @return Array of objects describing each manifest instruction, in the form:
|
* @return Array of objects describing each manifest instruction, in the form:
|
||||||
* { type: instruction-type, baseURI: string-uri, args: [arguments] }
|
* { type: instruction-type, baseURI: string-uri, args: [arguments] }
|
||||||
**/
|
**/
|
||||||
parseSync: function CMP_parseSync(aURI) {
|
parseSync: function(aURI) {
|
||||||
function parseLine(aLine) {
|
function parseLine(aLine) {
|
||||||
let line = aLine.trim();
|
let line = aLine.trim();
|
||||||
if (line.length == 0 || line.charAt(0) == '#')
|
if (line.length == 0 || line.charAt(0) == '#')
|
||||||
|
|
@ -80,14 +80,14 @@ this.ChromeManifestParser = {
|
||||||
lines.forEach(parseLine.bind(this));
|
lines.forEach(parseLine.bind(this));
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
_readFromJar: function CMP_readFromJar(aURI) {
|
_readFromJar: function(aURI) {
|
||||||
let data = "";
|
let data = "";
|
||||||
let entries = [];
|
let entries = [];
|
||||||
let readers = [];
|
let readers = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Deconstrict URI, which can be nested jar: URIs.
|
// Deconstrict URI, which can be nested jar: URIs.
|
||||||
let uri = aURI.clone();
|
let uri = aURI.clone();
|
||||||
while (uri instanceof Ci.nsIJARURI) {
|
while (uri instanceof Ci.nsIJARURI) {
|
||||||
entries.push(uri.JAREntry);
|
entries.push(uri.JAREntry);
|
||||||
|
|
@ -99,7 +99,7 @@ this.ChromeManifestParser = {
|
||||||
createInstance(Ci.nsIZipReader);
|
createInstance(Ci.nsIZipReader);
|
||||||
reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
|
reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
|
||||||
readers.push(reader);
|
readers.push(reader);
|
||||||
|
|
||||||
// Open the nested jars.
|
// Open the nested jars.
|
||||||
for (let i = entries.length - 1; i > 0; i--) {
|
for (let i = entries.length - 1; i > 0; i--) {
|
||||||
let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
||||||
|
|
@ -108,7 +108,7 @@ this.ChromeManifestParser = {
|
||||||
readers.push(innerReader);
|
readers.push(innerReader);
|
||||||
reader = innerReader;
|
reader = innerReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First entry is the actual file we want to read.
|
// First entry is the actual file we want to read.
|
||||||
let zis = reader.getInputStream(entries[0]);
|
let zis = reader.getInputStream(entries[0]);
|
||||||
data = NetUtil.readInputStreamToString(zis, zis.available());
|
data = NetUtil.readInputStreamToString(zis, zis.available());
|
||||||
|
|
@ -120,15 +120,15 @@ this.ChromeManifestParser = {
|
||||||
flushJarCache(readers[i].file);
|
flushJarCache(readers[i].file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
_readFromFile: function CMP_readFromFile(aURI) {
|
_readFromFile: function(aURI) {
|
||||||
let file = aURI.QueryInterface(Ci.nsIFileURL).file;
|
let file = aURI.QueryInterface(Ci.nsIFileURL).file;
|
||||||
if (!file.exists() || !file.isFile())
|
if (!file.exists() || !file.isFile())
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
let data = "";
|
let data = "";
|
||||||
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||||
createInstance(Ci.nsIFileInputStream);
|
createInstance(Ci.nsIFileInputStream);
|
||||||
|
|
@ -151,9 +151,7 @@ this.ChromeManifestParser = {
|
||||||
* Instruction type to filter by.
|
* Instruction type to filter by.
|
||||||
* @return True if any matching instructions were found in the manifest.
|
* @return True if any matching instructions were found in the manifest.
|
||||||
*/
|
*/
|
||||||
hasType: function CMP_hasType(aManifest, aType) {
|
hasType: function(aManifest, aType) {
|
||||||
return aManifest.some(function hasType_matchEntryType(aEntry) {
|
return aManifest.some(entry => entry.type == aType);
|
||||||
return aEntry.type == aType;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const Cc = Components.classes;
|
||||||
const Ci = Components.interfaces;
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/osfile.jsm");
|
Cu.import("resource://gre/modules/osfile.jsm");
|
||||||
|
/* globals OS*/
|
||||||
Cu.import("resource://gre/modules/Promise.jsm");
|
Cu.import("resource://gre/modules/Promise.jsm");
|
||||||
|
|
||||||
// Make it possible to mock out timers for testing
|
// Make it possible to mock out timers for testing
|
||||||
|
|
@ -20,26 +21,26 @@ this.EXPORTED_SYMBOLS = ["DeferredSave"];
|
||||||
const DEFAULT_SAVE_DELAY_MS = 50;
|
const DEFAULT_SAVE_DELAY_MS = 50;
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Log.jsm");
|
Cu.import("resource://gre/modules/Log.jsm");
|
||||||
//Configure a logger at the parent 'DeferredSave' level to format
|
// Configure a logger at the parent 'DeferredSave' level to format
|
||||||
//messages for all the modules under DeferredSave.*
|
// messages for all the modules under DeferredSave.*
|
||||||
const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
|
const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
|
||||||
var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
|
var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
|
||||||
parentLogger.level = Log.Level.Warn;
|
parentLogger.level = Log.Level.Warn;
|
||||||
var formatter = new Log.BasicFormatter();
|
var formatter = new Log.BasicFormatter();
|
||||||
//Set parent logger (and its children) to append to
|
// Set parent logger (and its children) to append to
|
||||||
//the Javascript section of the Browser Console
|
// the Javascript section of the Browser Console
|
||||||
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
|
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
|
||||||
//Set parent logger (and its children) to
|
// Set parent logger (and its children) to
|
||||||
//also append to standard out
|
// also append to standard out
|
||||||
parentLogger.addAppender(new Log.DumpAppender(formatter));
|
parentLogger.addAppender(new Log.DumpAppender(formatter));
|
||||||
|
|
||||||
//Provide the ability to enable/disable logging
|
// Provide the ability to enable/disable logging
|
||||||
//messages at runtime.
|
// messages at runtime.
|
||||||
//If the "extensions.logging.enabled" preference is
|
// If the "extensions.logging.enabled" preference is
|
||||||
//missing or 'false', messages at the WARNING and higher
|
// missing or 'false', messages at the WARNING and higher
|
||||||
//severity should be logged to the JS console and standard error.
|
// severity should be logged to the JS console and standard error.
|
||||||
//If "extensions.logging.enabled" is set to 'true', messages
|
// If "extensions.logging.enabled" is set to 'true', messages
|
||||||
//at DEBUG and higher should go to JS console and standard error.
|
// at DEBUG and higher should go to JS console and standard error.
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
|
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
|
||||||
|
|
@ -51,13 +52,13 @@ const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
|
||||||
* parent 'addons' level logger accordingly.
|
* parent 'addons' level logger accordingly.
|
||||||
*/
|
*/
|
||||||
var PrefObserver = {
|
var PrefObserver = {
|
||||||
init: function PrefObserver_init() {
|
init: function() {
|
||||||
Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
|
Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
|
||||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||||
this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
|
this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function PrefObserver_observe(aSubject, aTopic, aData) {
|
observe: function(aSubject, aTopic, aData) {
|
||||||
if (aTopic == "xpcom-shutdown") {
|
if (aTopic == "xpcom-shutdown") {
|
||||||
Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
|
Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
|
||||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||||
|
|
@ -103,7 +104,7 @@ PrefObserver.init();
|
||||||
* that marks the data as needing to be saved, and when the DeferredSave
|
* that marks the data as needing to be saved, and when the DeferredSave
|
||||||
* begins writing the data to disk. Default 50 milliseconds.
|
* begins writing the data to disk. Default 50 milliseconds.
|
||||||
*/
|
*/
|
||||||
this.DeferredSave = function (aPath, aDataProvider, aDelay) {
|
this.DeferredSave = function(aPath, aDataProvider, aDelay) {
|
||||||
// Create a new logger (child of 'DeferredSave' logger)
|
// Create a new logger (child of 'DeferredSave' logger)
|
||||||
// for use by this particular instance of DeferredSave object
|
// for use by this particular instance of DeferredSave object
|
||||||
let leafName = OS.Path.basename(aPath);
|
let leafName = OS.Path.basename(aPath);
|
||||||
|
|
@ -207,7 +208,7 @@ this.DeferredSave.prototype = {
|
||||||
try {
|
try {
|
||||||
toSave = this._dataProvider();
|
toSave = this._dataProvider();
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch (e) {
|
||||||
this.logger.error("Deferred save dataProvider failed", e);
|
this.logger.error("Deferred save dataProvider failed", e);
|
||||||
writing.then(null, error => {})
|
writing.then(null, error => {})
|
||||||
.then(count => {
|
.then(count => {
|
||||||
|
|
@ -216,7 +217,7 @@ this.DeferredSave.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writing.then(null, error => {return 0;})
|
writing.then(null, error => { return 0; })
|
||||||
.then(count => {
|
.then(count => {
|
||||||
this.logger.debug("Starting write");
|
this.logger.debug("Starting write");
|
||||||
this.totalSaves++;
|
this.totalSaves++;
|
||||||
|
|
|
||||||
|
|
@ -1,157 +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";
|
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["ChromeManifestParser"];
|
|
||||||
|
|
||||||
const Cc = Components.classes;
|
|
||||||
const Ci = Components.interfaces;
|
|
||||||
const Cr = Components.results;
|
|
||||||
const Cu = Components.utils;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
|
||||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
||||||
|
|
||||||
const MSG_JAR_FLUSH = "AddonJarFlush";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends local and remote notifications to flush a JAR file cache entry
|
|
||||||
*
|
|
||||||
* @param aJarFile
|
|
||||||
* The ZIP/XPI/JAR file as a nsIFile
|
|
||||||
*/
|
|
||||||
function flushJarCache(aJarFile) {
|
|
||||||
Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
|
|
||||||
Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster)
|
|
||||||
.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses chrome manifest files.
|
|
||||||
*/
|
|
||||||
this.ChromeManifestParser = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads and parses a chrome manifest file located at a specified URI, and all
|
|
||||||
* secondary manifests it references.
|
|
||||||
*
|
|
||||||
* @param aURI
|
|
||||||
* A nsIURI pointing to a chrome manifest.
|
|
||||||
* Typically a file: or jar: URI.
|
|
||||||
* @return Array of objects describing each manifest instruction, in the form:
|
|
||||||
* { type: instruction-type, baseURI: string-uri, args: [arguments] }
|
|
||||||
**/
|
|
||||||
parseSync: function(aURI) {
|
|
||||||
function parseLine(aLine) {
|
|
||||||
let line = aLine.trim();
|
|
||||||
if (line.length == 0 || line.charAt(0) == '#')
|
|
||||||
return;
|
|
||||||
let tokens = line.split(/\s+/);
|
|
||||||
let type = tokens.shift();
|
|
||||||
if (type == "manifest") {
|
|
||||||
let uri = NetUtil.newURI(tokens.shift(), null, aURI);
|
|
||||||
data = data.concat(this.parseSync(uri));
|
|
||||||
} else {
|
|
||||||
data.push({type: type, baseURI: baseURI, args: tokens});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contents = "";
|
|
||||||
try {
|
|
||||||
if (aURI.scheme == "jar")
|
|
||||||
contents = this._readFromJar(aURI);
|
|
||||||
else
|
|
||||||
contents = this._readFromFile(aURI);
|
|
||||||
} catch (e) {
|
|
||||||
// Silently fail.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contents)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
let baseURI = NetUtil.newURI(".", null, aURI).spec;
|
|
||||||
|
|
||||||
let data = [];
|
|
||||||
let lines = contents.split("\n");
|
|
||||||
lines.forEach(parseLine.bind(this));
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
_readFromJar: function(aURI) {
|
|
||||||
let data = "";
|
|
||||||
let entries = [];
|
|
||||||
let readers = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Deconstrict URI, which can be nested jar: URIs.
|
|
||||||
let uri = aURI.clone();
|
|
||||||
while (uri instanceof Ci.nsIJARURI) {
|
|
||||||
entries.push(uri.JAREntry);
|
|
||||||
uri = uri.JARFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the base jar.
|
|
||||||
let reader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
|
||||||
createInstance(Ci.nsIZipReader);
|
|
||||||
reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
|
|
||||||
readers.push(reader);
|
|
||||||
|
|
||||||
// Open the nested jars.
|
|
||||||
for (let i = entries.length - 1; i > 0; i--) {
|
|
||||||
let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
|
|
||||||
createInstance(Ci.nsIZipReader);
|
|
||||||
innerReader.openInner(reader, entries[i]);
|
|
||||||
readers.push(innerReader);
|
|
||||||
reader = innerReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First entry is the actual file we want to read.
|
|
||||||
let zis = reader.getInputStream(entries[0]);
|
|
||||||
data = NetUtil.readInputStreamToString(zis, zis.available());
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// Close readers in reverse order.
|
|
||||||
for (let i = readers.length - 1; i >= 0; i--) {
|
|
||||||
readers[i].close();
|
|
||||||
flushJarCache(readers[i].file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
_readFromFile: function(aURI) {
|
|
||||||
let file = aURI.QueryInterface(Ci.nsIFileURL).file;
|
|
||||||
if (!file.exists() || !file.isFile())
|
|
||||||
return "";
|
|
||||||
|
|
||||||
let data = "";
|
|
||||||
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
|
||||||
createInstance(Ci.nsIFileInputStream);
|
|
||||||
try {
|
|
||||||
fis.init(file, -1, -1, false);
|
|
||||||
data = NetUtil.readInputStreamToString(fis, fis.available());
|
|
||||||
} finally {
|
|
||||||
fis.close();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects if there were any instructions of a specified type in a given
|
|
||||||
* chrome manifest.
|
|
||||||
*
|
|
||||||
* @param aManifest
|
|
||||||
* Manifest data, as returned by ChromeManifestParser.parseSync().
|
|
||||||
* @param aType
|
|
||||||
* Instruction type to filter by.
|
|
||||||
* @return True if any matching instructions were found in the manifest.
|
|
||||||
*/
|
|
||||||
hasType: function(aManifest, aType) {
|
|
||||||
return aManifest.some(entry => entry.type == aType);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -31,9 +31,9 @@ EXTRA_PP_COMPONENTS += [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
|
'../extensions/ChromeManifestParser.jsm',
|
||||||
|
'../extensions/DeferredSave.jsm',
|
||||||
'AddonManager.jsm',
|
'AddonManager.jsm',
|
||||||
'ChromeManifestParser.jsm',
|
|
||||||
'DeferredSave.jsm',
|
|
||||||
'LightweightThemeManager.jsm',
|
'LightweightThemeManager.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue