Extension Manager:Sqlite Storage: Difference between revisions

No edit summary
 
(15 intermediate revisions by the same user not shown)
Line 21: Line 21:
* Downloads (Used to display download progress in the UI)
* Downloads (Used to display download progress in the UI)


The UI uses a template to display items, however it does not use the real extensions.rdf datasource. Instead a dynamic datasource is provided that calculates values for certain attributes:
The UI uses a template to display items, however it does not use the real <code>extensions.rdf</code> datasource. Instead a dynamic datasource is provided that calculates values for certain attributes:


* Add-on metadata
* Add-on metadata
Line 47: Line 47:


Much of the UI is dependant on the dynamically generated properties listed above to display the text in the correct locales and show state correctly.
Much of the UI is dependant on the dynamically generated properties listed above to display the text in the correct locales and show state correctly.
=Potential Improvements=
While it may not be necessary to actually do these during this there are a few issues that can be thought about while changing to sqlite:
* XPInstall is now just a downloader for the extension manager. There isn't much point in doing this in a separate component and doing so introduces complexity and communication issues. We can just handle the downloading and certificate check directly from the EM. This will also allow us to check the cert for updates {{Bug|248218}}
* Multiple target application ranges {{Bug|301236}}
* Separate UI from backend {{Bug|407329}}. Moving to pure notifications to the UI achieves a lot of this.
* Dynamic theme switching {{Bug|226791}}
* Install add-ons without restart {{Bug|256509}}
* Handle updates that change guid {{Bug|404968}}


=Proposed Changes=
=Proposed Changes=


Rather than being customised for displaying appropriately in the UI the database should instead be properly normalised with whatever information we need now and may need in the future. It should not include transient data that is only needed before the app restarts to keep the UI correct.
If we were to go the route of using sqlite templates to build the UI then it would be necessary to fill the database with all of the generated properties that are currently available on the EM rdf datasource as well as keep them up to date when things like the locale and preferences are changed in the application.
 
I don't believe that this is the sensible route to take. However instead this essentially requires that a new API be provided such that the UI can get the lists of installed add-ons and that contains methods to get those generated properties when necessary.


This means that information about add-ons waiting to be installed and downloaded will not be in the database. They instead should be held in in-memory data structures. It follows that the UI then will not be entirely generated by templates. Instead the Get Add-ons, Plugins, Updates and Installation panes will be programmatically handled. For the first two that should be relatively simple. The Updates and Installation panes will have to depend on notifications from the extension manager, much of which are already available on the install listener interface.
==Database Schema==


It is also likely not going to be possible to use templating for the extension lists. Firstly the sqlite templates do not respond to changes in the database, something which we could handle in a custom way for the add-ons manager but is unlikely to be implemented as standard. Secondly the large number of computed properties that the UI uses to display things correctly makes using a regular database query difficult. Many could potentially be handled with custom sqlite functions or custom code in the UI itself but I believe the best course of action is instead to effectively hide the database from the UI and use a proper API to get data on the add-ons, either extending or replacing nsIUpdateItem.
The database then should only contain the fixed information about installed add-ons, essentially all of the data read from the install.rdf, as well as any information needed to persist restarts like available update information.


==Database Schema==
This means that information about add-ons waiting to be installed and downloaded no longer need to be in the database. They can instead be held in in-memory data structures.


The database is made up of 6 tables:
The database will look roughly like this:


* addon (<u>addonid, installLocation</u>, version, ...., userDisabled)
[[Image:Extension_Manager-Sqlite_Storage-Schema.png]]
* targetApplication (<u>addonid, version, application</u>, maxVersion, minVersion)
* dependencies (<u>addonid, installLocation, requirement</u>, minVersion, maxVersion)
* localized (<u>addonid, installLocation, locale</u>, id)
* localizedData (<u>id</u>, name, ...)
* update (<u>addonid</u>, version, updateHash, updateURL)


There are some key points suggested in this design.
There are some key points suggested in this design.


The addon table contains all known add-ons in all install locations. This is different from the current situation where we only cache data for the add-ons in the install location with the highest priority. This makes various future behaviours possible including using the newest version of an add-on regardless of installLocation, using any of the same add-on based on compatibility/blocklisting etc. We can create a view in the database that returns only the currently active add-ons for the common query cases.
The <code>addon</code> table contains all known add-ons in all install locations. This is different from the current situation where we only cache data for the add-ons in the install location with the highest priority. This makes various future behaviours possible including using the newest version of an add-on regardless of install location, using any of the same add-on based on compatibility/blocklisting etc. We can create a view in the database that returns only the currently active add-ons for the common query cases.
 
