mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 988292: Avoid main-thread IO for {profile}\addons.json; r=felipc@gmail.com,r=irving
This commit is contained in:
parent
6760789e3a
commit
d6e5bff5be
@ -13,8 +13,6 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
@ -25,6 +23,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository_SQLiteMigrator",
|
||||
"resource://gre/modules/addons/AddonRepository_SQLiteMigrator.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "AddonRepository" ];
|
||||
|
||||
@ -495,12 +496,6 @@ this.AddonRepository = {
|
||||
// An array of callbacks pending the retrieval of add-ons from AddonDatabase
|
||||
_pendingCallbacks: null,
|
||||
|
||||
// Whether a migration in currently in progress
|
||||
_migrationInProgress: false,
|
||||
|
||||
// A callback to be called when migration finishes
|
||||
_postMigrationCallback: null,
|
||||
|
||||
// Whether a search is currently in progress
|
||||
_searching: false,
|
||||
|
||||
@ -552,7 +547,7 @@ this.AddonRepository = {
|
||||
* @param aCallback
|
||||
* The callback to pass the result back to
|
||||
*/
|
||||
getCachedAddonByID: function AddonRepo_getCachedAddonByID(aId, aCallback) {
|
||||
getCachedAddonByID: Task.async(function* (aId, aCallback) {
|
||||
if (!aId || !this.cacheEnabled) {
|
||||
aCallback(null);
|
||||
return;
|
||||
@ -568,20 +563,20 @@ this.AddonRepository = {
|
||||
// Data has not been retrieved from the database, so retrieve it
|
||||
this._pendingCallbacks = [];
|
||||
this._pendingCallbacks.push(getAddon);
|
||||
AddonDatabase.retrieveStoredData(function getCachedAddonByID_retrieveData(aAddons) {
|
||||
let pendingCallbacks = self._pendingCallbacks;
|
||||
|
||||
// Check if cache was shutdown or deleted before callback was called
|
||||
if (pendingCallbacks == null)
|
||||
return;
|
||||
let addons = yield AddonDatabase.retrieveStoredData();
|
||||
let pendingCallbacks = self._pendingCallbacks;
|
||||
|
||||
// Callbacks may want to trigger a other caching operations that may
|
||||
// affect _addons and _pendingCallbacks, so set to final values early
|
||||
self._pendingCallbacks = null;
|
||||
self._addons = aAddons;
|
||||
// Check if cache was shutdown or deleted before callback was called
|
||||
if (pendingCallbacks == null)
|
||||
return;
|
||||
|
||||
pendingCallbacks.forEach(function(aCallback) aCallback(aAddons));
|
||||
});
|
||||
// Callbacks may want to trigger a other caching operations that may
|
||||
// affect _addons and _pendingCallbacks, so set to final values early
|
||||
self._pendingCallbacks = null;
|
||||
self._addons = addons;
|
||||
|
||||
pendingCallbacks.forEach(function(aCallback) aCallback(addons));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -593,7 +588,7 @@ this.AddonRepository = {
|
||||
|
||||
// Data has been retrieved, so immediately return result
|
||||
getAddon(this._addons);
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Asynchronously repopulate cache so it only contains the add-ons
|
||||
@ -1526,114 +1521,96 @@ this.AddonRepository = {
|
||||
};
|
||||
|
||||
var AddonDatabase = {
|
||||
// true if the database connection has been opened
|
||||
initialized: false,
|
||||
// false if there was an unrecoverable error openning the database
|
||||
// false if there was an unrecoverable error opening the database
|
||||
databaseOk: true,
|
||||
|
||||
connectionPromise: null,
|
||||
// the in-memory database
|
||||
DB: BLANK_DB(),
|
||||
|
||||
/**
|
||||
* A getter to retrieve an nsIFile pointer to the DB
|
||||
* A getter to retrieve the path to the DB
|
||||
*/
|
||||
get jsonFile() {
|
||||
delete this.jsonFile;
|
||||
return this.jsonFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
|
||||
},
|
||||
return OS.Path.join(OS.Constants.Path.profileDir, FILE_DATABASE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronously opens a new connection to the database file.
|
||||
* Asynchronously opens a new connection to the database file.
|
||||
*
|
||||
* @return {Promise} a promise that resolves to the database.
|
||||
*/
|
||||
openConnection: function() {
|
||||
this.DB = BLANK_DB();
|
||||
this.initialized = true;
|
||||
delete this.connection;
|
||||
if (!this.connectionPromise) {
|
||||
this.connectionPromise = Task.spawn(function*() {
|
||||
this.DB = BLANK_DB();
|
||||
|
||||
let inputDB, fstream, cstream, schema;
|
||||
let inputDB, schema;
|
||||
|
||||
try {
|
||||
let data = "";
|
||||
fstream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Ci.nsIConverterInputStream);
|
||||
try {
|
||||
let data = yield OS.File.read(this.jsonFile, { encoding: "utf-8"})
|
||||
inputDB = JSON.parse(data);
|
||||
|
||||
fstream.init(this.jsonFile, -1, 0, 0);
|
||||
cstream.init(fstream, "UTF-8", 0, 0);
|
||||
let (str = {}) {
|
||||
let read = 0;
|
||||
do {
|
||||
read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
|
||||
data += str.value;
|
||||
} while (read != 0);
|
||||
}
|
||||
if (!inputDB.hasOwnProperty("addons") ||
|
||||
!Array.isArray(inputDB.addons)) {
|
||||
throw new Error("No addons array.");
|
||||
}
|
||||
|
||||
inputDB = JSON.parse(data);
|
||||
if (!inputDB.hasOwnProperty("schema")) {
|
||||
throw new Error("No schema specified.");
|
||||
}
|
||||
|
||||
if (!inputDB.hasOwnProperty("addons") ||
|
||||
!Array.isArray(inputDB.addons)) {
|
||||
throw new Error("No addons array.");
|
||||
}
|
||||
schema = parseInt(inputDB.schema, 10);
|
||||
|
||||
if (!inputDB.hasOwnProperty("schema")) {
|
||||
throw new Error("No schema specified.");
|
||||
}
|
||||
if (!Number.isInteger(schema) ||
|
||||
schema < DB_MIN_JSON_SCHEMA) {
|
||||
throw new Error("Invalid schema value.");
|
||||
}
|
||||
} catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
|
||||
logger.debug("No " + FILE_DATABASE + " found.");
|
||||
|
||||
schema = parseInt(inputDB.schema, 10);
|
||||
// Create a blank addons.json file
|
||||
this._saveDBToDisk();
|
||||
|
||||
if (!Number.isInteger(schema) ||
|
||||
schema < DB_MIN_JSON_SCHEMA) {
|
||||
throw new Error("Invalid schema value.");
|
||||
}
|
||||
let dbSchema = 0;
|
||||
try {
|
||||
dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA);
|
||||
} catch (e) {}
|
||||
|
||||
} catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
logger.debug("No " + FILE_DATABASE + " found.");
|
||||
if (dbSchema < DB_MIN_JSON_SCHEMA) {
|
||||
let results = yield new Promise((resolve, reject) => {
|
||||
AddonRepository_SQLiteMigrator.migrate(resolve);
|
||||
});
|
||||
|
||||
// Create a blank addons.json file
|
||||
this._saveDBToDisk();
|
||||
if (results.length) {
|
||||
yield this._insertAddons(results);
|
||||
}
|
||||
|
||||
let dbSchema = 0;
|
||||
try {
|
||||
dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA);
|
||||
} catch (e) {}
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
}
|
||||
|
||||
if (dbSchema < DB_MIN_JSON_SCHEMA) {
|
||||
this._migrationInProgress = AddonRepository_SQLiteMigrator.migrate((results) => {
|
||||
if (results.length)
|
||||
this.insertAddons(results);
|
||||
return this.DB;
|
||||
} catch (e) {
|
||||
logger.error("Malformed " + FILE_DATABASE + ": " + e);
|
||||
this.databaseOk = false;
|
||||
|
||||
if (this._postMigrationCallback) {
|
||||
this._postMigrationCallback();
|
||||
this._postMigrationCallback = null;
|
||||
}
|
||||
return this.DB;
|
||||
}
|
||||
|
||||
this._migrationInProgress = false;
|
||||
});
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
}
|
||||
// We use _insertAddon manually instead of calling
|
||||
// insertAddons to avoid the write to disk which would
|
||||
// be a waste since this is the data that was just read.
|
||||
for (let addon of inputDB.addons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
} catch (e) {
|
||||
logger.error("Malformed " + FILE_DATABASE + ": " + e);
|
||||
this.databaseOk = false;
|
||||
return;
|
||||
|
||||
} finally {
|
||||
cstream.close();
|
||||
fstream.close();
|
||||
return this.DB;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
|
||||
// We use _insertAddon manually instead of calling
|
||||
// insertAddons to avoid the write to disk which would
|
||||
// be a waste since this is the data that was just read.
|
||||
for (let addon of inputDB.addons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
return this.connectionPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1656,18 +1633,14 @@ var AddonDatabase = {
|
||||
shutdown: function AD_shutdown(aSkipFlush) {
|
||||
this.databaseOk = true;
|
||||
|
||||
if (!this.initialized) {
|
||||
return Promise.resolve(0);
|
||||
if (!this.connectionPromise) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.initialized = false;
|
||||
|
||||
this.__defineGetter__("connection", function shutdown_connectionGetter() {
|
||||
return this.openConnection();
|
||||
});
|
||||
this.connectionPromise = null;
|
||||
|
||||
if (aSkipFlush) {
|
||||
return Promise.resolve(0);
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.Writer.flush();
|
||||
}
|
||||
@ -1687,9 +1660,9 @@ var AddonDatabase = {
|
||||
.then(null, () => {})
|
||||
// shutdown(true) never rejects
|
||||
.then(() => this.shutdown(true))
|
||||
.then(() => OS.File.remove(this.jsonFile.path, {}))
|
||||
.then(() => OS.File.remove(this.jsonFile, {}))
|
||||
.then(null, error => logger.error("Unable to delete Addon Repository file " +
|
||||
this.jsonFile.path, error))
|
||||
this.jsonFile, error))
|
||||
.then(() => this._deleting = null)
|
||||
.then(aCallback);
|
||||
},
|
||||
@ -1714,7 +1687,7 @@ var AddonDatabase = {
|
||||
get Writer() {
|
||||
delete this.Writer;
|
||||
this.Writer = new DeferredSave(
|
||||
this.jsonFile.path,
|
||||
this.jsonFile,
|
||||
() => { return JSON.stringify(this); },
|
||||
DB_BATCH_TIMEOUT_MS
|
||||
);
|
||||
@ -1741,23 +1714,16 @@ var AddonDatabase = {
|
||||
* @param aCallback
|
||||
* The callback to pass the add-ons back to
|
||||
*/
|
||||
retrieveStoredData: function AD_retrieveStoredData(aCallback) {
|
||||
if (!this.initialized)
|
||||
this.openConnection();
|
||||
retrieveStoredData: Task.async(function* (){
|
||||
let db = yield this.openConnection();
|
||||
let result = {};
|
||||
|
||||
let gatherResults = () => {
|
||||
let result = {};
|
||||
for (let [key, value] of this.DB.addons)
|
||||
result[key] = value;
|
||||
for (let [key, value] of db.addons) {
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
executeSoon(function() aCallback(result));
|
||||
};
|
||||
|
||||
if (this._migrationInProgress)
|
||||
this._postMigrationCallback = gatherResults;
|
||||
else
|
||||
gatherResults();
|
||||
},
|
||||
return result;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Asynchronously repopulates the database so it only contains the
|
||||
@ -1781,19 +1747,19 @@ var AddonDatabase = {
|
||||
* @param aCallback
|
||||
* An optional callback to call once complete
|
||||
*/
|
||||
insertAddons: function AD_insertAddons(aAddons, aCallback) {
|
||||
if (!this.initialized)
|
||||
this.openConnection();
|
||||
insertAddons: Task.async(function* (aAddons, aCallback) {
|
||||
yield this.openConnection();
|
||||
yield this._insertAddons(aAddons, aCallback);
|
||||
}),
|
||||
|
||||
_insertAddons: Task.async(function* (aAddons, aCallback) {
|
||||
for (let addon of aAddons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
|
||||
this._saveDBToDisk();
|
||||
|
||||
if (aCallback)
|
||||
executeSoon(aCallback);
|
||||
},
|
||||
yield this._saveDBToDisk();
|
||||
aCallback && aCallback();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Inserts an individual add-on into the database. If the add-on already
|
||||
@ -1997,7 +1963,3 @@ var AddonDatabase = {
|
||||
appMaxVersion);
|
||||
},
|
||||
};
|
||||
|
||||
function executeSoon(aCallback) {
|
||||
Services.tm.mainThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user