mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1896 lines
63 KiB
JavaScript
1896 lines
63 KiB
JavaScript
/* 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 Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
|
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
|
|
"resource://gre/modules/AddonRepository.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
|
"resource://gre/modules/FileUtils.jsm");
|
|
|
|
|
|
["LOG", "WARN", "ERROR"].forEach(function(aName) {
|
|
this.__defineGetter__(aName, function() {
|
|
Components.utils.import("resource://gre/modules/AddonLogging.jsm");
|
|
|
|
LogManager.getLogger("addons.xpi-utils", this);
|
|
return this[aName];
|
|
})
|
|
}, this);
|
|
|
|
|
|
const KEY_PROFILEDIR = "ProfD";
|
|
const FILE_DATABASE = "extensions.sqlite";
|
|
const FILE_OLD_DATABASE = "extensions.rdf";
|
|
const FILE_XPI_ADDONS_LIST = "extensions.ini";
|
|
|
|
// The value for this is in Makefile.in
|
|
#expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__;
|
|
|
|
const PREF_DB_SCHEMA = "extensions.databaseSchema";
|
|
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
|
|
const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
|
|
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
|
|
|
|
|
|
// Properties that only exist in the database
|
|
const DB_METADATA = ["syncGUID",
|
|
"installDate",
|
|
"updateDate",
|
|
"size",
|
|
"sourceURI",
|
|
"releaseNotesURI",
|
|
"applyBackgroundUpdates"];
|
|
const DB_BOOL_METADATA = ["visible", "active", "userDisabled", "appDisabled",
|
|
"pendingUninstall", "bootstrap", "skinnable",
|
|
"softDisabled", "isForeignInstall",
|
|
"hasBinaryComponents", "strictCompatibility"];
|
|
|
|
const FIELDS_ADDON = "internal_id, id, syncGUID, location, version, type, " +
|
|
"internalName, updateURL, updateKey, optionsURL, " +
|
|
"optionsType, aboutURL, iconURL, icon64URL, " +
|
|
"defaultLocale, visible, active, userDisabled, " +
|
|
"appDisabled, pendingUninstall, descriptor, " +
|
|
"installDate, updateDate, applyBackgroundUpdates, bootstrap, " +
|
|
"skinnable, size, sourceURI, releaseNotesURI, softDisabled, " +
|
|
"isForeignInstall, hasBinaryComponents, strictCompatibility";
|
|
|
|
|
|
// Properties that exist in the install manifest
|
|
const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
|
|
"updateKey", "optionsURL", "optionsType", "aboutURL",
|
|
"iconURL", "icon64URL"];
|
|
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
|
|
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
|
|
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
|
|
|
|
|
|
|
|
const PREFIX_ITEM_URI = "urn:mozilla:item:";
|
|
const RDFURI_ITEM_ROOT = "urn:mozilla:item:root"
|
|
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
|
|
|
|
|
|
var XPIProvider;
|
|
|
|
|
|
this.__defineGetter__("gRDF", function() {
|
|
delete this.gRDF;
|
|
return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
|
|
getService(Ci.nsIRDFService);
|
|
});
|
|
|
|
function EM_R(aProperty) {
|
|
return gRDF.GetResource(PREFIX_NS_EM + aProperty);
|
|
}
|
|
|
|
/**
|
|
* Converts an RDF literal, resource or integer into a string.
|
|
*
|
|
* @param aLiteral
|
|
* The RDF object to convert
|
|
* @return a string if the object could be converted or null
|
|
*/
|
|
function getRDFValue(aLiteral) {
|
|
if (aLiteral instanceof Ci.nsIRDFLiteral)
|
|
return aLiteral.Value;
|
|
if (aLiteral instanceof Ci.nsIRDFResource)
|
|
return aLiteral.Value;
|
|
if (aLiteral instanceof Ci.nsIRDFInt)
|
|
return aLiteral.Value;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets an RDF property as a string
|
|
*
|
|
* @param aDs
|
|
* The RDF datasource to read the property from
|
|
* @param aResource
|
|
* The RDF resource to read the property from
|
|
* @param aProperty
|
|
* The property to read
|
|
* @return a string if the property existed or null
|
|
*/
|
|
function getRDFProperty(aDs, aResource, aProperty) {
|
|
return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
|
|
}
|
|
|
|
|
|
/**
|
|
* A mozIStorageStatementCallback that will asynchronously build DBAddonInternal
|
|
* instances from the results it receives. Once the statement has completed
|
|
* executing and all of the metadata for all of the add-ons has been retrieved
|
|
* they will be passed as an array to aCallback.
|
|
*
|
|
* @param aCallback
|
|
* A callback function to pass the array of DBAddonInternals to
|
|
*/
|
|
function AsyncAddonListCallback(aCallback) {
|
|
this.callback = aCallback;
|
|
this.addons = [];
|
|
}
|
|
|
|
AsyncAddonListCallback.prototype = {
|
|
callback: null,
|
|
complete: false,
|
|
count: 0,
|
|
addons: null,
|
|
|
|
handleResult: function(aResults) {
|
|
let row = null;
|
|
while ((row = aResults.getNextRow())) {
|
|
this.count++;
|
|
let self = this;
|
|
XPIDatabase.makeAddonFromRowAsync(row, function(aAddon) {
|
|
function completeAddon(aRepositoryAddon) {
|
|
aAddon._repositoryAddon = aRepositoryAddon;
|
|
aAddon.compatibilityOverrides = aRepositoryAddon ?
|
|
aRepositoryAddon.compatibilityOverrides :
|
|
null;
|
|
self.addons.push(aAddon);
|
|
if (self.complete && self.addons.length == self.count)
|
|
self.callback(self.addons);
|
|
}
|
|
|
|
if ("getCachedAddonByID" in AddonRepository)
|
|
AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
|
|
else
|
|
completeAddon(null);
|
|
});
|
|
}
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
this.complete = true;
|
|
if (this.addons.length == this.count)
|
|
this.callback(this.addons);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* A generator to synchronously return result rows from an mozIStorageStatement.
|
|
*
|
|
* @param aStatement
|
|
* The statement to execute
|
|
*/
|
|
function resultRows(aStatement) {
|
|
try {
|
|
while (stepStatement(aStatement))
|
|
yield aStatement.row;
|
|
}
|
|
finally {
|
|
aStatement.reset();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* A helper function to log an SQL error.
|
|
*
|
|
* @param aError
|
|
* The storage error code associated with the error
|
|
* @param aErrorString
|
|
* An error message
|
|
*/
|
|
function logSQLError(aError, aErrorString) {
|
|
ERROR("SQL error " + aError + ": " + aErrorString);
|
|
}
|
|
|
|
/**
|
|
* A helper function to log any errors that occur during async statements.
|
|
*
|
|
* @param aError
|
|
* A mozIStorageError to log
|
|
*/
|
|
function asyncErrorLogger(aError) {
|
|
logSQLError(aError.result, aError.message);
|
|
}
|
|
|
|
/**
|
|
* A helper function to execute a statement synchronously and log any error
|
|
* that occurs.
|
|
*
|
|
* @param aStatement
|
|
* A mozIStorageStatement to execute
|
|
*/
|
|
function executeStatement(aStatement) {
|
|
try {
|
|
aStatement.execute();
|
|
}
|
|
catch (e) {
|
|
logSQLError(XPIDatabase.connection.lastError,
|
|
XPIDatabase.connection.lastErrorString);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A helper function to step a statement synchronously and log any error that
|
|
* occurs.
|
|
*
|
|
* @param aStatement
|
|
* A mozIStorageStatement to execute
|
|
*/
|
|
function stepStatement(aStatement) {
|
|
try {
|
|
return aStatement.executeStep();
|
|
}
|
|
catch (e) {
|
|
logSQLError(XPIDatabase.connection.lastError,
|
|
XPIDatabase.connection.lastErrorString);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Copies properties from one object to another. If no target object is passed
|
|
* a new object will be created and returned.
|
|
*
|
|
* @param aObject
|
|
* An object to copy from
|
|
* @param aProperties
|
|
* An array of properties to be copied
|
|
* @param aTarget
|
|
* An optional target object to copy the properties to
|
|
* @return the object that the properties were copied onto
|
|
*/
|
|
function copyProperties(aObject, aProperties, aTarget) {
|
|
if (!aTarget)
|
|
aTarget = {};
|
|
aProperties.forEach(function(aProp) {
|
|
aTarget[aProp] = aObject[aProp];
|
|
});
|
|
return aTarget;
|
|
}
|
|
|
|
/**
|
|
* Copies properties from a mozIStorageRow to an object. If no target object is
|
|
* passed a new object will be created and returned.
|
|
*
|
|
* @param aRow
|
|
* A mozIStorageRow to copy from
|
|
* @param aProperties
|
|
* An array of properties to be copied
|
|
* @param aTarget
|
|
* An optional target object to copy the properties to
|
|
* @return the object that the properties were copied onto
|
|
*/
|
|
function copyRowProperties(aRow, aProperties, aTarget) {
|
|
if (!aTarget)
|
|
aTarget = {};
|
|
aProperties.forEach(function(aProp) {
|
|
aTarget[aProp] = aRow.getResultByName(aProp);
|
|
});
|
|
return aTarget;
|
|
}
|
|
|
|
var XPIDatabase = {
|
|
// true if the database connection has been opened
|
|
initialized: false,
|
|
// A cache of statements that are used and need to be finalized on shutdown
|
|
statementCache: {},
|
|
// A cache of weak referenced DBAddonInternals so we can reuse objects where
|
|
// possible
|
|
addonCache: [],
|
|
// The nested transaction count
|
|
transactionCount: 0,
|
|
// The database file
|
|
dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true),
|
|
|
|
// The statements used by the database
|
|
statements: {
|
|
_getDefaultLocale: "SELECT id, name, description, creator, homepageURL " +
|
|
"FROM locale WHERE id=:id",
|
|
_getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " +
|
|
"locale.description, locale.creator, locale.homepageURL " +
|
|
"FROM addon_locale JOIN locale ON " +
|
|
"addon_locale.locale_id=locale.id WHERE " +
|
|
"addon_internal_id=:internal_id",
|
|
_getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
|
|
"maxVersion FROM targetApplication WHERE " +
|
|
"addon_internal_id=:internal_id",
|
|
_getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
|
|
"addon_internal_id=:internal_id",
|
|
_readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
|
|
"WHERE locale_id=:id",
|
|
|
|
addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " +
|
|
":location, :version, :type, :internalName, " +
|
|
":updateURL, :updateKey, :optionsURL, " +
|
|
":optionsType, :aboutURL, " +
|
|
":iconURL, :icon64URL, :locale, :visible, :active, " +
|
|
":userDisabled, :appDisabled, :pendingUninstall, " +
|
|
":descriptor, :installDate, :updateDate, " +
|
|
":applyBackgroundUpdates, :bootstrap, :skinnable, " +
|
|
":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
|
|
":isForeignInstall, :hasBinaryComponents, " +
|
|
":strictCompatibility)",
|
|
addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
|
|
"(:internal_id, :name, :locale)",
|
|
addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
|
|
"homepageURL) VALUES (:name, :description, " +
|
|
":creator, :homepageURL)",
|
|
addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
|
|
":type, :value)",
|
|
addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
|
|
"(:internal_id, :id, :minVersion, " +
|
|
":maxVersion)",
|
|
addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " +
|
|
"(:internal_id, :os, :abi)",
|
|
|
|
clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
|
|
updateAddonActive: "UPDATE addon SET active=:active WHERE " +
|
|
"internal_id=:internal_id",
|
|
|
|
getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
|
|
"type<>'theme' AND bootstrap=0",
|
|
getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
|
|
"internalName=:internalName AND type='theme'",
|
|
getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'",
|
|
|
|
getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " +
|
|
"AND location=:location",
|
|
getAddons: "SELECT " + FIELDS_ADDON + " FROM addon",
|
|
getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type",
|
|
getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
|
|
"location=:location",
|
|
getInstallLocations: "SELECT DISTINCT location FROM addon",
|
|
getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
|
|
"visible=1 AND id=:id",
|
|
getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " +
|
|
"WHERE visible=1 AND internalName=:internalName",
|
|
getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1",
|
|
getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " +
|
|
"addon WHERE visible=1 " +
|
|
"AND (pendingUninstall=1 OR " +
|
|
"MAX(userDisabled,appDisabled)=active)",
|
|
getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " +
|
|
"WHERE syncGUID=:syncGUID",
|
|
makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
|
|
removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
|
|
// Equates to active = visible && !userDisabled && !softDisabled &&
|
|
// !appDisabled && !pendingUninstall
|
|
setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
|
|
"1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)",
|
|
setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
|
|
"appDisabled=:appDisabled, " +
|
|
"softDisabled=:softDisabled, " +
|
|
"pendingUninstall=:pendingUninstall, " +
|
|
"applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
|
|
"internal_id=:internal_id",
|
|
setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
|
|
"internal_id=:internal_id",
|
|
setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " +
|
|
"internal_id=:internal_id",
|
|
updateTargetApplications: "UPDATE targetApplication SET " +
|
|
"minVersion=:minVersion, maxVersion=:maxVersion " +
|
|
"WHERE addon_internal_id=:internal_id AND id=:id",
|
|
|
|
createSavepoint: "SAVEPOINT 'default'",
|
|
releaseSavepoint: "RELEASE SAVEPOINT 'default'",
|
|
rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
|
|
},
|
|
|
|
get dbfileExists() {
|
|
delete this.dbfileExists;
|
|
return this.dbfileExists = this.dbfile.exists();
|
|
},
|
|
set dbfileExists(aValue) {
|
|
delete this.dbfileExists;
|
|
return this.dbfileExists = aValue;
|
|
},
|
|
|
|
/**
|
|
* Begins a new transaction in the database. Transactions may be nested. Data
|
|
* written by an inner transaction may be rolled back on its own. Rolling back
|
|
* an outer transaction will rollback all the changes made by inner
|
|
* transactions even if they were committed. No data is written to the disk
|
|
* until the outermost transaction is committed. Transactions can be started
|
|
* even when the database is not yet open in which case they will be started
|
|
* when the database is first opened.
|
|
*/
|
|
beginTransaction: function XPIDB_beginTransaction() {
|
|
if (this.initialized)
|
|
this.getStatement("createSavepoint").execute();
|
|
this.transactionCount++;
|
|
},
|
|
|
|
/**
|
|
* Commits the most recent transaction. The data may still be rolled back if
|
|
* an outer transaction is rolled back.
|
|
*/
|
|
commitTransaction: function XPIDB_commitTransaction() {
|
|
if (this.transactionCount == 0) {
|
|
ERROR("Attempt to commit one transaction too many.");
|
|
return;
|
|
}
|
|
|
|
if (this.initialized)
|
|
this.getStatement("releaseSavepoint").execute();
|
|
this.transactionCount--;
|
|
},
|
|
|
|
/**
|
|
* Rolls back the most recent transaction. The database will return to its
|
|
* state when the transaction was started.
|
|
*/
|
|
rollbackTransaction: function XPIDB_rollbackTransaction() {
|
|
if (this.transactionCount == 0) {
|
|
ERROR("Attempt to rollback one transaction too many.");
|
|
return;
|
|
}
|
|
|
|
if (this.initialized) {
|
|
this.getStatement("rollbackSavepoint").execute();
|
|
this.getStatement("releaseSavepoint").execute();
|
|
}
|
|
this.transactionCount--;
|
|
},
|
|
|
|
/**
|
|
* Attempts to open the database file. If it fails it will try to delete the
|
|
* existing file and create an empty database. If that fails then it will
|
|
* open an in-memory database that can be used during this session.
|
|
*
|
|
* @param aDBFile
|
|
* The nsIFile to open
|
|
* @return the mozIStorageConnection for the database
|
|
*/
|
|
openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) {
|
|
LOG("Opening database");
|
|
let connection = null;
|
|
|
|
// Attempt to open the database
|
|
try {
|
|
connection = Services.storage.openUnsharedDatabase(aDBFile);
|
|
this.dbfileExists = true;
|
|
}
|
|
catch (e) {
|
|
ERROR("Failed to open database (1st attempt)", e);
|
|
try {
|
|
aDBFile.remove(true);
|
|
}
|
|
catch (e) {
|
|
ERROR("Failed to remove database that could not be opened", e);
|
|
}
|
|
try {
|
|
connection = Services.storage.openUnsharedDatabase(aDBFile);
|
|
}
|
|
catch (e) {
|
|
ERROR("Failed to open database (2nd attempt)", e);
|
|
|
|
// If we have got here there seems to be no way to open the real
|
|
// database, instead open a temporary memory database so things will
|
|
// work for this session
|
|
return Services.storage.openSpecialDatabase("memory");
|
|
}
|
|
}
|
|
|
|
connection.executeSimpleSQL("PRAGMA synchronous = FULL");
|
|
connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
|
|
|
|
return connection;
|
|
},
|
|
|
|
/**
|
|
* Opens a new connection to the database file.
|
|
*
|
|
* @param aRebuildOnError
|
|
* A boolean indicating whether add-on information should be loaded
|
|
* from the install locations if the database needs to be rebuilt.
|
|
* @return the migration data from the database if it was an old schema or
|
|
* null otherwise.
|
|
*/
|
|
openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
|
|
delete this.connection;
|
|
|
|
if (!aForceOpen && !this.dbfileExists) {
|
|
this.connection = null;
|
|
return {};
|
|
}
|
|
|
|
this.initialized = true;
|
|
|
|
this.connection = this.openDatabaseFile(this.dbfile);
|
|
|
|
let migrateData = null;
|
|
// If the database was corrupt or missing then the new blank database will
|
|
// have a schema version of 0.
|
|
let schemaVersion = this.connection.schemaVersion;
|
|
if (schemaVersion != DB_SCHEMA) {
|
|
// A non-zero schema version means that a schema has been successfully
|
|
// created in the database in the past so we might be able to get useful
|
|
// information from it
|
|
if (schemaVersion != 0) {
|
|
LOG("Migrating data from schema " + schemaVersion);
|
|
migrateData = this.getMigrateDataFromDatabase();
|
|
|
|
// Delete the existing database
|
|
this.connection.close();
|
|
try {
|
|
if (this.dbfileExists)
|
|
this.dbfile.remove(true);
|
|
|
|
// Reopen an empty database
|
|
this.connection = this.openDatabaseFile(this.dbfile);
|
|
}
|
|
catch (e) {
|
|
ERROR("Failed to remove old database", e);
|
|
// If the file couldn't be deleted then fall back to an in-memory
|
|
// database
|
|
this.connection = Services.storage.openSpecialDatabase("memory");
|
|
}
|
|
}
|
|
else {
|
|
let dbSchema = 0;
|
|
try {
|
|
dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA);
|
|
} catch (e) {}
|
|
|
|
if (dbSchema == 0) {
|
|
// Only migrate data from the RDF if we haven't done it before
|
|
migrateData = this.getMigrateDataFromRDF();
|
|
}
|
|
}
|
|
|
|
// At this point the database should be completely empty
|
|
this.createSchema();
|
|
|
|
if (aRebuildOnError) {
|
|
let activeBundles = this.getActiveBundles();
|
|
WARN("Rebuilding add-ons database from installed extensions.");
|
|
this.beginTransaction();
|
|
try {
|
|
let state = XPIProvider.getInstallLocationStates();
|
|
XPIProvider.processFileChanges(state, {}, false, undefined, undefined,
|
|
migrateData, activeBundles)
|
|
// Make sure to update the active add-ons and add-ons list on shutdown
|
|
Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
|
|
this.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
ERROR("Error processing file changes", e);
|
|
this.rollbackTransaction();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the database connection has a file open then it has the right schema
|
|
// by now so make sure the preferences reflect that. If not then there is
|
|
// an in-memory database open which means a problem opening and deleting the
|
|
// real database, clear the schema preference to force trying to load the
|
|
// database on the next startup
|
|
if (this.connection.databaseFile) {
|
|
Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
|
|
}
|
|
else {
|
|
try {
|
|
Services.prefs.clearUserPref(PREF_DB_SCHEMA);
|
|
}
|
|
catch (e) {
|
|
// The preference may not be defined
|
|
}
|
|
}
|
|
Services.prefs.savePrefFile(null);
|
|
|
|
// Begin any pending transactions
|
|
for (let i = 0; i < this.transactionCount; i++)
|
|
this.connection.executeSimpleSQL("SAVEPOINT 'default'");
|
|
return migrateData;
|
|
},
|
|
|
|
/**
|
|
* A lazy getter for the database connection.
|
|
*/
|
|
get connection() {
|
|
this.openConnection(true);
|
|
return this.connection;
|
|
},
|
|
|
|
/**
|
|
* Gets the list of file descriptors of active extension directories or XPI
|
|
* files from the add-ons list. This must be loaded from disk since the
|
|
* directory service gives no easy way to get both directly. This list doesn't
|
|
* include themes as preferences already say which theme is currently active
|
|
*
|
|
* @return an array of persisitent descriptors for the directories
|
|
*/
|
|
getActiveBundles: function XPIDB_getActiveBundles() {
|
|
let bundles = [];
|
|
|
|
let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
|
|
true);
|
|
|
|
let iniFactory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
|
|
getService(Ci.nsIINIParserFactory);
|
|
let parser = iniFactory.createINIParser(addonsList);
|
|
|
|
let keys = parser.getKeys("ExtensionDirs");
|
|
|
|
while (keys.hasMore())
|
|
bundles.push(parser.getString("ExtensionDirs", keys.getNext()));
|
|
|
|
// Also include the list of active bootstrapped extensions
|
|
for (let id in XPIProvider.bootstrappedAddons)
|
|
bundles.push(XPIProvider.bootstrappedAddons[id].descriptor);
|
|
|
|
return bundles;
|
|
},
|
|
|
|
/**
|
|
* Retrieves migration data from the old extensions.rdf database.
|
|
*
|
|
* @return an object holding information about what add-ons were previously
|
|
* userDisabled and any updated compatibility information
|
|
*/
|
|
getMigrateDataFromRDF: function XPIDB_getMigrateDataFromRDF(aDbWasMissing) {
|
|
let migrateData = {};
|
|
|
|
// Migrate data from extensions.rdf
|
|
let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true);
|
|
if (rdffile.exists()) {
|
|
LOG("Migrating data from extensions.rdf");
|
|
let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec);
|
|
let root = Cc["@mozilla.org/rdf/container;1"].
|
|
createInstance(Ci.nsIRDFContainer);
|
|
root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
|
|
let elements = root.GetElements();
|
|
while (elements.hasMoreElements()) {
|
|
let source = elements.getNext().QueryInterface(Ci.nsIRDFResource);
|
|
|
|
let location = getRDFProperty(ds, source, "installLocation");
|
|
if (location) {
|
|
if (!(location in migrateData))
|
|
migrateData[location] = {};
|
|
let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length);
|
|
migrateData[location][id] = {
|
|
version: getRDFProperty(ds, source, "version"),
|
|
userDisabled: false,
|
|
targetApplications: []
|
|
}
|
|
|
|
let disabled = getRDFProperty(ds, source, "userDisabled");
|
|
if (disabled == "true" || disabled == "needs-disable")
|
|
migrateData[location][id].userDisabled = true;
|
|
|
|
let targetApps = ds.GetTargets(source, EM_R("targetApplication"),
|
|
true);
|
|
while (targetApps.hasMoreElements()) {
|
|
let targetApp = targetApps.getNext()
|
|
.QueryInterface(Ci.nsIRDFResource);
|
|
let appInfo = {
|
|
id: getRDFProperty(ds, targetApp, "id")
|
|
};
|
|
|
|
let minVersion = getRDFProperty(ds, targetApp, "updatedMinVersion");
|
|
if (minVersion) {
|
|
appInfo.minVersion = minVersion;
|
|
appInfo.maxVersion = getRDFProperty(ds, targetApp, "updatedMaxVersion");
|
|
}
|
|
else {
|
|
appInfo.minVersion = getRDFProperty(ds, targetApp, "minVersion");
|
|
appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion");
|
|
}
|
|
migrateData[location][id].targetApplications.push(appInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return migrateData;
|
|
},
|
|
|
|
/**
|
|
* Retrieves migration data from a database that has an older or newer schema.
|
|
*
|
|
* @return an object holding information about what add-ons were previously
|
|
* userDisabled and any updated compatibility information
|
|
*/
|
|
getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() {
|
|
let migrateData = {};
|
|
|
|
// Attempt to migrate data from a different (even future!) version of the
|
|
// database
|
|
try {
|
|
var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
|
|
|
|
const REQUIRED = ["internal_id", "id", "location", "userDisabled",
|
|
"installDate", "version"];
|
|
|
|
let reqCount = 0;
|
|
let props = [];
|
|
for (let row in resultRows(stmt)) {
|
|
if (REQUIRED.indexOf(row.name) != -1) {
|
|
reqCount++;
|
|
props.push(row.name);
|
|
}
|
|
else if (DB_METADATA.indexOf(row.name) != -1) {
|
|
props.push(row.name);
|
|
}
|
|
else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
|
|
props.push(row.name);
|
|
}
|
|
}
|
|
|
|
if (reqCount < REQUIRED.length) {
|
|
ERROR("Unable to read anything useful from the database");
|
|
return migrateData;
|
|
}
|
|
stmt.finalize();
|
|
|
|
stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
|
|
for (let row in resultRows(stmt)) {
|
|
if (!(row.location in migrateData))
|
|
migrateData[row.location] = {};
|
|
let addonData = {
|
|
targetApplications: []
|
|
}
|
|
migrateData[row.location][row.id] = addonData;
|
|
|
|
props.forEach(function(aProp) {
|
|
if (DB_BOOL_METADATA.indexOf(aProp) != -1)
|
|
addonData[aProp] = row[aProp] == 1;
|
|
else
|
|
addonData[aProp] = row[aProp];
|
|
})
|
|
}
|
|
|
|
var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
|
|
"maxVersion FROM " +
|
|
"targetApplication WHERE " +
|
|
"addon_internal_id=:internal_id");
|
|
|
|
for (let location in migrateData) {
|
|
for (let id in migrateData[location]) {
|
|
taStmt.params.internal_id = migrateData[location][id].internal_id;
|
|
delete migrateData[location][id].internal_id;
|
|
for (let row in resultRows(taStmt)) {
|
|
migrateData[location][id].targetApplications.push({
|
|
id: row.id,
|
|
minVersion: row.minVersion,
|
|
maxVersion: row.maxVersion
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
// An error here means the schema is too different to read
|
|
ERROR("Error migrating data", e);
|
|
}
|
|
finally {
|
|
if (taStmt)
|
|
taStmt.finalize();
|
|
if (stmt)
|
|
stmt.finalize();
|
|
}
|
|
|
|
return migrateData;
|
|
},
|
|
|
|
/**
|
|
* Shuts down the database connection and releases all cached objects.
|
|
*/
|
|
shutdown: function XPIDB_shutdown(aCallback) {
|
|
LOG("shutdown");
|
|
if (this.initialized) {
|
|
for each (let stmt in this.statementCache)
|
|
stmt.finalize();
|
|
this.statementCache = {};
|
|
this.addonCache = [];
|
|
|
|
if (this.transactionCount > 0) {
|
|
ERROR(this.transactionCount + " outstanding transactions, rolling back.");
|
|
while (this.transactionCount > 0)
|
|
this.rollbackTransaction();
|
|
}
|
|
|
|
this.initialized = false;
|
|
let connection = this.connection;
|
|
delete this.connection;
|
|
|
|
// Re-create the connection smart getter to allow the database to be
|
|
// re-loaded during testing.
|
|
this.__defineGetter__("connection", function() {
|
|
this.openConnection(true);
|
|
return this.connection;
|
|
});
|
|
|
|
connection.asyncClose(function() {
|
|
LOG("Database closed");
|
|
aCallback();
|
|
});
|
|
}
|
|
else {
|
|
if (aCallback)
|
|
aCallback();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets a cached statement or creates a new statement if it doesn't already
|
|
* exist.
|
|
*
|
|
* @param key
|
|
* A unique key to reference the statement
|
|
* @param aSql
|
|
* An optional SQL string to use for the query, otherwise a
|
|
* predefined sql string for the key will be used.
|
|
* @return a mozIStorageStatement for the passed SQL
|
|
*/
|
|
getStatement: function XPIDB_getStatement(aKey, aSql) {
|
|
if (aKey in this.statementCache)
|
|
return this.statementCache[aKey];
|
|
if (!aSql)
|
|
aSql = this.statements[aKey];
|
|
|
|
try {
|
|
return this.statementCache[aKey] = this.connection.createStatement(aSql);
|
|
}
|
|
catch (e) {
|
|
ERROR("Error creating statement " + aKey + " (" + aSql + ")");
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates the schema in the database.
|
|
*/
|
|
createSchema: function XPIDB_createSchema() {
|
|
LOG("Creating database schema");
|
|
this.beginTransaction();
|
|
|
|
// Any errors in here should rollback the transaction
|
|
try {
|
|
this.connection.createTable("addon",
|
|
"internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
"id TEXT, syncGUID TEXT, " +
|
|
"location TEXT, version TEXT, " +
|
|
"type TEXT, internalName TEXT, updateURL TEXT, " +
|
|
"updateKey TEXT, optionsURL TEXT, " +
|
|
"optionsType TEXT, aboutURL TEXT, iconURL TEXT, " +
|
|
"icon64URL TEXT, defaultLocale INTEGER, " +
|
|
"visible INTEGER, active INTEGER, " +
|
|
"userDisabled INTEGER, appDisabled INTEGER, " +
|
|
"pendingUninstall INTEGER, descriptor TEXT, " +
|
|
"installDate INTEGER, updateDate INTEGER, " +
|
|
"applyBackgroundUpdates INTEGER, " +
|
|
"bootstrap INTEGER, skinnable INTEGER, " +
|
|
"size INTEGER, sourceURI TEXT, " +
|
|
"releaseNotesURI TEXT, softDisabled INTEGER, " +
|
|
"isForeignInstall INTEGER, " +
|
|
"hasBinaryComponents INTEGER, " +
|
|
"strictCompatibility INTEGER, " +
|
|
"UNIQUE (id, location), " +
|
|
"UNIQUE (syncGUID)");
|
|
this.connection.createTable("targetApplication",
|
|
"addon_internal_id INTEGER, " +
|
|
"id TEXT, minVersion TEXT, maxVersion TEXT, " +
|
|
"UNIQUE (addon_internal_id, id)");
|
|
this.connection.createTable("targetPlatform",
|
|
"addon_internal_id INTEGER, " +
|
|
"os, abi TEXT, " +
|
|
"UNIQUE (addon_internal_id, os, abi)");
|
|
this.connection.createTable("addon_locale",
|
|
"addon_internal_id INTEGER, "+
|
|
"locale TEXT, locale_id INTEGER, " +
|
|
"UNIQUE (addon_internal_id, locale)");
|
|
this.connection.createTable("locale",
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
"name TEXT, description TEXT, creator TEXT, " +
|
|
"homepageURL TEXT");
|
|
this.connection.createTable("locale_strings",
|
|
"locale_id INTEGER, type TEXT, value TEXT");
|
|
this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " +
|
|
"locale_strings (locale_id)");
|
|
this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
|
|
"ON addon BEGIN " +
|
|
"DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " +
|
|
"DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " +
|
|
"DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " +
|
|
"DELETE FROM locale WHERE id=old.defaultLocale; " +
|
|
"END");
|
|
this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " +
|
|
"DELETE ON addon_locale WHEN NOT EXISTS " +
|
|
"(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " +
|
|
"DELETE FROM locale WHERE id=old.locale_id; " +
|
|
"END");
|
|
this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " +
|
|
"DELETE ON locale BEGIN " +
|
|
"DELETE FROM locale_strings WHERE locale_id=old.id; " +
|
|
"END");
|
|
this.connection.schemaVersion = DB_SCHEMA;
|
|
this.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
ERROR("Failed to create database schema", e);
|
|
logSQLError(this.connection.lastError, this.connection.lastErrorString);
|
|
this.rollbackTransaction();
|
|
this.connection.close();
|
|
this.connection = null;
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads the multi-value locale strings for a locale
|
|
*
|
|
* @param aLocale
|
|
* The locale object to read into
|
|
*/
|
|
_readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) {
|
|
let stmt = this.getStatement("_readLocaleStrings");
|
|
|
|
stmt.params.id = aLocale.id;
|
|
for (let row in resultRows(stmt)) {
|
|
if (!(row.type in aLocale))
|
|
aLocale[row.type] = [];
|
|
aLocale[row.type].push(row.value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads the locales for an add-on
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to read the locales for
|
|
* @return the array of locales
|
|
*/
|
|
_getLocales: function XPIDB__getLocales(aAddon) {
|
|
let stmt = this.getStatement("_getLocales");
|
|
|
|
let locales = [];
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
for (let row in resultRows(stmt)) {
|
|
let locale = {
|
|
id: row.id,
|
|
locales: [row.locale]
|
|
};
|
|
copyProperties(row, PROP_LOCALE_SINGLE, locale);
|
|
locales.push(locale);
|
|
}
|
|
locales.forEach(function(aLocale) {
|
|
this._readLocaleStrings(aLocale);
|
|
}, this);
|
|
return locales;
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads the default locale for an add-on
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to read the default locale for
|
|
* @return the default locale for the add-on
|
|
* @throws if the database does not contain the default locale information
|
|
*/
|
|
_getDefaultLocale: function XPIDB__getDefaultLocale(aAddon) {
|
|
let stmt = this.getStatement("_getDefaultLocale");
|
|
|
|
stmt.params.id = aAddon._defaultLocale;
|
|
if (!stepStatement(stmt))
|
|
throw new Error("Missing default locale for " + aAddon.id);
|
|
let locale = copyProperties(stmt.row, PROP_LOCALE_SINGLE);
|
|
locale.id = aAddon._defaultLocale;
|
|
stmt.reset();
|
|
this._readLocaleStrings(locale);
|
|
return locale;
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads the target application entries for an add-on
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to read the target applications for
|
|
* @return an array of target applications
|
|
*/
|
|
_getTargetApplications: function XPIDB__getTargetApplications(aAddon) {
|
|
let stmt = this.getStatement("_getTargetApplications");
|
|
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
return [copyProperties(row, PROP_TARGETAPP) for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads the target platform entries for an add-on
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to read the target platforms for
|
|
* @return an array of target platforms
|
|
*/
|
|
_getTargetPlatforms: function XPIDB__getTargetPlatforms(aAddon) {
|
|
let stmt = this.getStatement("_getTargetPlatforms");
|
|
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
return [copyProperties(row, ["os", "abi"]) for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Synchronously makes a DBAddonInternal from a storage row or returns one
|
|
* from the cache.
|
|
*
|
|
* @param aRow
|
|
* The storage row to make the DBAddonInternal from
|
|
* @return a DBAddonInternal
|
|
*/
|
|
makeAddonFromRow: function XPIDB_makeAddonFromRow(aRow) {
|
|
if (this.addonCache[aRow.internal_id]) {
|
|
let addon = this.addonCache[aRow.internal_id].get();
|
|
if (addon)
|
|
return addon;
|
|
}
|
|
|
|
let addon = new XPIProvider.DBAddonInternal();
|
|
addon._internal_id = aRow.internal_id;
|
|
addon._installLocation = XPIProvider.installLocationsByName[aRow.location];
|
|
addon._descriptor = aRow.descriptor;
|
|
addon._defaultLocale = aRow.defaultLocale;
|
|
copyProperties(aRow, PROP_METADATA, addon);
|
|
copyProperties(aRow, DB_METADATA, addon);
|
|
DB_BOOL_METADATA.forEach(function(aProp) {
|
|
addon[aProp] = aRow[aProp] != 0;
|
|
});
|
|
try {
|
|
addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
|
|
}
|
|
catch (e) {
|
|
// An exception will be thrown if the add-on appears in the database but
|
|
// not on disk. In general this should only happen during startup as
|
|
// this change is being detected.
|
|
}
|
|
|
|
this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
|
|
return addon;
|
|
},
|
|
|
|
/**
|
|
* Asynchronously fetches additional metadata for a DBAddonInternal.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal
|
|
* @param aCallback
|
|
* The callback to call when the metadata is completely retrieved
|
|
*/
|
|
fetchAddonMetadata: function XPIDB_fetchAddonMetadata(aAddon) {
|
|
function readLocaleStrings(aLocale, aCallback) {
|
|
let stmt = XPIDatabase.getStatement("_readLocaleStrings");
|
|
|
|
stmt.params.id = aLocale.id;
|
|
stmt.executeAsync({
|
|
handleResult: function(aResults) {
|
|
let row = null;
|
|
while ((row = aResults.getNextRow())) {
|
|
let type = row.getResultByName("type");
|
|
if (!(type in aLocale))
|
|
aLocale[type] = [];
|
|
aLocale[type].push(row.getResultByName("value"));
|
|
}
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
aCallback();
|
|
}
|
|
});
|
|
}
|
|
|
|
function readDefaultLocale() {
|
|
delete aAddon.defaultLocale;
|
|
let stmt = XPIDatabase.getStatement("_getDefaultLocale");
|
|
|
|
stmt.params.id = aAddon._defaultLocale;
|
|
stmt.executeAsync({
|
|
handleResult: function(aResults) {
|
|
aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(),
|
|
PROP_LOCALE_SINGLE);
|
|
aAddon.defaultLocale.id = aAddon._defaultLocale;
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
if (aAddon.defaultLocale) {
|
|
readLocaleStrings(aAddon.defaultLocale, readLocales);
|
|
}
|
|
else {
|
|
ERROR("Missing default locale for " + aAddon.id);
|
|
readLocales();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function readLocales() {
|
|
delete aAddon.locales;
|
|
aAddon.locales = [];
|
|
let stmt = XPIDatabase.getStatement("_getLocales");
|
|
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.executeAsync({
|
|
handleResult: function(aResults) {
|
|
let row = null;
|
|
while ((row = aResults.getNextRow())) {
|
|
let locale = {
|
|
id: row.getResultByName("id"),
|
|
locales: [row.getResultByName("locale")]
|
|
};
|
|
copyRowProperties(row, PROP_LOCALE_SINGLE, locale);
|
|
aAddon.locales.push(locale);
|
|
}
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
let pos = 0;
|
|
function readNextLocale() {
|
|
if (pos < aAddon.locales.length)
|
|
readLocaleStrings(aAddon.locales[pos++], readNextLocale);
|
|
else
|
|
readTargetApplications();
|
|
}
|
|
|
|
readNextLocale();
|
|
}
|
|
});
|
|
}
|
|
|
|
function readTargetApplications() {
|
|
delete aAddon.targetApplications;
|
|
aAddon.targetApplications = [];
|
|
let stmt = XPIDatabase.getStatement("_getTargetApplications");
|
|
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.executeAsync({
|
|
handleResult: function(aResults) {
|
|
let row = null;
|
|
while ((row = aResults.getNextRow()))
|
|
aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP));
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
readTargetPlatforms();
|
|
}
|
|
});
|
|
}
|
|
|
|
function readTargetPlatforms() {
|
|
delete aAddon.targetPlatforms;
|
|
aAddon.targetPlatforms = [];
|
|
let stmt = XPIDatabase.getStatement("_getTargetPlatforms");
|
|
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.executeAsync({
|
|
handleResult: function(aResults) {
|
|
let row = null;
|
|
while ((row = aResults.getNextRow()))
|
|
aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"]));
|
|
},
|
|
|
|
handleError: asyncErrorLogger,
|
|
|
|
handleCompletion: function(aReason) {
|
|
let callbacks = aAddon._pendingCallbacks;
|
|
delete aAddon._pendingCallbacks;
|
|
callbacks.forEach(function(aCallback) {
|
|
aCallback(aAddon);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
readDefaultLocale();
|
|
},
|
|
|
|
/**
|
|
* Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one
|
|
* from the cache.
|
|
*
|
|
* @param aRow
|
|
* The mozIStorageRow to make the DBAddonInternal from
|
|
* @return a DBAddonInternal
|
|
*/
|
|
makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) {
|
|
let internal_id = aRow.getResultByName("internal_id");
|
|
if (this.addonCache[internal_id]) {
|
|
let addon = this.addonCache[internal_id].get();
|
|
if (addon) {
|
|
// If metadata is still pending for this instance add our callback to
|
|
// the list to be called when complete, otherwise pass the addon to
|
|
// our callback
|
|
if ("_pendingCallbacks" in addon)
|
|
addon._pendingCallbacks.push(aCallback);
|
|
else
|
|
aCallback(addon);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let addon = new XPIProvider.DBAddonInternal();
|
|
addon._internal_id = internal_id;
|
|
let location = aRow.getResultByName("location");
|
|
addon._installLocation = XPIProvider.installLocationsByName[location];
|
|
addon._descriptor = aRow.getResultByName("descriptor");
|
|
copyRowProperties(aRow, PROP_METADATA, addon);
|
|
addon._defaultLocale = aRow.getResultByName("defaultLocale");
|
|
copyRowProperties(aRow, DB_METADATA, addon);
|
|
DB_BOOL_METADATA.forEach(function(aProp) {
|
|
addon[aProp] = aRow.getResultByName(aProp) != 0;
|
|
});
|
|
try {
|
|
addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
|
|
}
|
|
catch (e) {
|
|
// An exception will be thrown if the add-on appears in the database but
|
|
// not on disk. In general this should only happen during startup as
|
|
// this change is being detected.
|
|
}
|
|
|
|
this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
|
|
addon._pendingCallbacks = [aCallback];
|
|
this.fetchAddonMetadata(addon);
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads all install locations known about by the database. This
|
|
* is often a a subset of the total install locations when not all have
|
|
* installed add-ons, occasionally a superset when an install location no
|
|
* longer exists.
|
|
*
|
|
* @return an array of names of install locations
|
|
*/
|
|
getInstallLocations: function XPIDB_getInstallLocations() {
|
|
if (!this.connection)
|
|
return [];
|
|
|
|
let stmt = this.getStatement("getInstallLocations");
|
|
|
|
return [row.location for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Synchronously reads all the add-ons in a particular install location.
|
|
*
|
|
* @param location
|
|
* The name of the install location
|
|
* @return an array of DBAddonInternals
|
|
*/
|
|
getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) {
|
|
if (!this.connection)
|
|
return [];
|
|
|
|
let stmt = this.getStatement("getAddonsInLocation");
|
|
|
|
stmt.params.location = aLocation;
|
|
return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Asynchronously gets an add-on with a particular ID in a particular
|
|
* install location.
|
|
*
|
|
* @param aId
|
|
* The ID of the add-on to retrieve
|
|
* @param aLocation
|
|
* The name of the install location
|
|
* @param aCallback
|
|
* A callback to pass the DBAddonInternal to
|
|
*/
|
|
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
|
|
if (!this.connection) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
|
|
let stmt = this.getStatement("getAddonInLocation");
|
|
|
|
stmt.params.id = aId;
|
|
stmt.params.location = aLocation;
|
|
stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
|
|
if (aAddons.length == 0) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
// This should never happen but indicates invalid data in the database if
|
|
// it does
|
|
if (aAddons.length > 1)
|
|
ERROR("Multiple addons with ID " + aId + " found in location " + aLocation);
|
|
aCallback(aAddons[0]);
|
|
}));
|
|
},
|
|
|
|
/**
|
|
* Asynchronously gets the add-on with an ID that is visible.
|
|
*
|
|
* @param aId
|
|
* The ID of the add-on to retrieve
|
|
* @param aCallback
|
|
* A callback to pass the DBAddonInternal to
|
|
*/
|
|
getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
|
|
if (!this.connection) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
|
|
let stmt = this.getStatement("getVisibleAddonForID");
|
|
|
|
stmt.params.id = aId;
|
|
stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
|
|
if (aAddons.length == 0) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
// This should never happen but indicates invalid data in the database if
|
|
// it does
|
|
if (aAddons.length > 1)
|
|
ERROR("Multiple visible addons with ID " + aId + " found");
|
|
aCallback(aAddons[0]);
|
|
}));
|
|
},
|
|
|
|
/**
|
|
* Asynchronously gets the visible add-ons, optionally restricting by type.
|
|
*
|
|
* @param aTypes
|
|
* An array of types to include or null to include all types
|
|
* @param aCallback
|
|
* A callback to pass the array of DBAddonInternals to
|
|
*/
|
|
getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
|
|
if (!this.connection) {
|
|
aCallback([]);
|
|
return;
|
|
}
|
|
|
|
let stmt = null;
|
|
if (!aTypes || aTypes.length == 0) {
|
|
stmt = this.getStatement("getVisibleAddons");
|
|
}
|
|
else {
|
|
let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " +
|
|
"type IN (";
|
|
for (let i = 1; i <= aTypes.length; i++) {
|
|
sql += "?" + i;
|
|
if (i < aTypes.length)
|
|
sql += ",";
|
|
}
|
|
sql += ")";
|
|
|
|
// Note that binding to index 0 sets the value for the ?1 parameter
|
|
stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql);
|
|
for (let i = 0; i < aTypes.length; i++)
|
|
stmt.bindByIndex(i, aTypes[i]);
|
|
}
|
|
|
|
stmt.executeAsync(new AsyncAddonListCallback(aCallback));
|
|
},
|
|
|
|
/**
|
|
* Synchronously gets all add-ons of a particular type.
|
|
*
|
|
* @param aType
|
|
* The type of add-on to retrieve
|
|
* @return an array of DBAddonInternals
|
|
*/
|
|
getAddonsByType: function XPIDB_getAddonsByType(aType) {
|
|
if (!this.connection)
|
|
return [];
|
|
|
|
let stmt = this.getStatement("getAddonsByType");
|
|
|
|
stmt.params.type = aType;
|
|
return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Synchronously gets an add-on with a particular internalName.
|
|
*
|
|
* @param aInternalName
|
|
* The internalName of the add-on to retrieve
|
|
* @return a DBAddonInternal
|
|
*/
|
|
getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
|
|
if (!this.connection)
|
|
return null;
|
|
|
|
let stmt = this.getStatement("getVisibleAddonForInternalName");
|
|
|
|
let addon = null;
|
|
stmt.params.internalName = aInternalName;
|
|
|
|
if (stepStatement(stmt))
|
|
addon = this.makeAddonFromRow(stmt.row);
|
|
|
|
stmt.reset();
|
|
return addon;
|
|
},
|
|
|
|
/**
|
|
* Asynchronously gets all add-ons with pending operations.
|
|
*
|
|
* @param aTypes
|
|
* The types of add-ons to retrieve or null to get all types
|
|
* @param aCallback
|
|
* A callback to pass the array of DBAddonInternal to
|
|
*/
|
|
getVisibleAddonsWithPendingOperations:
|
|
function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
|
|
if (!this.connection) {
|
|
aCallback([]);
|
|
return;
|
|
}
|
|
|
|
let stmt = null;
|
|
if (!aTypes || aTypes.length == 0) {
|
|
stmt = this.getStatement("getVisibleAddonsWithPendingOperations");
|
|
}
|
|
else {
|
|
let sql = "SELECT * FROM addon WHERE visible=1 AND " +
|
|
"(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " +
|
|
"AND type IN (";
|
|
for (let i = 1; i <= aTypes.length; i++) {
|
|
sql += "?" + i;
|
|
if (i < aTypes.length)
|
|
sql += ",";
|
|
}
|
|
sql += ")";
|
|
|
|
// Note that binding to index 0 sets the value for the ?1 parameter
|
|
stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" +
|
|
aTypes.length, sql);
|
|
for (let i = 0; i < aTypes.length; i++)
|
|
stmt.bindByIndex(i, aTypes[i]);
|
|
}
|
|
|
|
stmt.executeAsync(new AsyncAddonListCallback(aCallback));
|
|
},
|
|
|
|
/**
|
|
* Asynchronously get an add-on by its Sync GUID.
|
|
*
|
|
* @param aGUID
|
|
* Sync GUID of add-on to fetch
|
|
* @param aCallback
|
|
* A callback to pass the DBAddonInternal record to. Receives null
|
|
* if no add-on with that GUID is found.
|
|
*
|
|
*/
|
|
getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
|
|
let stmt = this.getStatement("getAddonBySyncGUID");
|
|
stmt.params.syncGUID = aGUID;
|
|
|
|
stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
|
|
if (aAddons.length == 0) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
aCallback(aAddons[0]);
|
|
}));
|
|
},
|
|
|
|
/**
|
|
* Synchronously gets all add-ons in the database.
|
|
*
|
|
* @return an array of DBAddonInternals
|
|
*/
|
|
getAddons: function XPIDB_getAddons() {
|
|
if (!this.connection)
|
|
return [];
|
|
|
|
let stmt = this.getStatement("getAddons");
|
|
|
|
return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
|
|
},
|
|
|
|
/**
|
|
* Synchronously adds an AddonInternal's metadata to the database.
|
|
*
|
|
* @param aAddon
|
|
* AddonInternal to add
|
|
* @param aDescriptor
|
|
* The file descriptor of the add-on
|
|
*/
|
|
addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
|
|
// If there is no DB yet then forcibly create one
|
|
if (!this.connection)
|
|
this.openConnection(false, true);
|
|
|
|
this.beginTransaction();
|
|
|
|
var self = this;
|
|
function insertLocale(aLocale) {
|
|
let localestmt = self.getStatement("addAddonMetadata_locale");
|
|
let stringstmt = self.getStatement("addAddonMetadata_strings");
|
|
|
|
copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params);
|
|
executeStatement(localestmt);
|
|
let row = XPIDatabase.connection.lastInsertRowID;
|
|
|
|
PROP_LOCALE_MULTI.forEach(function(aProp) {
|
|
aLocale[aProp].forEach(function(aStr) {
|
|
stringstmt.params.locale = row;
|
|
stringstmt.params.type = aProp;
|
|
stringstmt.params.value = aStr;
|
|
executeStatement(stringstmt);
|
|
});
|
|
});
|
|
return row;
|
|
}
|
|
|
|
// Any errors in here should rollback the transaction
|
|
try {
|
|
|
|
if (aAddon.visible) {
|
|
let stmt = this.getStatement("clearVisibleAddons");
|
|
stmt.params.id = aAddon.id;
|
|
executeStatement(stmt);
|
|
}
|
|
|
|
let stmt = this.getStatement("addAddonMetadata_addon");
|
|
|
|
stmt.params.locale = insertLocale(aAddon.defaultLocale);
|
|
stmt.params.location = aAddon._installLocation.name;
|
|
stmt.params.descriptor = aDescriptor;
|
|
copyProperties(aAddon, PROP_METADATA, stmt.params);
|
|
copyProperties(aAddon, DB_METADATA, stmt.params);
|
|
DB_BOOL_METADATA.forEach(function(aProp) {
|
|
stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
|
|
});
|
|
executeStatement(stmt);
|
|
let internal_id = this.connection.lastInsertRowID;
|
|
|
|
stmt = this.getStatement("addAddonMetadata_addon_locale");
|
|
aAddon.locales.forEach(function(aLocale) {
|
|
let id = insertLocale(aLocale);
|
|
aLocale.locales.forEach(function(aName) {
|
|
stmt.params.internal_id = internal_id;
|
|
stmt.params.name = aName;
|
|
stmt.params.locale = id;
|
|
executeStatement(stmt);
|
|
});
|
|
});
|
|
|
|
stmt = this.getStatement("addAddonMetadata_targetApplication");
|
|
|
|
aAddon.targetApplications.forEach(function(aApp) {
|
|
stmt.params.internal_id = internal_id;
|
|
stmt.params.id = aApp.id;
|
|
stmt.params.minVersion = aApp.minVersion;
|
|
stmt.params.maxVersion = aApp.maxVersion;
|
|
executeStatement(stmt);
|
|
});
|
|
|
|
stmt = this.getStatement("addAddonMetadata_targetPlatform");
|
|
|
|
aAddon.targetPlatforms.forEach(function(aPlatform) {
|
|
stmt.params.internal_id = internal_id;
|
|
stmt.params.os = aPlatform.os;
|
|
stmt.params.abi = aPlatform.abi;
|
|
executeStatement(stmt);
|
|
});
|
|
|
|
this.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
this.rollbackTransaction();
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously updates an add-ons metadata in the database. Currently just
|
|
* removes and recreates.
|
|
*
|
|
* @param aOldAddon
|
|
* The DBAddonInternal to be replaced
|
|
* @param aNewAddon
|
|
* The new AddonInternal to add
|
|
* @param aDescriptor
|
|
* The file descriptor of the add-on
|
|
*/
|
|
updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
|
|
aDescriptor) {
|
|
this.beginTransaction();
|
|
|
|
// Any errors in here should rollback the transaction
|
|
try {
|
|
this.removeAddonMetadata(aOldAddon);
|
|
aNewAddon.syncGUID = aOldAddon.syncGUID;
|
|
aNewAddon.installDate = aOldAddon.installDate;
|
|
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
|
|
aNewAddon.foreignInstall = aOldAddon.foreignInstall;
|
|
aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
|
|
!aNewAddon.appDisabled && !aNewAddon.pendingUninstall)
|
|
|
|
this.addAddonMetadata(aNewAddon, aDescriptor);
|
|
this.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
this.rollbackTransaction();
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously updates the target application entries for an add-on.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal being updated
|
|
* @param aTargets
|
|
* The array of target applications to update
|
|
*/
|
|
updateTargetApplications: function XPIDB_updateTargetApplications(aAddon,
|
|
aTargets) {
|
|
this.beginTransaction();
|
|
|
|
// Any errors in here should rollback the transaction
|
|
try {
|
|
let stmt = this.getStatement("updateTargetApplications");
|
|
aTargets.forEach(function(aTarget) {
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.params.id = aTarget.id;
|
|
stmt.params.minVersion = aTarget.minVersion;
|
|
stmt.params.maxVersion = aTarget.maxVersion;
|
|
executeStatement(stmt);
|
|
});
|
|
this.commitTransaction();
|
|
}
|
|
catch (e) {
|
|
this.rollbackTransaction();
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously removes an add-on from the database.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal being removed
|
|
*/
|
|
removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
|
|
let stmt = this.getStatement("removeAddonMetadata");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
executeStatement(stmt);
|
|
},
|
|
|
|
/**
|
|
* Synchronously marks a DBAddonInternal as visible marking all other
|
|
* instances with the same ID as not visible.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to make visible
|
|
* @param callback
|
|
* A callback to pass the DBAddonInternal to
|
|
*/
|
|
makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
|
|
let stmt = this.getStatement("clearVisibleAddons");
|
|
stmt.params.id = aAddon.id;
|
|
executeStatement(stmt);
|
|
|
|
stmt = this.getStatement("makeAddonVisible");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
executeStatement(stmt);
|
|
|
|
aAddon.visible = true;
|
|
},
|
|
|
|
/**
|
|
* Synchronously sets properties for an add-on.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal being updated
|
|
* @param aProperties
|
|
* A dictionary of properties to set
|
|
*/
|
|
setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
|
|
function convertBoolean(value) {
|
|
return value ? 1 : 0;
|
|
}
|
|
|
|
let stmt = this.getStatement("setAddonProperties");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
|
|
["userDisabled", "appDisabled", "softDisabled",
|
|
"pendingUninstall"].forEach(function(aProp) {
|
|
if (aProp in aProperties) {
|
|
stmt.params[aProp] = convertBoolean(aProperties[aProp]);
|
|
aAddon[aProp] = aProperties[aProp];
|
|
}
|
|
else {
|
|
stmt.params[aProp] = convertBoolean(aAddon[aProp]);
|
|
}
|
|
});
|
|
|
|
if ("applyBackgroundUpdates" in aProperties) {
|
|
stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
|
|
aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
|
|
}
|
|
else {
|
|
stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
|
|
}
|
|
|
|
executeStatement(stmt);
|
|
},
|
|
|
|
/**
|
|
* Synchronously sets the Sync GUID for an add-on.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal being updated
|
|
* @param aGUID
|
|
* GUID string to set the value to
|
|
*/
|
|
setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) {
|
|
let stmt = this.getStatement("setAddonSyncGUID");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.params.syncGUID = aGUID;
|
|
|
|
executeStatement(stmt);
|
|
},
|
|
|
|
/**
|
|
* Synchronously sets the file descriptor for an add-on.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal being updated
|
|
* @param aProperties
|
|
* A dictionary of properties to set
|
|
*/
|
|
setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
|
|
let stmt = this.getStatement("setAddonDescriptor");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.params.descriptor = aDescriptor;
|
|
|
|
executeStatement(stmt);
|
|
},
|
|
|
|
/**
|
|
* Synchronously updates an add-on's active flag in the database.
|
|
*
|
|
* @param aAddon
|
|
* The DBAddonInternal to update
|
|
*/
|
|
updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
|
|
LOG("Updating add-on state");
|
|
|
|
let stmt = this.getStatement("updateAddonActive");
|
|
stmt.params.internal_id = aAddon._internal_id;
|
|
stmt.params.active = aAddon.active ? 1 : 0;
|
|
executeStatement(stmt);
|
|
},
|
|
|
|
/**
|
|
* Synchronously calculates and updates all the active flags in the database.
|
|
*/
|
|
updateActiveAddons: function XPIDB_updateActiveAddons() {
|
|
LOG("Updating add-on states");
|
|
let stmt = this.getStatement("setActiveAddons");
|
|
executeStatement(stmt);
|
|
|
|
// Note that this does not update the active property on cached
|
|
// DBAddonInternal instances so we throw away the cache. This should only
|
|
// happen during shutdown when everything is going away anyway or during
|
|
// startup when the only references are internal.
|
|
this.addonCache = [];
|
|
},
|
|
|
|
/**
|
|
* Writes out the XPI add-ons list for the platform to read.
|
|
*/
|
|
writeAddonsList: function XPIDB_writeAddonsList() {
|
|
Services.appinfo.invalidateCachesOnRestart();
|
|
|
|
let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
|
|
true);
|
|
if (!this.connection) {
|
|
try {
|
|
addonsList.remove(false);
|
|
LOG("Deleted add-ons list");
|
|
}
|
|
catch (e) {
|
|
}
|
|
|
|
Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
|
|
return;
|
|
}
|
|
|
|
let enabledAddons = [];
|
|
let text = "[ExtensionDirs]\r\n";
|
|
let count = 0;
|
|
let fullCount = 0;
|
|
|
|
let stmt = this.getStatement("getActiveAddons");
|
|
|
|
for (let row in resultRows(stmt)) {
|
|
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
|
|
enabledAddons.push(encodeURIComponent(row.id) + ":" +
|
|
encodeURIComponent(row.version));
|
|
}
|
|
fullCount += count;
|
|
|
|
// The selected skin may come from an inactive theme (the default theme
|
|
// when a lightweight theme is applied for example)
|
|
text += "\r\n[ThemeDirs]\r\n";
|
|
|
|
let dssEnabled = false;
|
|
try {
|
|
dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED);
|
|
} catch (e) {}
|
|
|
|
if (dssEnabled) {
|
|
stmt = this.getStatement("getThemes");
|
|
}
|
|
else {
|
|
stmt = this.getStatement("getActiveTheme");
|
|
stmt.params.internalName = XPIProvider.selectedSkin;
|
|
}
|
|
|
|
if (stmt) {
|
|
count = 0;
|
|
for (let row in resultRows(stmt)) {
|
|
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
|
|
enabledAddons.push(encodeURIComponent(row.id) + ":" +
|
|
encodeURIComponent(row.version));
|
|
}
|
|
fullCount += count;
|
|
}
|
|
|
|
if (fullCount > 0) {
|
|
LOG("Writing add-ons list");
|
|
|
|
let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
|
|
true);
|
|
var fos = FileUtils.openFileOutputStream(addonsListTmp);
|
|
fos.write(text, text.length);
|
|
fos.close();
|
|
addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
|
|
|
|
Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
|
|
}
|
|
else {
|
|
if (addonsList.exists()) {
|
|
LOG("Deleting add-ons list");
|
|
addonsList.remove(false);
|
|
}
|
|
|
|
Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
|
|
}
|
|
}
|
|
};
|