The <code>target_application</code> data is linked to add-ons by addon id and version rather than being a foreign key into the <code>addon</code> table. Since an add-on might be installed with the same version in different install locations there is no need to duplicate the compatibility information (which should be the same) in the database. Likewise known updates have their target application info here. We could potentially think about caching this information almost permanently such that future installs of a previously installed add-on may not need another compatibility update check.
 
Note the <code>locale_strings</code> table. This is used for the contributors, localizers and other multi-string localized metadata. The type column will be an enumeration of the different types.
 
''Might want to do the same for the requirements, however right now they come only out of the install.rdf and are never updated by remote data.''
 
''Do we actually need to keep all the target application data? We do currently but all we need for compatibility checks is minVersion and maxVersion and the app (toolkit/firefox). I'm generally in favour of keeping as much data but querying compatibility would be much simpler with the min/maxversion in the addon table''
 
==UI==
 
Currently the UI is purely template based which has meant that plugin and add-on search results have ended up being put into in-memory rdf datasources. Switching away from templates will allow us to break this into a more straightforward approach. We just have one <code>richlistbox</code> per pane in a deck, on window startup we create <code>richlistitems</code> for every installed add-on, plugin and search results when available.
 
For the install and add-on lists we will have to have some kind of notifications from the EM backend to the UI to let it know that a change has been made that might require updating the UI.


The targetApplication table is keyed on addonid and version rather than being a foreign key into the addon table. It should keep targetApplication information for all installLocations, since an add-on might be installed with the same version in different installLocations there is no need to duplicate that information in the database. Likewise known updates have their targetApplication info here. We could potentially think about caching this information almost permanently such that future installs of a previously installed add-on may not need another compatibility update check.
For installs the already present <code>nsIAddonInstallListener</code> interface will be used. There will be some cleaning up so it gets passed the same <code>nsIAddonDownload</code> object for every single call.


Might want to do the same for the dependencies, however right now they come only out of the install.rdf and are never updated by remote data.
Properties for installed add-ons don't change all that much, mainly pending operation changes. We already have state changes exposed through observer notifications, we can likely reuse/adapt that method.


==API==
==API==


The following interface is what the UI would use to display add-on information for both installed, pending and downloading add-ons. These are just rough sketches, many blanks.
Interface changes are necessary so that the UI can query for all of the necessary information. The main changes are to <code>nsIExtensionManager</code> and <code>nsIUpdateItem</code> (renamed to <code>nsIAddon</code>).
 
[[Extension Manager:Sqlite Storage:nsIExtensionManager]]
 
[[Extension Manager:Sqlite Storage:nsIAddon]]
 
Installed items would be initialised from the <code>addon</code> table in the database. This gives enough information for internal operations. Accesses to other properties like the localised information will cause additional db queries to find them.


<pre>
For a pending install/downloading add-on the <code>nsIAddon</code> interface is mostly unfilled, things like the version are unavailable until after the download is complete and the install.rdf read. If they are updates then the id and name and some other information can be prefilled from the original item. This would enable us to reject updates that turn out to not be the id and version expected.
interface nsIAddon : nsISupports
{
  readonly attribute AString  id;
  readonly attribute AString  version;
  readonly attribute AString  name;


  //... other metadata ...
''We could consider moving the add-on operations (uninstall, disable, enable etc.) onto the nsIAddon interface.''


  readonly attribute boolean  isDisabled;
This is an overview of the data stores in the extension manager:
  readonly attribute boolean  isBlocklisted;


  //... other programatically generated items ...
[[Image:Extension_Manager-Sqlite_Storage-Storage.png]]
}


interface nsIAddonDownload : nsIAddon
The extension manager has two main stores of information, the database (which holds installed add-ons and available updates) and an install cache which holds all of the add-ons being downloaded or pending restart. It also includes the available updates so that these pending items are managed in the same place.
{
  const unsigned long STATE_DOWNLOADING = 0;
  // Whatever states we need to represent different points of the install
  const unsigned long STATE_PENDING_RESTART = 42;


  readonly attribute int  state
This is not massively different to the current situation except that previously items in the install cache had to be added to the container in the database to allow the UI to function correctly.
  readonly attribute long progress;
  readonly attribute long maxProgress;
}
</pre>


