/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Extension Manager.
 *
 * The Initial Developer of the Original Code is Ben Goodger.
 * Portions created by the Initial Developer are Copyright (C) 2004
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Ben Goodger <ben@bengoodger.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var gExtensionManager = null;

const nsIExtensionManager       = Components.interfaces.nsIExtensionManager;
const nsIUpdateService          = Components.interfaces.nsIUpdateService;

const PREF_EM_APP_ID            = "app.id";
const PREF_EM_APP_VERSION       = "app.version";
const PREF_EM_LAST_APP_VERSION  = "extensions.lastAppVersion";
const PREF_UPDATE_COUNT         = "update.extensions.count";

function VersionChecker(aExtensionResource, aAppID, aAppVersion, aDataSource)
{
  this._extensionResource = aExtensionResource;
  this._appID = aAppID;
  this._appVersion = aAppVersion;
  this._ds = aDataSource;
}

VersionChecker.prototype = { 
  get isCompatible ()
  {
    var targets = this._ds.GetTargets(this._extensionResource, this._ds._emR("targetApplication"), true);
    while (targets.hasMoreElements()) {
      var targetAppString = targets.getNext().QueryInterface(Components.interfaces.nsIRDFLiteral);

      var versionParts = targetAppString.Value.split(",");
      if (versionParts[0] == this._appID) {
        var minRequiredVersionStr = versionParts[1];
        var maxRequiredVersionStr = versionParts[2];
        var power = this._getLargestPower([this._appVersion, 
                                          minRequiredVersionStr, 
                                          maxRequiredVersionStr]);
        var minRequiredVersion = this._parseVersion(minRequiredVersionStr, power);
        var maxRequiredVersion = this._parseVersion(maxRequiredVersionStr, power);
        var appVersion = this._parseVersion(this._appVersion, power);
        
        return (appVersion >= minRequiredVersion && 
                appVersion <= maxRequiredVersion);
      }
    }
    return false;
  },

  // Convert a version string into an integer value
  _parseVersion: function (aVersion, aPower)
  {
    var parts = aVersion.split(".");
    var version = 0;
    if (aPower == 0)
      aPower = parts.length;
    
    for (var i = 0; i < parts.length; ++i) {
      var token = parts[i];
      if (token.charAt(token.length-1) == "+") {
        token = token.substr(0, token.lastIndexOf("+"));
        version += 1;
        if (token.length == 0)
          continue;
      }
      
      version += parseInt(token) * Math.pow(10, aPower - i);
    }
    return version;
  },
  
  _parsePower: function (aVersion)
  {
    return aVersion.split(".").length;
  },

  _getLargestPower: function (aVersionArray)
  {
    var biggestPower = 0;
    for (var i = 0; i < aVersionArray.length; ++i) {
      var power = this._parsePower(aVersionArray[i]);
      if (power > biggestPower) 
        biggestPower = power;
    }
    return biggestPower;
  }
}

function nsExtensionManager()
{
  var os = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  os.addObserver(this, "profile-after-change", false);
  os.addObserver(this, "profile-do-change", false);
}

