/* 
 Copyright 2005-2010 Xmarks Inc.
 
 service.jsm: implements singleton service functions shared across windows.

 */

var EXPORTED_SYMBOLS = ["fms"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Xmarks;

if (Xmarks === undefined) {
    Xmarks = {};
}

function LoadJavascript(filename, id) {
    if (id == "undefined") {
        Cc["@mozilla.org/moz/jssubscript-loader;1"].
        getService(Ci.mozIJSSubScriptLoader).
        loadSubScript(filename, null);
    }
}

function GetTopWin(wintype) {
    var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
        getService(Ci.nsIWindowMediator).
        getMostRecentWindow(wintype);

    return topwin;
}

function FoxmarksSetCookie() {
    var url = "http://www.xmarks.com";
    var d = new Date();
    d.setTime(d.getTime() + (20 * 365 * 24 * 60 * 60 * 1000));
    var cm = Components.classes["@mozilla.org/cookiemanager;1"].
                    getService(Components.interfaces.nsICookieManager2);

    cm.add(".xmarks.com", "/", "mid", Xmarks.gSettings.machineId, false, false, false,
            d.getTime() / 1000);
}

function FoxmarksLaunchUpgradePage() {
    FoxmarksSetCookie();

    upgradeCallback.timer = Cc["@mozilla.org/timer;1"].
        createInstance(Ci.nsITimer);
    upgradeCallback.timer.initWithCallback(upgradeCallback, 5000,
        Ci.nsITimer.TYPE_ONE_SHOT);
}

function IsOnline() {
    return !(Cc["@mozilla.org/network/io-service;1"].
        getService(Ci.nsIIOService).offline);
}
    

function FoxmarksBuildPostData(num_bookmarks){
    return {
        username: Xmarks.gSettings.username,
        version: Xmarks.FoxmarksVersion(),
        types: {
            bookmarks: {
                is_enabled: Xmarks.gSettings.isSyncEnabled("bookmarks"),
                count: num_bookmarks
            },
            passwords: {
                is_enabled: Xmarks.gSettings.isSyncEnabled("passwords")
            }
        }
    };
}
var upgradeCallback = {
    notify: function(timer) {
        fms.server.countItems("bookmarks",
            "bookmark", function(status, num_bookmarks){
            var currver = Xmarks.FoxmarksVersion();
            var wm = Cc['@mozilla.org/appshell/window-mediator;1'].
                getService(Ci.nsIWindowMediator);
            var topWindow = wm.getMostRecentWindow('navigator:browser');

            if(topWindow &&
                topWindow.document){ 
                var appcontent =
                    topWindow.document.getElementById("appcontent");
                var listener = function(e){
                    var doc = e.originalTarget;
                    var button = 
                        doc.getElementById(
                        "set_up_password_sync_button"
                    );

                    if(button){
                        button.setAttribute("onclick",  null);
                        button.addEventListener(
                            "click",
                            function(){
                                var os = Cc["@mozilla.org/observer-service;1"].
                                    getService(Ci.nsIObserverService);
                                os.notifyObservers(null, 
                                    "foxmarks-showsettingpane", 
                                    "foxmarks-syncpane"
                                );
                            }, 
                            true
                        );
                        appcontent.removeEventListener(
                            "DOMContentLoaded",
                            listener, 
                            false
                        );
                    }
                };
                if(appcontent){
                    appcontent.addEventListener(
                        "DOMContentLoaded",
                        listener, 
                        false
                    );
                }
            }
            // We used to set the protocol as Xmarks.gSettings.httpProtocol,
            // but that causes warnings when that page references non-HTTPS
            // ads, so we peg the protocol as http.
            Xmarks.OpenInNewTab("http://" + Xmarks.gSettings.webHost + 
                "/firefox/upgrade/" + currver, true, 
                FoxmarksBuildPostData(num_bookmarks));
            Xmarks.gSettings.currVersion = currver;
        });
    }
}
function FoxmarksCheckForServerChanges() {
    if(Xmarks.gSettings.useOwnServer){
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
        os.notifyObservers(null, "foxmarks-realstart", "");
    } else {
        var url = Xmarks.gSettings.httpProtocol + Xmarks.gSettings.apiHost +
            "/internal/serverprefs";
        var request = new Request(
            "POST", 
            url,
            {
                mid: Xmarks.gSettings.machineId,
                version: Xmarks.gSettings.serverVersion,
                abgroup: Xmarks.gSettings.abgroup,
                cid: "xmfx"
            },
            {
                isAuthRequest: false,
                headers: null,
                ignoreBody: false,
                ignoreAuthTokens: true
            }
        );
        request.Start(function(response){
            if(response && response.status === 0){

                if(response.version > Xmarks.gSettings.serverVersion){
                    if(response.prefs){ 
                        var prefs = response.prefs;
                        for(var pref in prefs){
                            if(prefs.hasOwnProperty(pref)){
                                Xmarks.gSettings[pref] = prefs[pref];
                            }
                        }
                    }
                    Xmarks.gSettings.serverVersion = response.version;
                }
            }
            var os = Cc["@mozilla.org/observer-service;1"]
                .getService(Ci.nsIObserverService);
            os.notifyObservers(null, "foxmarks-realstart", "");
        });
    }
}

function FoxmarksLaunchSetupWizard() {
    FoxmarksSetCookie();
    FoxmarksWizardCallback.timer = Cc["@mozilla.org/timer;1"].
        createInstance(Ci.nsITimer);
    FoxmarksWizardCallback.timer.initWithCallback(FoxmarksWizardCallback, 5000,
        Ci.nsITimer.TYPE_ONE_SHOT);
}

var FoxmarksWizardCallback = {
    notify: function(timer) {
        var url = "http://" + Xmarks.gSettings.staticHost + Xmarks.gSettings.wizardPrefs;
        var request = new Request(
            "GET", 
            url,
            null,
            {
                isAuthRequest: false,
                headers: null,
                ignoreBody: false,
                ignoreAuthTokens: true
            }
        );
        request.Start(function(response){
            var os = Cc["@mozilla.org/observer-service;1"]
                .getService(Ci.nsIObserverService);
            var JSON = Cc["@mozilla.org/dom/json;1"].
                createInstance(Ci.nsIJSON);
            os.notifyObservers(null, "foxmarks-newpopup", JSON.encode(response));
        });
    }
}

function HandleShutdown(cancel) {
    var retval = { helpurl: null };
    var sb = Xmarks.Bundle().GetStringFromName;
    var dontask = {value: false};
    var rv = 0;

    if (!Xmarks.gSettings.syncOnShutdown || Xmarks.gSettings.suspended) {
        return;
    }

    // Any dirty datasources?
    var dirty = getDatasources("dirty");
    if (!dirty.length) {
        return;
    }

    if (!Xmarks.gSettings.haveSynced) {
        return;
    }

    var topwin = GetTopWin();
    if (!topwin) {
        return;
    }

    var dirtyNames = [];
    for (i = 0; i < dirty.length; ++i)
        dirtyNames.push(dirty[i].syncType);

    dirtyNames = dirtyNames.join(", ");

    if (!Xmarks.gSettings.syncOnShutdownAskTabs)
        dirty = dirty.filter(function(d) d.syncType != 'tabs');

    if (!Xmarks.gSettings.syncOnShutdownAskHistory)
        dirty = dirty.filter(function(d) d.syncType != 'history');

    if (Xmarks.gSettings.syncOnShutdownAsk && dirty.length) {
        rv = Cc["@mozilla.org/embedcomp/prompt-service;1"].
        getService(Ci.nsIPromptService).
        confirmEx(topwin, sb("appname.long"), 
            sb("msg.unsynced").replace("$0", dirtyNames),
            Ci.nsIPromptService.STD_YES_NO_BUTTONS, null, null, null,
            sb("msg.dontask"), dontask);
        // Reverse sense: confirmEx returns 0 - yes, 1 - no
        rv = !rv;

        // If user says "don't ask me again", set syncOnShutdown to whatever
        // they have chosen in this instance.
        if (dontask.value) {
            Xmarks.gSettings.syncOnShutdown = rv;
        }
        Xmarks.gSettings.syncOnShutdownAsk = !dontask.value;

    } else {                           // don't ask
        rv = Xmarks.gSettings.syncOnShutdown;
    }

    if (rv) {
        var win = topwin.openDialog(
            "chrome://foxmarks/content/foxmarks-progress.xul", "_blank",
            "chrome,dialog,modal,centerscreen", "syncDirty", retval, null);
        if (retval.helpurl) { // we hit an error and user pressed help button
            if (cancel instanceof Ci.nsISupportsPRBoolean) {
                cancel.value = true;
                topwin.openDialog("chrome://browser/content/browser.xul",
                    "_blank", "chrome,all,dialog=no", retval.helpurl);
            }
        }
    }
}

function LoadFiles() {
    try {
        Cu.import("resource://xmarks/settings.jsm", Xmarks);
        Xmarks.tabs = {};
        Xmarks.history = {};
        Cu.import("resource://xmarks/tabsync.jsm", Xmarks.tabs);
        Cu.import("resource://xmarks/history.jsm", Xmarks.history);
        Cu.import("resource://xmarks/base64.jsm");
    } catch(e) {
        Cu.reportError(e)
    }

    LoadJavascript("chrome://foxmarks/content/foxmarks-update.js",
        typeof(ForceUpdate));
    LoadJavascript("chrome://foxmarks/content/foxmarks-clobber.js",
        typeof(onClobberCancel));
    LoadJavascript("chrome://foxmarks/content/foxmarks-bookmark.js",
        typeof(loadDatasourceSet));
    if ("@mozilla.org/browser/nav-bookmarks-service;1" in Cc)
        LoadJavascript("chrome://foxmarks/content/foxmarks-places.js",
            typeof(BookmarkDatasource));
    else
        LoadJavascript("chrome://foxmarks/content/foxmarks-rdf.js",
            typeof(BookmarkDatasource));
    LoadJavascript("chrome://foxmarks/content/foxmarks-nodes.js",
        typeof(Node));
    LoadJavascript("chrome://foxmarks/content/foxmarks-command.js",
        typeof(Command));
    LoadJavascript("chrome://foxmarks/content/foxmarks-core.js",
        typeof(Synchronize));
    LoadJavascript("chrome://foxmarks/content/foxmarks-json.js", 
        "undefined");

    Cu.import("resource://xmarks/network.jsm");

    LoadJavascript("chrome://foxmarks/content/shared/CreateAESManager.js",
        typeof(CreateAESManager));
    LoadJavascript("chrome://foxmarks/content/foxmarks-utils.js",
        typeof(forEach));
    LoadJavascript("chrome://foxmarks/content/foxmarks-unittest.js",
        typeof(gFoxmarksUT));
    LoadJavascript("chrome://foxmarks/content/foxmarks-uitools.js",
        typeof(Xmarks.OpenWindowByType));
    if("@mozilla.org/login-manager;1" in Cc){
        LoadJavascript("chrome://foxmarks/content/foxmarks-password.js",
            typeof(PasswordDatasource));
    }
    LoadJavascript("chrome://foxmarks/content/foxmarks-server.js",
        typeof(SyncServer));
}

var gFailureCount = 0;// number of consecutive times we've failed
var gBackoffUntil = 0;// if we've been failing, our backoff time (ms since 1970)
var gServerBackoff = 
    { 'bookmarks': 0,
      'passwords': 0,
      min: function(){
        return Math.min(this['bookmarks'], this['passwords']);
      },
      max: function(){
        return Math.max(this['bookmarks'], this['passwords']);
      },
      clear: function(){
        this['bookmarks'] = 0;
        this['passwords'] = 0;
      }
    };  // seconds server wants us to back off

function ReturnErrorMsg(code, msg, restoreState, noBackoff) {
    Xmarks.gSettings.lastError  = code;
    Xmarks.Notify({status: code, msg: msg });
    ClearBusy();
    SetState(restoreState ? restoreState : ((code == 503)
            ? "dirty" : "error"));
    Xmarks.LogWrite("Returned error: " + msg + "(" + code + ")");
    if(code != 2 && !noBackoff){
        var d = new Date();
        // Initial back-off is 15 minutes, doubling with each error
        gBackoffUntil = d.getTime() + 1000 * (Math.max(
            15 * 60 * Math.pow(2, gFailureCount++),
            gServerBackoff.max()) + Math.floor(Math.random() * 15));
        gServerBackoff.clear();;
        var retry = new Date();
        retry.setTime(gBackoffUntil);
        Xmarks.LogWrite("Will retry at " + retry);
    }
    fms.lastError = code;
}

function ReturnErrorCode(code) {
    ReturnErrorMsg(code, Xmarks.MapError(code));
}

function ReturnSuccess(msgname, args, restoreState) {
    if (args == null) {
        var args = {};
    }

    args.status = 0;
    args.msg = Xmarks.Bundle().GetStringFromName(msgname);

    SetState(restoreState ? restoreState : "ready");
    ClearBusy();
    gFailureCount = 0;
    fms.lastError = 0;
    Xmarks.LogWrite("Success: " + Xmarks.Bundle().GetStringFromName(msgname));
    Xmarks.Notify(args);
}

function SetBusy() {
    if (IsBusy._busy) {
        return false;
    } else {
        IsBusy._busy = true;
        return true;
    }
}

function ClearBusy() {
    IsBusy._busy = false;
}

function IsBusy() {
    return IsBusy._busy;
}
IsBusy._busy = false;

function GetState() {
    return GetState._state;
}
GetState._state = "ready";

function SetState(newstate) {
    if (newstate == GetState()) {
        return;
    }

    var os = Cc["@mozilla.org/observer-service;1"]
        .getService(Ci.nsIObserverService);

    os.notifyObservers(null, "foxmarks-statechange", newstate);

    GetState._state = newstate;
}

// Internal callbacks

function LogStart(op, dest) {
    Xmarks.LogWrite("------ Xmarks/" + Xmarks.FoxmarksVersion() + " (" + 
            fms.getStorageEngine("bookmarks") + ") starting " + op +
            " with " + dest + " ------");
}

function IfNotBusy(f) {
    if (!SetBusy()) {
        return false;
    }
    f.apply(fms, []);
    return true;
}

function HandleResponse(successmsg) {
    var prevState = GetState();
    SetState("working");
    return function(status, response) {
        if (status) {
            ReturnErrorCode(status);
        } else {
            ReturnSuccess(successmsg, response);
        }
    };
}

///////////////////////////////////////////////////////////////////////////
//
// nsFoxmarksService
//

var fms = null;  // set during initialization to instance object

function nsFoxmarksService() {
    LoadFiles();
}

nsFoxmarksService.prototype = {
    /////////////////////////////////////////////////////////////////////////
    // nsIFoxmarksService

    timer: null,
    _server: null,
    lastmodified: null,

    status: function(syncType) {
        return IfNotBusy(function() {
            LogStart("status", Xmarks.gSettings.host);
            if(syncType === undefined)
                syncType = "bookmarks";

            this.server.status(syncType, HandleResponse("msg.accountverified"));
        });
    },

    extstatus: function(syncType) {
        return IfNotBusy(function() {
            LogStart("extstatus", Xmarks.gSettings.host);
            if(syncType === undefined)
                syncType = "bookmarks";

            this.server.extstatus(syncType, 
                HandleResponse("msg.accountverified"));
        });
    },

    purgepasswords: function(){
        LogStart("purgepasswords", Xmarks.gSettings.host);
        this.server.purgepasswords(HandleResponse("msg.synccompleted"));
        return true;
    },

    restore: function(rev) {
        return IfNotBusy(function() {
            LogStart("Restoring to Revision: " + rev, Xmarks.gSettings.host);
            this.server.restore(rev, HandleResponse("msg.restore"));
        });
    },
    getrevision: function(rev) {
        return IfNotBusy(function() {
            LogStart("Getting Revision: " + rev, Xmarks.gSettings.host);
            this.server.getrevision(rev, HandleResponse("msg.getrevision"));
        });
    },

    getrevisions: function() {
        return IfNotBusy(function() {
            LogStart("getrevisions", Xmarks.gSettings.host);
            this.server.getrevisions(HandleResponse("msg.getrevisions"));
        });
    },
    verifypin: function(pin) {
        return IfNotBusy(function() {
            LogStart("verify pin", Xmarks.gSettings.host);
            this.server.verifypin(pin, HandleResponse("msg.pinverified"));
        });
    },

    synchronize: function(automatic, dirtyOnly) {
        return IfNotBusy(function() {
            LogStart("sync", Xmarks.gSettings.host);
            this.server.manual = automatic === true ? false : true;
            this.server.sync(HandleResponse("msg.synccompleted"), dirtyOnly);
        });
        return true;
    },

    synchronizeInitial: function (remoteIsMaster, doMerge) {
        return IfNotBusy(function() {
            LogStart("initial sync", Xmarks.gSettings.host);
            this.server.manual = true;

            // We need to self correct for password sync.  If there is an
            // uploadReq == true, we know it doesn't have data on the server.
            // if it's false, we should force a merge per our PRD.
            if(Xmarks.gSettings.isSyncEnabled("passwords")){
                // Set default higher if we're doing password sync
                Xmarks.gSettings.securityLevel = 1;
                if(!Xmarks.gSettings.mustUpload("passwords")){
                    Xmarks.gSettings.setMustMerge("passwords", true);
                }
            }

            var Finished = HandleResponse("msg.synccompleted");
            if (doMerge) {
                this.server.merge(!remoteIsMaster, Finished);
            } else {
                if (remoteIsMaster) {
                    this.server.download(Finished);
                } else {
                    this.server.upload(Finished);
                }
            }
        });
    },

    upload: function () {
        return IfNotBusy(function() {
            LogStart("upload", Xmarks.gSettings.host);
            this.server.manual = true;
            this.server.upload(HandleResponse("msg.uploadcompleted"));
        });
    },

    download: function () {
        return IfNotBusy(function() {
            LogStart("download", Xmarks.gSettings.host);
            this.server.manual = true;
            this.server.download(HandleResponse("msg.remotefilecopied"));
        });
    },

    _handleDataResponse: function(response){
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);

        os.notifyObservers(null, "foxmarks-dataservice", response.toSource());
    },
    getSimilarSites: function(url) {
        LogStart("getSimilarSites", Xmarks.gSettings.apiHost);
        this.server.getSimilarSites(url, this._handleDataResponse);
        return true;
    },
    getTurboTags: function(url) {
        LogStart("getTurboTags", Xmarks.gSettings.apiHost);
        this.server.getTurboTags(url, this._handleDataResponse);
        return true;
    },
    updateReview: function(url, url_id, title, description, rating, review, 
            post_to_fb) {
        LogStart("updateReview", Xmarks.gSettings.apiHost);
        this.server.updateReview(url, url_id, title, description, rating, 
            review, post_to_fb, this._handleDataResponse);
        return true;
    },
    getProfileNames: function() {
        var prevState = GetState();

        var funcFinished = function(status, response) {
            if (!status) {
                ReturnSuccess("error.0", response, prevState);
            } else {
                ReturnErrorMsg(status, Xmarks.MapError(status), prevState);
            }
        };
        return IfNotBusy(function() {
            LogStart("getProfileNames", Xmarks.gSettings.acctMgrHost);
            if (this.server.getProfileNames) {
                this.server.getProfileNames(funcFinished);
                return true;
            } else {
                return false;
            }
        });
    },

    launchSuccessPage: function(){
        this.server.countItems("bookmarks",
            "bookmark", function(status, num_bookmarks){
            var currver = Xmarks.FoxmarksVersion();
            // We used to set the protocol from Xmarks.gSettings.httpProtocol
            // but that causes warnings when the dest page contains non-HTTPS
            // content, so we peg the protocol as http. 
            Xmarks.OpenInNewTab("http://" + Xmarks.gSettings.webHost + 
                "/firefox/success/" + currver, true, 
                FoxmarksBuildPostData(num_bookmarks));
            Xmarks.gSettings.currVersion = currver;
        });
    },
    cancel: function() {
        this.server.cancel();
        var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
        timer.initWithCallback({ notify: function() {
            if (IsBusy()) {
                ReturnErrorCode(2);
            }
        }}, 2000, Ci.nsITimer.TYPE_ONE_SHOT);
    },
    datacancel: function() {
        this.server.datacancel();
    },

    getState: function() {
        return GetState();
    },

    _pin: null,
    _auth: null,

    getAuth: function() {
        return this._auth;
    },

    setAuth: function(auth) {
        this._auth = auth;
    },
    getPin: function() {
        return this._pin;
    },

    setPin: function(password) {
        this._pin = password;
    },

    getStorageEngine: function(synctype) {
        return getDatasourceAttribute(synctype, "engine");
    },

    getLastError: function() {
        return this.lastError;
    },

    lastError: 0,
    _uninstall: false,
    _channel: null,
    _pingListener: {
         onStartRequest: function () {},
         onDataAvailable: function () {},
         onStopRequest: function () {},
         onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
            this._newchannel = aNewChannel;
         },
        getInterface: function (aIID) {
            try {
                return this.QueryInterface(aIID);
            } catch (e) {
                throw Components.results.NS_NOINTERFACE;
            }
        },
        onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
        onStatus : function (aRequest, aContext, aStatus, aStatusArg) { },
        onRedirect : function (aOldChannel, aNewChannel) { },
        QueryInterface : function(aIID) {
            if (aIID.equals(Components.interfaces.nsISupports) ||
                aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
                aIID.equals(Components.interfaces.nsIChannelEventSink) || 
                aIID.equals(Components.interfaces.nsIProgressEventSink) ||
                aIID.equals(Components.interfaces.nsIHttpEventSink) ||
                aIID.equals(Components.interfaces.nsIStreamListener))
            return this;
            throw Components.results.NS_NOINTERFACE;
         } 
     },
     pingServer: function(topic){
        var ioService = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Components.interfaces.nsIIOService);

        // create an nsIURI
        var attrs = [];
        var session = Date.now().toString(36);
        attrs.push("app="       + "jezebel");
        attrs.push("mid="       + Xmarks.gSettings.machineId);
        attrs.push("abgroup="       + Xmarks.gSettings.abgroup);
        attrs.push("sess="      + session);
        attrs.push("page="      + topic);
        attrs.push("username="  + Xmarks.gSettings.username);
        attrs.push("no_cache="  + Date.now().toString(36));

        var query = attrs.join("&");
        var url = Xmarks.gSettings.httpProtocol + "tr.xmarks.com/tracking/impressions.gif?" + query;
        this._pingRequest = new Request(
            "GET",
            url, 
            null, 
            {
                isAuthRequest: false,
                headers: null,
                ignoreBody: true,
                ignoreAuthTokens: true
            }
        );
        this._pingRequest.Start(function(){});
    },
    getLastModified: function(synctype) {
        return fms.lastmodified;
    },

    get server() {
        if (!this._server) {
            this._server = new SyncServer();
        }
        return this._server;
    },

    Init: function() {
        // Pre-initialization here.
        var os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
        os.addObserver(this, "quit-application-requested", false);
        os.addObserver(this, "foxmarks-datasourcechanged", false);
        os.addObserver(this, "foxmarks-rununittest", false);
        os.addObserver(this, "foxmarks-unittesterror", false);
        os.addObserver(this, "foxmarks-tr", false);
        os.addObserver(this, "earlyformsubmit", false);
        os.addObserver(this, "final-ui-startup", false);
        os.addObserver(this, "em-action-requested", false);
        os.addObserver(this, "quit-application-granted", false);
        os.addObserver(this, "foxmarks-showsettingpane", false);
        os.addObserver(this, "foxmarks-realstart", false);
    },
    
    /////////////////////////////////////////////////////////////////////////
    //
    // nsIObserver

    observe: function(subject, topic, data)  { // called at startup
        var timerCallback = {

            notify: function(timer) {

                var now = new Date().getTime();

                // Do automatic sync if necessary
                if (IsOnline() && !IsBusy() && 
                    (!gFailureCount || now > gBackoffUntil) &&
                    Xmarks.gSettings.synchOnTimer && 
                    !Xmarks.gSettings.suspended &&
                    Xmarks.gSettings.haveSynced) {
                    if (Xmarks.gSettings.minutesSinceLastSync > 
                            Xmarks.gSettings.autoSynchFreq) {
                        fms.synchronize(true);
                    } else {
                        if (fms.lastmodified && Xmarks.gSettings.haveSynced && 
                            fms.lastmodified >
                            Date.parse(Xmarks.gSettings.lastSynchDate) &&
                            now - fms.lastmodified > 5 * 60 * 1000) {
                            fms.synchronize(true);
                        }
                    }
                }
            }
        }

        if (topic == "final-ui-startup") {

            // check to see if crypto hash is installed (bug #7397)
            // if we error, wait a while and try again
            var counter = 0;
            var cryptoCallback = {
                notify: function() {
                    // give it another shot
                    try {
                        var hash = Cc["@mozilla.org/security/hash;1"].
                                    createInstance(Ci.nsICryptoHash);
                    // otherwise, wait again if we have time
                    } catch(e){
                        counter++;

                        Xmarks.LogWrite("Error: Cryptohash not initialized");
                        if(counter < 10){
                            var timer = Cc["@mozilla.org/timer;1"].
                                createInstance(Ci.nsITimer);
                            timer.initWithCallback(cryptoCallback, 500,
                                Ci.nsITimer.TYPE_ONE_SHOT);
                        }
                        return;
                    }
                    FoxmarksCheckForServerChanges();
                }
            };

            var timer = Cc["@mozilla.org/timer;1"].
                createInstance(Ci.nsITimer);
            timer.initWithCallback(cryptoCallback, 0,
                Ci.nsITimer.TYPE_ONE_SHOT);
        } else if (topic == "foxmarks-realstart") {
            // Real initialization starts here.
            Xmarks.UpdateToXMarks();

            var dsList = getDatasources("all"); 
            fms.nat = {}; 
            while (dsList.length) {
                ds = dsList.shift();
                fms.nat[ds.syncType] = 
                    new ds().WatchForChanges(this.server);
            }

            if (Xmarks.gSettings.getBoolPref("enableSERP", undefined) ===
                    undefined && Xmarks.gSettings.majorVersion <= 3) {
                Xmarks.gSettings.serpEnabled = true;
            }

            if (!Xmarks.gSettings.wizardSuppress && 
                    !Xmarks.gSettings.useOwnServer &&
                    !Xmarks.gSettings.haveSynced) {
                Xmarks.LogWrite("Starting Wizard: Never Synched");
                FoxmarksLaunchSetupWizard();
            } else if (Xmarks.gSettings.majorVersion < 2) {
                Xmarks.LogWrite("Starting Wizard: Major Upgrade");
                Xmarks.gSettings.majorVersion = 3;
                FoxmarksLaunchSetupWizard();
            } else {
                // need to check for upgrades here
                Xmarks.gSettings.majorVersion = 3;
                var currver = Xmarks.FoxmarksVersion();
                var lastver = Xmarks.gSettings.currVersion;
                var ca = currver.split(".");
                var la = lastver.split(".");
                var newver =false;
                
                if(ca.length != la.length){
                    newver = true;
                } else {
                    for(var x=0; x < ca.length-1; x++){
                        if(parseInt(ca[x]) != parseInt(la[x])){
                            newver = true;
                            break;
                        }
                    }
                }
                if(newver){
                    FoxmarksLaunchUpgradePage();
                }
            }
            
            this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
            this.timer.initWithCallback(timerCallback, 1000*60,
                Ci.nsITimer.TYPE_REPEATING_SLACK);
        } else if (topic == "foxmarks-tr") {
            this.pingServer(data);

        } else if (topic == "quit-application-requested") {
            HandleShutdown();
        } else if(topic == "em-action-requested"){
            subject.QueryInterface(Components.interfaces.nsIUpdateItem);
            if(subject.id == "foxmarks@kei.com"){
                switch(data){
                    case 'item-uninstalled':
                        this._uninstall = true;
                        this.pingServer("uninstall");
                        break;
                    case 'item-disabled':
                        this.pingServer("disable");
                        break;
                    case 'item-cancel-action':
                        if(this._uninstall == false){
                            this.pingServer("cancel-disable");
                        } else {
                            this.pingServer("cancel-uninstall");
                        }
                        this._uninstall = false;
                        break;
                }

            }

        } else if(topic == "quit-application-granted"){
            if(this._uninstall){
                Xmarks.gSettings.clearAllPrefs();
            }
        } else if(topic == "foxmarks-showsettingpane"){
            if(data.length > 0){
                Xmarks.OpenFoxmarksSettingsDialog(data);
            }
        } else if (topic == "foxmarks-unittesterror"){
            try {
                ReturnErrorCode(parseInt(data));
            }
            catch(e){
                Components.utils.reportError(e);
            }
        } else if (topic == "foxmarks-rununittest"){
            this.server.runUnitTest();
        } else if (topic == "foxmarks-datasourcechanged") {
            var a = data.split(';');
            this.lastmodified = parseInt(a[0]);
            var okState = (GetState() == "ready" || GetState() == "unknown");
            if (okState && Xmarks.gSettings.haveSynced &&
                Xmarks.gSettings.isSyncEnabled(a[1]) && 
                this.lastmodified > Date.parse(Xmarks.gSettings.lastSynchDate)) {
                SetState("dirty");
            }
        } else {
            Xmarks.LogWrite("Yikes unknown topic " + topic);
        }
    },

    /////////////////////////////////////////////////////////////////////////
    // nsIFormSubmitObserver
    notify : function (formElement, aWindow, actionURI) {
        if(fms && fms.nat["passwords"]
                && Xmarks && Xmarks.gSettings.isSyncEnabled("passwords")
                ){
            fms.nat["passwords"].formsubmit(formElement);
        }
        return true;
    },

    /////////////////////////////////////////////////////////////////////////
    // nsIClassInfo
    getInterfaces: function (aCount) {
        var interfaces = [Ci.nsIFoxmarksService,
        Ci.nsIObserver,
        Ci.nsIFormSubmitObserver,
        Ci.nsiRDFObserver];
        aCount.value = interfaces.length;
        return interfaces;
    },

    getHelperForLanguage: function (aLanguage) {
        return null;
    },

    get contractID() {
        return "@foxcloud.com/extensions/foxmarks;1";
    },

    get classDescription() {
        return "Xmarks Service";
    },

    get classID() {
        return Components.ID("{49ace257-6111-48b2-a988-f9eb38b0fa58}");
    },

    get implementationLanguage() {
        return Ci.nsIProgrammingLanguage.JAVASCRIPT;
    },

    get flags() {
        return Ci.nsIClassInfo.SINGLETON;
    },

    /////////////////////////////////////////////////////////////////////////
    // nsISupports
    QueryInterface: function (aIID) {
        if (!aIID.equals(Ci.nsIFoxmarksService) &&
            !aIID.equals(Ci.nsISupports) &&
            !aIID.equals(Ci.nsIRDFObserver) &&
            !aIID.equals(Ci.nsIFormSubmitObserver) &&
            !aIID.equals(Ci.nsIObserver))
        throw Components.results.NS_ERROR_NO_INTERFACE;
        return this;
    }
};


var fms = new nsFoxmarksService();
Xmarks.fms = fms;