Installed items would be initialised from the database. Requests for things like localized data can be request additional data from the database after the fact.
The methods for retrieving addons on <code>nsIExtensionManager</code> pulls items from one or other of the database and install cache. <code>getPendingInstalls</code>, <code>getAvailableUpdates</code> both retrieve from the install cache. <code>getItemList</code> and <code>getItemForID</code> retrieve from the database.


For a pending install/downloading add-on the nsIAddon interface is mostly unfilled, things like the version are unavailable until after the download is complete and the install.rdf read. If they are updates then the id and name and some other information can be prefilled from the original item. This would enable us to reject updates that turn out to not be the id and version expected.
The install cache is initially empty at every startup. When first queried it adds mirrored copies of all the available updates in the database. The installation methods create new items in the install cache. Update checks create new items in the database and a mirror of the same item in the install cache. Items remain in the install cache until shutdown.


We could consider moving the add-on operations (uninstall, disable, enable etc.) onto the nsIAddon interface.
''Maybe items can be removed if they have been installed and no restart was required''


=Major Tasks=
=Major Tasks=
Line 116: Line 129:
* API Implementation
* API Implementation
* UI Work
* UI Work
* Replace XPInstall based downloader?
* Create suitable test harness

Latest revision as of 10:51, 18 July 2008

Current Situation

Currently the extension manager caches add-on metadata, blocklist and disabled status, downloads, pending installs and known updates. This is an incomplete list of the highlights:

  • Add-on metadata (majority from install.rdf)
    • id
    • version
    • type
    • hidden
    • appManaged
    • targetApplication entries (modified by update checks)
    • default metadata
    • localized metadata
    • dependencies
    • appDisabled (set based on blocklist, compatibility and security)
    • userDisabled (user controlled)
    • available update (added by update checks)
  • Pending installs (essentially a subset of the above items, added so they appear in the UI before restarting)
  • Downloads (Used to display download progress in the UI)