nsExtensionManager.prototype = {
  /////////////////////////////////////////////////////////////////////////////
  // nsIObserver
  observe: function (aSubject, aTopic, aData)
  {
    switch (aTopic) {
    case "profile-after-change":
      var os = Components.classes["@mozilla.org/observer-service;1"]
                         .getService(Components.interfaces.nsIObserverService);
      os.removeObserver(this, "profile-after-change");
      
      // XXXben this can go away once appstartup/runner code calls us. 
      this.checkForMismatches();
      break;
    case "profile-do-change":
      var os = Components.classes["@mozilla.org/observer-service;1"]
                         .getService(Components.interfaces.nsIObserverService);
      os.removeObserver(this, "profile-do-change");
    
      var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                  .getService(Components.interfaces.nsIProperties);
      var autoregFile = fileLocator.get("ProfD", Components.interfaces.nsIFile);
      autoregFile.append(".autoreg");
      if (autoregFile.exists()) {
        // An existing autoreg file is an indication that something major has 
        // happened to the extensions datasource (install/uninstall/enable/disable)
        // and as such we must load it now and see what needs to happen.
        this._ensureDS();
        
        // Look for extensions that need to be enabled
        
        // Look for extensions that need to be disabled
        
        // Look for extensions that need to be removed
        
        // Now that we've finalized the EM operation, remove the autoreg file so
        // we don't do this every time we start.
        autoregFile.remove(false);
      }
      break;
    }
  },
  
  checkForMismatches: function () 
  {
    var rv = false;
    
    // Check to see if the version of the application that is being started
    // now is the same one that was started last time. 
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
    var currAppVersion = pref.getCharPref(PREF_EM_APP_VERSION);
    try {
      var lastAppVersion = pref.getCharPref(PREF_EM_LAST_APP_VERSION);
    }
    catch (e) {}
    if (currAppVersion != lastAppVersion) {
      // Version mismatch, we're have to load the extensions datasource
      // and do version checking. Time hit here doesn't matter since this 
      // doesn't happen all that often.
      this._ensureDS();
      var currAppID = pref.getCharPref(PREF_EM_APP_ID);
      var items = this._ds.getIncompatibleItemList(currAppID, currAppVersion);

      if (items.length > 0) {
        for (var i = 0; i < items.length; ++i) {
          // Now disable the extension so it won't hurt anything. 
          this.disableExtension(items[i].id);
        }
        var updates = Components.classes["@mozilla.org/updates/update-service;1"]
                                .getService(Components.interfaces.nsIUpdateService);
        updates.checkForUpdates(items, items.length, Components.interfaces.nsIUpdateItem.TYPE_ADDON, 
                                nsIUpdateService.SOURCE_EVENT_MISMATCH,
                                null);
        
        rv = true;
      }
    }
    
    // Now update the last app version so we don't do this checking 
    // again.
    pref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
    
    return rv;
  },
  
  _writeAutoReg: function ()
  {
    // When an operation is performed that requires a component re-registration
    // (extension enabled/disabled, installed, uninstalled), we must write the
    // set of registry-relative paths of components to register to an .autoreg 
    // file which lives in the profile folder. 
    //
    // To do this we must enumerate all installed extensions and write data 
    // about all valid items to the file. 
    
    this._ensureDS();
    
    var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                .getService(Components.interfaces.nsIProperties);
    var profileDir = fileLocator.get("ProfD", Components.interfaces.nsIFile);
    var globalDir = fileLocator.get("ProfD", Components.interfaces.nsIFile); // XXXben - XCurProcDir
    
    // Open the .autoreg file for writing.
    
    var autoregFile = profileDir.clone();
    autoregFile.append(".autoreg");
    var fos = Components.classes["@mozilla.org/network/file-output-stream;1"]
                        .createInstance(Components.interfaces.nsIFileOutputStream);
    const MODE_WRONLY   = 0x02;
    const MODE_CREATE   = 0x08;
    const MODE_TRUNCATE = 0x20;
    fos.init(autoregFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, 0664, 0);

    var extensions = this.getItemList(null, Components.interfaces.nsIUpdateItem.TYPE_EXTENSION, { });
    for (var i = 0; i < extensions.length; ++i) {
      var extension = extensions[i];
      
      var valid = this._ds.getExtensionProperty(extension.id, "toBeDisabled") != "true";
      valid = this._ds.getExtensionProperty(extension.id, "toBeUninstalled") != "true";
      
      if (valid) continue;
      
      // Now locate this extension within the extensions folder
      // First, where is it installed - profile or global?
      var location = this._ds.getExtensionProperty(extension.id, "installLocation");
      var dir = location == "profile" ? profileDir : globalDir;
      
      // Now synthesize the components dir for the extension
      var componentsDir = dir.clone();
      componentsDir.append("extensions");
      componentsDir.append(extension.id);
      componentsDir.append("components");
      if (componentsDir.exists()) {
        // write the relative path of the components dir
        var line = "reg:extensions/" + extension.id + "/components/\n";
        fos.write(line, line.length);
      }
    }
    fos.close();
  },

  /////////////////////////////////////////////////////////////////////////////  
  // nsIExtensionManager
  installExtensionFromStream: function (aStream, aUseProfile)
  {
    var parser = Components.classes["@mozilla.org/rdf/xml-parser;1"]
                           .createInstance(Components.interfaces.nsIRDFXMLParser);
    var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                       .createInstance(Components.interfaces.nsIRDFDataSource);
    var streamListener = parser.parseAsync(ds, null);
    
    var bytesAvailable;
    do {
      bytesAvailable = aStream.available();
      if (!bytesAvailable)
        break;
      
      streamListener.onDataAvailable(null, null, aStream, 0, bytesAvailable);
    }
    while (1);
    
    this._ensureDS();
    
    this._ds.installExtension(ds, aUseProfile);
    this._writeAutoReg();
  },
      
  uninstallExtension: function (aExtensionID)
  {
    this._ds.uninstallExtension(aExtensionID);
    this._writeAutoReg();
  },
  
  enableExtension: function (aExtensionID)
  {
    this._ds.enableExtension(aExtensionID);
    this._writeAutoReg();
  },
  
  disableExtension: function (aExtensionID)
  {
    this._ds.disableExtension(aExtensionID);
    this._writeAutoReg();
  },
  
  // XXXben - handle the case where the item provides its own update url. 
  update: function (aItems, aItemCount)
  {
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
    var appID = pref.getCharPref(PREF_EM_APP_ID);
    var appVersion = pref.getCharPref(PREF_EM_APP_VERSION);

    if (aItems.length == 0) {
      var anyType = Components.interfaces.nsIUpdateItem.TYPE_ANY;
      aItems = this.getItemList(null, anyType, { });
    }
    var updater = new nsExtensionItemUpdater(aItems, appID, appVersion);
    updater.checkForUpdates();
  },
  
  getItemList: function (aItemID, aType, aCountRef)
  {
    this._ensureDS();
    return this._ds.getItemList(aItemID, aType, aCountRef);
  },    
  
  // Themes
  installTheme: function (aThemeID)
  {
  
  },
  
  uninstallTheme: function (aThemeID)
  {
  
  },

  get datasource()
  {
    this._ensureDS();
    return this._ds;
  },
  
  //
  _ds: null,

  /////////////////////////////////////////////////////////////////////////////    
  // Other
  
  // This should NOT be called until after the window is shown! 
  _ensureDS: function ()
  {
    if (!this._ds) {
      this._ds = new nsExtensionsDataSource();
      if (this._ds) {
        this._ds.loadExtensions(false);
        this._ds.loadExtensions(true);
      }
    }
  },

  /////////////////////////////////////////////////////////////////////////////
  // nsISupports
  QueryInterface: function (aIID) 
  {
    if (!aIID.equals(Components.interfaces.nsIExtensionManager) &&
        !aIID.equals(Components.interfaces.nsIObserver) &&
        !aIID.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

function nsExtensionItemUpdater(aItems, aTargetAppID, aTargetAppVersion) 
{
  this._items = aItems;
  this._count = aItems.length;
  this._appID = aTargetAppID;
  this._appVersion = aTargetAppVersion;

  this._os = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);

  // This is the number of extensions/themes/etc that we found updates for.
  this._updateCount = 0;
}

nsExtensionItemUpdater.prototype = {
  /////////////////////////////////////////////////////////////////////////////
  // nsIExtensionItemUpdater
  checkForUpdates: function () 
  {
    this._os.notifyObservers(null, "Update:Extension:Started", "");
    var wspFactory = Components.classes["@mozilla.org/xmlextras/proxy/webserviceproxyfactory;1"]
                              .getService(Components.interfaces.nsIWebServiceProxyFactory);
    wspFactory.createProxyAsync("http://localhost:8080/axis/services/VersionCheck?wsdl", 
                                "VersionCheck", "", true, this);
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // nsExtensionItemUpdater
  _proxy: null,
  
  _checkForUpdates: function ()
  {
    for (var i = 0; i < this._items.length; ++i) {
      var e = this._items[i];
      this._os.notifyObservers(null, "Update:Extension:Item-Started", e.name + " " + e.version);
      this._proxy.getNewestExtension(eval(e.objectSource), this._appID, this._appVersion);
    }
  },

  /////////////////////////////////////////////////////////////////////////////
  // nsIWSDLLoadListener  
  onLoad: function (aProxy)
  { 
    this._proxy = aProxy;
    this._proxy.setListener(this);
    this._checkForUpdates();
  },
  
  onError: function (aStatus, aMessage)
  {
    this._os.notifyObservers(aResult, "Update:Extension:Item-Error", aMessage);
  },
  
  getNewestExtensionCallback: function (aResult)
  {
    var item = aResult;
    try {
      item.name.toString(); // XXXben This is a lame hack to cause an exception to be
                            // thrown for null values when there is no newer extension
                            // or something else bad happens on the server that we 
                            // don't recognize.
      this._os.notifyObservers(aResult, "Update:Extension:Item-Ended", "goat");
      ++this._updateCount;
    }
    catch (e) {
    }
    
    if (--this._count == 0) {
      var pref = Components.classes["@mozilla.org/preferences-service;1"]
                            .getService(Components.interfaces.nsIPrefBranch);
      pref.setIntPref(PREF_UPDATE_COUNT, this._updateCount); 
    
      this._os.notifyObservers(null, "Update:Extension:Ended", "");
    }
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // nsISupports
  QueryInterface: function (aIID) 
  {
    if (!aIID.equals(Components.interfaces.nsIExtensionItemUpdater) &&
        !aIID.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }  
};


function EM_NS(aProperty)
{
  return "http://www.mozilla.org/2004/em-rdf#" + aProperty;
}

function nsExtensionsDataSource()
{
  this._rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
                        .getService(Components.interfaces.nsIRDFService);
}

nsExtensionsDataSource.prototype = {
  _rdf: null,
  
  _emR: function (aProperty) 
  {
    return this._rdf.GetResource(EM_NS(aProperty));
  },
  
  _emL: function (aLiteral)
  {
    return this._rdf.GetLiteral(aLiteral);
  },

  _stripPrefix: function (aResourceURI)
  {
    return aResourceURI.substr("urn:mozilla:extension:".length, aResourceURI.length);
  },

  getIncompatibleItemList: function (aAppID, aAppVersion)
  {
    var items = [];
    
    var ctr = Components.classes["@mozilla.org/rdf/container;1"]
                        .createInstance(Components.interfaces.nsIRDFContainer);
    ctr.Init(this, this._rdf.GetResource("urn:mozilla:extension:root"));
    
    var elements = ctr.GetElements();
    while (elements.hasMoreElements()) {
      var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
      var checker = new VersionChecker(e, aAppID, aAppVersion, this);
      if (!checker.isCompatible) {
        var id = this._stripPrefix(e.Value);
        var item = Components.classes["@mozilla.org/updates/item;1"]
                             .createInstance(Components.interfaces.nsIUpdateItem);
        item.init(id, this.getExtensionProperty(id, "version"),
                  this.getExtensionProperty(id, "name"),
                  -1, "", "", 
                  Components.interfaces.nsIUpdateItem.TYPE_EXTENSION); // XXXben
        items.push(item);
      }
    }
    return items;
  },
  
  getItemList: function (aItemID, aType, aCountRef)
  {
    var items = [];
    if (aItemID) {
      var item = Components.classes["@mozilla.org/updates/item;1"]
                            .createInstance(Components.interfaces.nsIUpdateItem);
      item.init(aItemID, this.getExtensionProperty(aItemID, "version"),
                this.getExtensionProperty(aItemID, "name"),
                -1, "", "", aType);
      items.push(item);
    }
    else {
      var ctr = Components.classes["@mozilla.org/rdf/container;1"]
                          .createInstance(Components.interfaces.nsIRDFContainer);
      ctr.Init(this, this._rdf.GetResource("urn:mozilla:extension:root"));
      
      var elements = ctr.GetElements();
      while (elements.hasMoreElements()) {
        var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
        var id = this._stripPrefix(e.Value);
        var item = Components.classes["@mozilla.org/updates/item;1"]
                              .createInstance(Components.interfaces.nsIUpdateItem);
        item.init(id, this.getExtensionProperty(id, "version"),
                  this.getExtensionProperty(id, "name"),
                  -1, "", "", aType);
        items.push(item);
      }
    }
    aCountRef.value = items.length;
    return items;
  },
  
  _setProperty: function (aDS, aSource, aProperty, aNewValue)
  {
    var oldValue = aDS.GetTarget(aSource, aProperty, true);
    if (oldValue)
      aDS.Change(aSource, aProperty, oldValue, aNewValue);
    else
      aDS.Assert(aSource, aProperty, aNewValue, true);
  },
  
  getExtensionProperty: function (aExtensionID, aProperty)
  {
    var extension = this._rdf.GetResource("urn:mozilla:extension:" + aExtensionID);
    try {
      return this.GetTarget(extension, this._emR(aProperty), true).QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
    }
    catch (e) {}
    return "";
  },
  
  _setExtensionProperty: function (aExtensionID, aPropertyArc, aPropertyValue)
  {
    var extension = this._rdf.GetResource("urn:mozilla:extension:" + aExtensionID);
    var installLocation = this.GetTarget(extension, this._emR("installLocation"), true);
    var isProfile = installLocation.EqualsNode(this._emL("profile"));
    var ds = isProfile ? this._profileExtensions : this._appExtensions;
    
    this._setProperty(ds, extension, aPropertyArc, aPropertyValue);

    this._flush(isProfile);  
  },
  
  enableExtension: function (aExtensionID)
  {
    this._setExtensionProperty(aExtensionID, this._emR("toBeEnabled"), this._emL("true"));
    this._setExtensionProperty(aExtensionID, this._emR("toBeDisabled"), this._emL("false"));
    this._setExtensionProperty(aExtensionID, this._emR("disabled"), this._emL("false"));
  },
  
  disableExtension: function (aExtensionID)
  {
    this._setExtensionProperty(aExtensionID, this._emR("toBeDisabled"), this._emL("true"));
    this._setExtensionProperty(aExtensionID, this._emR("toBeEnabled"), this._emL("false"));
    this._setExtensionProperty(aExtensionID, this._emR("disabled"), this._emL("true"));
  },
  
  uninstallExtension: function (aExtensionID)
  {
    var ctr = Components.classes["@mozilla.org/rdf/container;1"]
                        .createInstance(Components.interfaces.nsIRDFContainer);
    ctr.Init(this, this._rdf.GetResource("urn:mozilla:extension:root"));
    
    var extension = this._rdf.GetResource("urn:mozilla:extension:" + aExtensionID);
    ctr.RemoveElement(extension, true);
    
    this._setExtensionProperty(aExtensionID, this._emR("toBeUninstalled"), this._emL("true"));
  },
  
  loadExtensions: function (aProfile)
  {
    var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
    var key = aProfile ? "ProfD" : "ProfD"; // XXXben - XCurProcDir
    var extensionsFile = fileLocator.get(key, Components.interfaces.nsIFile);
    extensionsFile.append("extensions");
    
    var extensionsDir = extensionsFile.clone();
    if (!extensionsDir.exists())
      extensionsDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
    
    extensionsFile.append("extensions.rdf");
    
    if (!extensionsFile.exists()) {
      // XXXben - Copy over the default file - temporary. 
      if (key == "ProfD") {
        var defaultExtensionsFile = fileLocator.get("ProfDefNoLoc", Components.interfaces.nsIFile);
        defaultExtensionsFile.append("extensions");
        defaultExtensionsFile.append("extensions.rdf");
        defaultExtensionsFile.copyTo(extensionsDir, "extensions.rdf");
      }
      else
        return;
    }

    var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
                           .getService(Components.interfaces.nsIIOService);
    var fph = ioServ.getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler);
    var dsURL = fph.getURLSpecFromFile(extensionsFile);
    
    var ds = this._rdf.GetDataSourceBlocking(dsURL);
    
    if (aProfile) {
      this._profileExtensions = ds;
      if (!this._composite) 
        this._composite = Components.classes["@mozilla.org/rdf/datasource;1?name=composite-datasource"]
                                    .createInstance(Components.interfaces.nsIRDFDataSource);
      if (this._appExtensions)
        this._composite.RemoveDataSource(this._appExtensions);
      this._composite.AddDataSource(this._profileExtensions);
      if (this._appExtensions)
        this._composite.AddDataSource(this._appExtensions);  
    }
    else {
      this._appExtensions = ds;
      
      if (!this._composite)
        this._composite = Components.classes["@mozilla.org/rdf/datasource;1?name=composite-datasource"]
                                    .createInstance(Components.interfaces.nsIRDFCompositeDataSource);
      this._composite.AddDataSource(this._appExtensions);
    }
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // nsIRDFDataSource

  _appExtensions: null,
  _profileExtensions: null,  
  _composite: null,
  
  get URI()
  {
    return "rdf:extensions";
  },
  
  GetSource: function (aProperty, aTarget, aTruthValue)
  {
    return this._composite.GetSource(aProperty, aTarget, aTruthValue);
  },
  
  GetSources: function (aProperty, aTarget, aTruthValue)
  {
    return this._composite.GetSources(aProperty, aTarget, aTruthValue);
  },
  
  GetTarget: function (aSource, aProperty, aTruthValue)
  {
    if (aProperty.EqualsNode(this._emR("iconURL"))) {
      var hasIconURL = this._composite.hasArcOut(aSource, aProperty);
      // If the download entry doesn't have a IconURL property, use a
      // generic icon URL instead.
      if (!hasIconURL)
        return this._rdf.GetResource("chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png");
    }
    else if (aProperty.EqualsNode(this._emR("installLocation"))) {
      var hasNameArc = this._profileExtensions.hasArcOut(aSource, this._emR("name"));
      var hasVersionArc = this._profileExtensions.hasArcOut(aSource, this._emR("version"));
      return hasNameArc && hasVersionArc ? this._emL("profile") : this._emL("global");
    }
    
    return this._composite.GetTarget(aSource, aProperty, aTruthValue);
  },
  
  GetTargets: function (aSource, aProperty, aTruthValue)
  {
    return this._composite.GetTargets(aSource, aProperty, aTruthValue);
  },
  
  Assert: function (aSource, aProperty, aTarget, aTruthValue)
  {
    return Components.results.NS_RDF_ASSERTION_REJECTED;
  },
  
  Unassert: function (aSource, aProperty, aTarget)
  {
    return Components.results.NS_RDF_ASSERTION_REJECTED;
  },
  
  Change: function (aSource, aProperty, aOldTarget, aNewTarget)
  {
    return Components.results.NS_RDF_ASSERTION_REJECTED;
  },

  Move: function (aSource, aNewSource, aProperty, aTarget)
  {
    return Components.results.NS_RDF_ASSERTION_REJECTED;
  },
  
  HasAssertion: function (aSource, aProperty, aTarget, aTruthValue)
  {
    return this._composite.HasAssertion(aSource, aProperty, aTarget, aTruthValue);
  },
  
  AddObserver: function (aObserver)
  {
    this._composite.AddObserver(aObserver);
  },
  
  RemoveObserver: function (aObserver)
  {
    this._composite.RemoveObserver(aObserver);
  },
  
  ArcLabelsIn: function (aNode)
  {
    return this._composite.ArcLabelsIn(aNode);
  },
  
  ArcLabelsOut: function (aSource)
  {
    return this._composite.ArcLabelsOut(aSource);
  },
  
  GetAllResources: function ()
  {
    return this._composite.GetAllResources();
  },
  
  IsCommandEnabled: function (aSources, aCommand, aArguments)
  {
    return this._composite.IsCommandEnabled(aSources, aCommand, aArguments);
  },
  
  DoCommand: function (aSources, aCommand, aArguments)
  {
    this._composite.DoCommand(aSources, aCommand, aArguments);
  },
  
  GetAllCmds: function (aSource)
  {
    return this._composite.GetAllCmds(aSource);
  },
  
  hasArcIn: function (aNode, aArc)
  {
    return this._composite.hasArcIn(aNode, aArc);
  },
  
  hasArcOut: function (aSource, aArc)
  {
    return this._composite.hasArcOut(aSource, aArc);
  },
  
  beginUpdateBatch: function ()
  {
    return this._composite.beginUpdateBatch();
  },
  
  endUpdateBatch: function ()
  {
    return this._composite.endUpdateBatch();
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // nsIRDFRemoteDataSource
  
  get loaded()
  {
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  },
  
  Init: function (aURI)
  {
  },
  
  Refresh: function (aBlocking)
  {
  },
  
  Flush: function ()
  {
    this._flush(false);
    this._flush(true);
  },
  
  FlushTo: function (aURI)
  {
  },
  
  _flush: function (aIsProfile)
  { 
    var ds = aIsProfile ? this._profileExtensions : this._appExtensions;
    var rds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
    rds.Flush();
  },

  /////////////////////////////////////////////////////////////////////////////
  // nsISupports
  QueryInterface: function (aIID) 
  {
    if (!aIID.equals(Components.interfaces.nsIRDFDataSource) &&
        !aIID.equals(Components.interfaces.nsIRDFRemoteDataSource) && 
        !aIID.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};


var gModule = {
  _firstTime: true,
  
  registerSelf: function (aComponentManager, aFileSpec, aLocation, aType) 
  {
    if (this._firstTime) {
      this._firstTime = false;
      throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
    }
    aComponentManager = aComponentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    
    for (var key in this._objects) {
      var obj = this._objects[key];
      aComponentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
                                                aFileSpec, aLocation, aType);
    }
    
    // Make the Extension Manager a startup observer
    var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
                                    .getService(Components.interfaces.nsICategoryManager);
    categoryManager.addCategoryEntry("app-startup", this._objects.manager.className,
                                     "service," + this._objects.manager.contractID, 
                                     true, true, null);
  },
  
  getClassObject: function (aComponentManager, aCID, aIID) 
  {
    if (!aIID.equals(Components.interfaces.nsIFactory))
      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

    for (var key in this._objects) {
      if (aCID.equals(this._objects[key].CID))
        return this._objects[key].factory;
    }
    
    throw Components.results.NS_ERROR_NO_INTERFACE;
  },
  
  _objects: {
    manager: { CID: Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}"),
               contractID: "@mozilla.org/extensions/manager;1",
               className: "Extension Manager",
               factory: {
                          createInstance: function (aOuter, aIID) 
                          {
                            if (aOuter != null)
                              throw Components.results.NS_ERROR_NO_AGGREGATION;
                            
                            if (!gExtensionManager)
                              gExtensionManager = new nsExtensionManager();
                              
                            return gExtensionManager.QueryInterface(aIID);
                          }
                        }
             }
   },
  
  canUnload: function (aComponentManager) 
  {
    return true;
  }
};

function NSGetModule(compMgr, fileSpec) 
{
  return gModule;
}