The UI uses a template to display items, however it does not use the real extensions.rdf datasource. Instead a dynamic datasource is provided that calculates values for certain attributes:

  • Add-on metadata
    • iconURL (may come from the real datasource, may be a direct file uri to a theme's icon)
    • previewImage (a direct file uri to the theme's preview image)
    • aboutURL (hidden if the add-on is disabled)
    • installDate (returns the mtime of the extension's directory)
    • compatible (returns whether the extension is compatible with the application)
    • providesUpdatesSecurely (whether the update path is secure)
    • satisfiesDependencies (whether all dependencies are available)
    • blocklisted (asks the blocklist service if the add-on is blocklisted)
    • opType (any pending operation for the add-on)
    • appManaged (can only return true if the extension is installed by the app)
    • hidden (can only return true if the extension is in a restricted install location)
    • locked (can only return true if the extension is in a restricted install location)
    • textual data (retrieved from properties, the localized area or the default area as appropriate)
    • isDisabled (based on appDisabled and userDisabled)
    • updateable (checks preferences and permissions to see if this add-on can be updated)
  • Downloads
    • state (current install state)
    • progress (current download progress)

Every pane of the UI is currently built using an rdf based template. Switching panes swaps in a new template in a bunch of complex code. The Get Add-ons pane and Plugins pane are made available through the use of two in-memory datasources added to the templates composite datasource.

Much of the UI is dependant on the dynamically generated properties listed above to display the text in the correct locales and show state correctly.

Potential Improvements

While it may not be necessary to actually do these during this there are a few issues that can be thought about while changing to sqlite:

  • XPInstall is now just a downloader for the extension manager. There isn't much point in doing this in a separate component and doing so introduces complexity and communication issues. We can just handle the downloading and certificate check directly from the EM. This will also allow us to check the cert for updates bug 248218
  • Multiple target application ranges bug 301236
  • Separate UI from backend bug 407329. Moving to pure notifications to the UI achieves a lot of this.
  • Dynamic theme switching bug 226791
  • Install add-ons without restart bug 256509
  • Handle updates that change guid bug 404968

Proposed Changes

If we were to go the route of using sqlite templates to build the UI then it would be necessary to fill the database with all of the generated properties that are currently available on the EM rdf datasource as well as keep them up to date when things like the locale and preferences are changed in the application.

I don't believe that this is the sensible route to take. However instead this essentially requires that a new API be provided such that the UI can get the lists of installed add-ons and that contains methods to get those generated properties when necessary.

Database Schema

The database then should only contain the fixed information about installed add-ons, essentially all of the data read from the install.rdf, as well as any information needed to persist restarts like available update information.

This means that information about add-ons waiting to be installed and downloaded no longer need to be in the database. They can instead be held in in-memory data structures.

The database will look roughly like this:

 

There are some key points suggested in this design.

The addon table contains all known add-ons in all install locations. This is different from the current situation where we only cache data for the add-ons in the install location with the highest priority. This makes various future behaviours possible including using the newest version of an add-on regardless of install location, using any of the same add-on based on compatibility/blocklisting etc. We can create a view in the database that returns only the currently active add-ons for the common query cases.

The target_application data is linked to add-ons by addon id and version rather than being a foreign key into the addon table. Since an add-on might be installed with the same version in different install locations there is no need to duplicate the compatibility information (which should be the same) in the database. Likewise known updates have their target application info here. We could potentially think about caching this information almost permanently such that future installs of a previously installed add-on may not need another compatibility update check.

Note the locale_strings table. This is used for the contributors, localizers and other multi-string localized metadata. The type column will be an enumeration of the different types.

Might want to do the same for the requirements, however right now they come only out of the install.rdf and are never updated by remote data.

Do we actually need to keep all the target application data? We do currently but all we need for compatibility checks is minVersion and maxVersion and the app (toolkit/firefox). I'm generally in favour of keeping as much data but querying compatibility would be much simpler with the min/maxversion in the addon table

UI

Currently the UI is purely template based which has meant that plugin and add-on search results have ended up being put into in-memory rdf datasources. Switching away from templates will allow us to break this into a more straightforward approach. We just have one richlistbox per pane in a deck, on window startup we create richlistitems for every installed add-on, plugin and search results when available.

For the install and add-on lists we will have to have some kind of notifications from the EM backend to the UI to let it know that a change has been made that might require updating the UI.

For installs the already present nsIAddonInstallListener interface will be used. There will be some cleaning up so it gets passed the same nsIAddonDownload object for every single call.

Properties for installed add-ons don't change all that much, mainly pending operation changes. We already have state changes exposed through observer notifications, we can likely reuse/adapt that method.

API

Interface changes are necessary so that the UI can query for all of the necessary information. The main changes are to nsIExtensionManager and nsIUpdateItem (renamed to nsIAddon).

Extension Manager:Sqlite Storage:nsIExtensionManager

Extension Manager:Sqlite Storage:nsIAddon

Installed items would be initialised from the addon table in the database. This gives enough information for internal operations. Accesses to other properties like the localised information will cause additional db queries to find them.

For a pending install/downloading add-on the nsIAddon interface is mostly unfilled, things like the version are unavailable until after the download is complete and the install.rdf read. If they are updates then the id and name and some other information can be prefilled from the original item. This would enable us to reject updates that turn out to not be the id and version expected.

We could consider moving the add-on operations (uninstall, disable, enable etc.) onto the nsIAddon interface.

This is an overview of the data stores in the extension manager:

 

The extension manager has two main stores of information, the database (which holds installed add-ons and available updates) and an install cache which holds all of the add-ons being downloaded or pending restart. It also includes the available updates so that these pending items are managed in the same place.

This is not massively different to the current situation except that previously items in the install cache had to be added to the container in the database to allow the UI to function correctly.

The methods for retrieving addons on nsIExtensionManager pulls items from one or other of the database and install cache. getPendingInstalls, getAvailableUpdates both retrieve from the install cache. getItemList and getItemForID retrieve from the database.

The install cache is initially empty at every startup. When first queried it adds mirrored copies of all the available updates in the database. The installation methods create new items in the install cache. Update checks create new items in the database and a mirror of the same item in the install cache. Items remain in the install cache until shutdown.

Maybe items can be removed if they have been installed and no restart was required

Major Tasks

  • API Implementation
  • UI Work
  • Create suitable test harness