/*
 Copyright 2007-2008 Foxmarks Inc.

 foxmarks-server.js: component that implements the logical interface to
 the server.

 */

// TO DO:
// * Deal with failure to retrieve baseline state from server -> merge



function getDatasourceAttribute(synctype, attr){
    switch(synctype){
        case 'bookmarks':
            switch(attr){
                case 'engine':
                    return BookmarkDatasource.STORAGE_ENGINE;
                default:
                    throw("getDatasourceAttribute: unknown attribute (" + attr + ")");
            }
            break;
        case 'lastmodifieddate':
            break;
        default:
            throw("getDatasourceAttribute: unknown synctype (" + synctype + ")");
    }

}

function hasPasswordSync(){
    return "@mozilla.org/login-manager;1" in Cc;

}

var SYNCSET = {};
if(hasPasswordSync()){
    SYNCSET = {
        "bookmarks": BookmarkDatasource,
        "passwords": PasswordDatasource
    };
}
else {

    SYNCSET = {
        "bookmarks": BookmarkDatasource
    };
}
   
function createDatasource(type){
    var model = new SYNCSET[type];
    if(model === undefined)
        throw("createDatasource:  unknown synctype (" + type +")");

    return model;
}   

function loadDatasourceSet(allItems) {
    var result = [];
    var type;
    var obj = {};
    for(type in SYNCSET){
        if(obj[type] === undefined){
            if(Xmarks.gSettings.isSyncEnabled(type) || allItems){
                var model = createDatasource(type);
                result.push(model);
            }
        }
    }

    
    return result;
}

function SyncServer() {
}

SyncServer.prototype = {
    _syncEngine: null,
    _request: null,
    _baselineCache: {},
    cancel: function() {
        if(this._syncEngine) {
            this._syncEngine.cancel();
        } 
        if (this._request){
            this._request.Cancel();
        }
    },
    datacancel: function(){
        if (this._request){
            this._request.Cancel();
        }
    },
    _createSyncEngine: function(datasource){
        var cls = Xmarks.gSettings.useOwnServer ?
            OwnSyncEngine : 
            Syncd2SyncEngine;
        var result = new cls(this,datasource);

        result.manual = this.manual;
        return result;
    },

    getBaseline: function(syncType,callback){
        var ds = createDatasource(syncType);
        var se = this._createSyncEngine(ds);
        se._fetchBaseline(function(status){
            if(se._responseOK(status, callback)){
                callback(0, se._baseline);
            }
        });
    },
    suspendWatcher: function(state){
        var os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
        os.notifyObservers(null, "foxmarks-watchersuspend", 
            state ? "1" : "0");
    },


    _doPerDatasource: function(funcname, callback, arg2){
        var list = loadDatasourceSet();
        var that = this;
        var ds = {};

        try {
            that.suspendWatcher(true);
        
            // loop through each set of syncdata, using the 
            //  chained callback mechanism, dropping out if
            //  we encounter an error
            var mycallback = function(statuscode){
                if(statuscode == 0 || statuscode == 444){
                    // 444 -handle purged state
                    if(statuscode == 444){
                        // if we allow purge from other data types
                        // we'll need to modify this string
                        Xmarks.Alert(Xmarks.Bundle().GetStringFromName(
                            "msg.passwordsyncpurged"));
                        Xmarks.gSettings.setSyncEnabled(ds.syncType, false);
                        statuscode = 0;
                    }
                    if(ds && ds.syncType !== undefined){
                        Xmarks.gSettings.SyncComplete(ds.syncType);
                        Xmarks.SetProgressComponentStatus(ds.syncType, "end");
                    }
                    ds = list.shift();
                    if(ds){
                        that._syncEngine = that._createSyncEngine(ds);
                        Xmarks.SetProgressComponentStatus(ds.syncType, "start");
                        if(arg2 !== undefined){
                            that._syncEngine[funcname](arg2, mycallback);
                        }
                        else {
                            that._syncEngine[funcname](mycallback);
                        }
                    }
                    else {
                        callback(statuscode);
                        that.suspendWatcher(false);
                        that._syncEngine = null;
                        if(!Xmarks.gSettings.useBaselineCache){
                            that._baselineCache = {};
                        }
                    }
                }
                else {
                    callback(statuscode);
                    that.suspendWatcher(false);
                    that._syncEngine = null;
                    if(!Xmarks.gSettings.useBaselineCache){
                        that._baselineCache = {};
                    }
                }
            }

            mycallback(0);
        }catch(e){
            that.suspendWatcher(false);
            if(typeof e == "object" && e.message){
                Xmarks.LogWrite("Sync Error: " + e.message + "(" +
                        e.fileName + ": " + e.lineNumber + ")");
            } else {
                Xmarks.LogWrite("Synchronization failed. Error is " + 
                    e.toSource() + " (type = " + typeof e + ")");
            }
            if(typeof e == "number"){
                mycallback(e);
            } else {
                mycallback(4);
            }
        }
    },
    sync: function(prevState, callback){
        Xmarks.SetProgressMessage("progress.syncing");
        this._doPerDatasource("sync", callback, prevState);
    },
    upload: function(callback){
        Xmarks.SetProgressMessage("progress.syncing");
        this._doPerDatasource("upload", callback);
    },
    download: function(callback){
        Xmarks.SetProgressMessage("progress.syncing");
        this._doPerDatasource("download", callback);
    },
    merge: function(local, callback){
        Xmarks.SetProgressMessage("progress.syncing");
        this._doPerDatasource("merge", callback, local);
    },
    status: function(syncType, callback){
        var ds = createDatasource(syncType);
        var se = this._createSyncEngine(ds);
        se.status(callback);
    },
    extstatus: function(syncType, callback){
        var ds = createDatasource(syncType);
        var se = this._createSyncEngine(ds);
        se.extstatus(callback);
    },
    getProfileNames: function(callback){
        var se = this._createSyncEngine(null);
        se.getProfileNames(callback);
    },
    _getCorrelatorInfo: function(url, corruri, callback, flags){
        const https = "https://";
        flags = flags || {};
        var str;
        var self = this;
        var protocol = url.substr(0,https.length).toLowerCase();
        var funcFinished = function(status, response) {
            self._request = null;
            callback(status, response);
        };

        // we contact correlator via https if the url we are interested
        // in is https
        if (Xmarks.gSettings.securityLevel == 1 || protocol == https ){ 
            str = https;
        } else {
            str = "http://";
        }

        str += Xmarks.gSettings.apiHost + corruri;
        this._request = new Request(
            "POST",
            str,
            {
                urls: [url],
                mid: Xmarks.gSettings.machineId,
                apihost: Xmarks.gSettings.apiHost,
                drifthost: Xmarks.gSettings.driftHost,
                statichost: Xmarks.gSettings.staticHost,
                cid: "xmfx"
            }, 
            {
                isAuthRequest: false,
                headers: null,
                ignoreBody: false,
                noJSON: flags.noJSON,
                noAuthDialog: true
            }
        );
        this._request.Start(funcFinished);
    },
    getSimilarSites: function(url, callback){
        return this._getCorrelatorInfo(
            url, 
            "/internal/bib", 
            callback, 
            {
                noJSON: true
            }
        );
    },
    getTurboTags: function(url, callback){
        return this._getCorrelatorInfo(url, "/internal/topics_review/read", 
            callback);
    },

    updateReview: function(url, url_id, title, description, rating, review, 
            post_to_fb, callback) {
        var args =  { url: url, url_id: url_id, title: title, 
            description: description, rating: rating, review: review, 
            post_to_fb: post_to_fb };
        if (args.url_id == -1) {
            // Hack: this is our "no url_id" value
            delete args.url_id;
        }
        var useSSL = (Xmarks.gSettings.securityLevel == 1 || 
            url.indexOf("https://") == 0);
        this._request = new Request("POST", 
            { protocol: useSSL ? "https" : "http", 
                host: Xmarks.gSettings.apiHost, 
                path: "/internal/review/write" },
            args);
        this._request.Start(callback);
    },

    purgepasswords: function(callback){
        var ds = createDatasource("passwords");
        var se = this._createSyncEngine(ds);
        se.purgepasswords(callback);
    },
    restore: function(rev,callback){
        var ds = createDatasource("bookmarks");
        var se = this._createSyncEngine(ds);
        try {
            se.restore(rev,callback);
        } catch(e){
            Xmarks.LogWrite("Restore Failed (" + e.message + ")");
            if(typeof e == "number"){
                callback(e);
            } else {
                callback(4);
            }
        }
    },
    getrevision: function(rev,callback){
        var ds = createDatasource("bookmarks");
        var se = this._createSyncEngine(ds);
        try {
            se.getrevision(rev,callback);
        } catch(e){
            Xmarks.LogWrite("GetRevision Failed (" + e.message + ")");
            if(typeof e == "number"){
                callback(e);
            } else {
                callback(4);
            }
        }
    },
    getrevisions: function(callback){
        var ds = createDatasource("bookmarks");
        var se = this._createSyncEngine(ds);
        try {
            se.getrevisions(callback);
        } catch(e){
            Xmarks.LogWrite("GetRevision Failed (" + e.message + ")");
            if(typeof e == "number"){
                callback(e);
            } else {
                callback(4);
            }
        }
    },
    verifypin: function(pin, callback){
        var ds = createDatasource("passwords");
        var se = this._createSyncEngine(ds);
        try {
            se.verifypin(pin, callback);
        } catch(e){
            Xmarks.LogWrite("Verify Pin Failed (" + e.message + ")");
            if(typeof e == "number"){
                callback(e);
            } else {
                callback(4);
            }
        }
    },
    runUnitTest: function(){
        gFoxmarksUT.run();
    },
    countItems: function(synctype, itemtype, callback){
        var ds = createDatasource(synctype);
        var se = this._createSyncEngine(ds);
        se.countItems(itemtype, callback);
    }
};


function SyncEngine(datasource) {
    this._datasource = datasource;
}

SyncEngine.prototype = {
    request: null,
    _iscancelled: false,

    get _baseline() {
        return this._mgr._baselineCache[
            this._datasource.syncType
        ];
    },
    set _baseline(val) {
        this._mgr._baselineCache[
            this._datasource.syncType
        ] = val;
    },

    cancel:  function() {
        if (this.request) {
            this.request.Cancel();
        }
        this._iscancelled = true;
    },
    isCancelled: function(){
        return this._iscancelled;
    },

    _responseOK: function(response, callback) {
        if(this.isCancelled()){
            callback(2);
            return false;
        }
        else if (typeof response == 'number') {
            if (!response) {
                return true;
            } else {
                callback(response);
            }
        } else if (typeof response == 'object') {
            if (!response.status) {
                return true;
            } else {
                if (response.status == 403) {
                    Xmarks.LogWrite("Got a 403");
                    Handle403(response);
                } else if (response.status == 503) {
                    try {
                        gServerBackoff[this._datasource.syncType] = response.backoff_delay || 0;
                        if (gServerBackoff[this._datasource.syncType]) {
                            Xmarks.LogWrite("Server wants us to wait " +
                                gServerBackoff[this._datasource.syncType] + " seconds");
                        }
                    } catch(e) {}
                }
                callback(response.status);
                return false;
            }
        } else {
            throw Error("unexpected response type: " + response);
        }
    },
    countItems: function(itemtype, callback){
        var self = this;
        var ns = new Nodeset(self._datasource);
        ns.FetchFromNative(function(status){
            if (self._responseOK(status, callback)) {
                var ctr = 0;
                ns.OnTree(
                    function(nid){
                      if(ns.Node(nid).ntype == itemtype)
                        ctr++;
                    },
                    function(){
                        callback(status, ctr);
                    }
                );
            }
        });
    },
    sync: function(prevState, callback) {
        // Local Vars
        var lns = null, sns = null;
        var lcs = null, scs = null;
        var sco = null;
        var lastModified = 0;
        var self = this;
        var fms = Cc["@foxcloud.com/extensions/foxmarks;1"].
            getService(Ci.nsIFoxmarksService);
        self.startTime = Xmarks.LogTimer("Timer Sync Start");
        var funcCheckStatus = function(status, response) {
            if (self._responseOK(status, callback)) {
                if (response.isreset == true) {
                    self.upload(callback);
                } else {
                    self.merge(true, callback);
                }
            }
        };

        // Local functions
        var funcGetServerChanges = function(status) {
            if (self._responseOK(status, callback)) {
                Xmarks.LogTimer("Timer Sync: Fetch Baseline", self.startTime);
                // Get changes.
                self._getServerChanges(funcGotServerChanges);
            }
        };
        var funcGotServerChanges = function(status, serverContextObject) {
            if (self._responseOK(status, callback)) {
                Xmarks.LogTimer("Timer Sync: Got Server Changes", self.startTime);
                sco = serverContextObject;
                if (!sco.continuous) {
                    // Someone has done an upload. Allow the user the option
                    // of performing a merge or a download.

                    switch (self._datasource.DiscontinuityPrompt()) {
                    case 0: // merge
                        return self.merge(false, callback);

                    case 1: // download
                        return self.download(callback);

                    case 2: // cancel
                        callback(2);
                        return;
                    }
                }
                if (!sco.mscs.length && prevState == 'ready' && 
                        self._haveFetched) {
                    Xmarks.LogWrite("Nothing to see here; move along.");
                    callback(0);
                    return;
                }

                //Xmarks.SetProgressMessage("progress.syncing");
                scs = sco.mscs;
                sns = sco.sns;

                // Calculate minimal local change set.
                lastModified = fms.getLastModified(self._datasource.synctype);
                lns = new Nodeset(self._datasource);
                lns.FetchFromNative(funcGotLocalNodeset);
                self._haveFetched = true;
            }
        }
        var funcGotLocalNodeset = function(status) {
            if (self._responseOK(status, callback)) {
                Xmarks.LogTimer("Timer Sync: Fetched Local", self.startTime);
                var base = new Nodeset(self._datasource,self._baseline);
                base.Compare(lns, funcGetLocalCommandset);
            }
        }
        var funcGetLocalCommandset = function(status, lcs) {
            if (self._responseOK(status, callback)) {
                Xmarks.LogTimer("Timer Sync: Command Set", self.startTime);
                // Check to see whether there's been a clobber
                if (lns.length < 3 * self._baseline.length / 4) {
                    Xmarks.LogWrite("Yikes! Length was " + self._baseline.length + 
                        " but is now " + lns.length);
                    self.manual = true;
                    if (!self._datasource.ClobberDialog(lns.length, self._baseline.length)) {
                        // User canceled. Back off for 24 hours.
                        gBackoffUntil = Date.now() + 24 * 60 * 60 * 1000;
                        callback(2);
                        return;
                    }
                }
            
                Xmarks.LogWrite("lcs = " + lcs.length + " scs = " + scs.length);

                try {
                    Synchronize(self._baseline, lcs, scs, lns, sns, 
                        funcSyncComplete);
                } catch (e) {
                    Xmarks.LogWrite("Synchronization failed. Error is " + 
                        e.toSource());
                    if(typeof e == "number"){
                        callback(e);
                    } else {
                        callback(4);
                    }
                }
            }
        };
        var funcSyncComplete = function(finalLcs,
                finalScs,
                conflicts,
                showedUI) {
                Xmarks.LogTimer("Timer Sync: Sync Set", self.startTime);
            //Xmarks.SetProgressMessage("progress.writing");

            if (showedUI) {
                self.manual = true;
            }

            if (lastModified != fms.getLastModified(self._datasource.synctype)) {
                Xmarks.LogWrite("Local datastore changed during sync");
                Xmarks.LogWrite("lastModified " + lastModified + " != " + fms.getLastModified(self._datasource.synctype));
                callback(409);
                return;
            }

            // Scoping: assign back to our enclosing function's locals
            lcs = finalLcs;
            scs = finalScs;

            // Apply the server's changes to the local set.
            try {
                lns = new Nodeset(self._datasource, lns);
                scs.execute(lns);
                Xmarks.LogTimer("Timer Sync: Applied Changes", self.startTime);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }
            
            // Write local changes (if any) to the server.
            if (lcs.set.length > 0) {
                self._writeServerChanges(lcs, lns, sco, conflicts, 
                    funcWroteServerChanges);
            } else {
                funcWroteServerChanges();
            }
        };
        var funcWroteServerChanges = function(obj) {
            if (obj) {      // We had something to write
                if (self._responseOK(obj, callback)) {
                } else {
                    Xmarks.LogWrite("putchanges failed; response is " + 
                            obj.toJSONString());
                    return;
                }
            }

            Xmarks.LogTimer("Timer Sync: Sent Server Changes", self.startTime);
            // Flush the local nodeset back to the native datastore if it changed.
            if (scs.set.length > 0) {
                //Xmarks.SetProgressMessage("progress.loading");
                lns.FlushToNative(funcWroteLocalChanges);
            } else {
                funcWroteLocalChanges(0);
            }
        };
        var funcWroteLocalChanges = function(response) {
            // If we got changes from the server or wrote changes to
            // the server, we'll have a new revision number. If neither
            // of these happened, then the sync was a big no-op.
            if (self._responseOK(response, callback)) {
                Xmarks.LogTimer("Timer Sync: Wrote to Native", self.startTime);
                if (lcs.set.length > 0 || scs.set.length > 0) {
                    self._completeTransaction(lns, sco, callback);
                } else {
                    // We didn't do anything, but note that we successfully
                    // Synced nonetheless.
                    callback(0);         // Done!
                }
            }
        };

        // function start

        // If we've never synced, do an upload or merge.
        if (!Xmarks.gSettings.getHaveSynced(self._datasource.syncType)) {
            self.status(funcCheckStatus);
        }
        else {
            if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
                Xmarks.LogWrite("Forced Upload: Pin Reset");
                self.upload(function(response){
                    if(self._responseOK(response, callback)){
                        Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
                        callback(response);
                    }
                });
                return;

            }
            else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
                Xmarks.LogWrite("Forced Merge for Passwords");
                self.merge(true, function(response){
                    if(self._responseOK(response, callback)){
                        Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
                        callback(response);
                    }
                });
                return;

            }
            // Normal sync: make sure we've got a baseline to work with.
            //Xmarks.SetProgressMessage("progress.downloading");
            self._fetchBaseline(funcGetServerChanges);
            return;
        }
    }
};


function Syncd2SyncEngine(mgr, datasource) {
    this._datasource = datasource;
    this._mgr = mgr;
}
Syncd2SyncEngine.prototype = new SyncEngine;

function OwnSyncEngine(mgr, datasource) {
    this._datasource = datasource;
    this._mgr = mgr;
}
OwnSyncEngine.prototype = new SyncEngine;

Syncd2SyncEngine.prototype._args = function(dict, ignoreUpload) {
    if (this.manual) {
        dict["manual"] = true;
    }
    if (gFailureCount) {
        dict["retry"] = gFailureCount;
    }
    if (Xmarks.gSettings.machineId) {
        if (!dict["log"]) {
            dict["log"] = {};
        }
        dict["log"]["mid"] = Xmarks.gSettings.machineId;
        dict["log"]["serp"] = Xmarks.gSettings.serpEnabled ? Xmarks.gSettings.serpMaxItems : 0;
        dict["log"]["ssEnabled"] = Xmarks.gSettings.simsiteEnabled;

        var st = [];
        for(var x = 0; x < 10; x++){
            st.push(Xmarks.gSettings.getST(false,x));
        }

        if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
            dict["log"]["st"] = st;
        }
        st = [];
        for(var x = 0; x < 10; x++){
            st.push(Xmarks.gSettings.getST(true,x));
            Xmarks.gSettings.clearST(x);
        }

        if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
            dict["log"]["ust"] = st;
        }
        if(Xmarks.gSettings.trSERP){
            dict["log"]["trs"] = Xmarks.gSettings.trSERP;
            Xmarks.gSettings.trSERP = "";
        }
        if(Xmarks.gSettings.numTurboTags){
            dict["log"]["ttags"] = Xmarks.gSettings.numTurboTags;
            Xmarks.gSettings.numTurboTags = 0;;
        }
    }
    if (Xmarks.gSettings.viewId) {
        if(!ignoreUpload){
            dict.view = Xmarks.gSettings.viewId;
        }
    }


    return dict;
}

Syncd2SyncEngine.prototype.upload = function(callback) {
    var self = this;
    var ns = new Nodeset(this._datasource);

    // if we are doing a force upload, then that trumps forcedMerge
    if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
        Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
    }
    // even though the user says upload, we don't want
    // to do that for passwords during initial sync
    if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Merge for Passwords");
        self.merge(true, function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }

    // Hack: we don't seem to be able to handle
    // authentication during an upload request.
    // So to avoid trouble, we do a throw-away
    // status request first. If auth is required,
    // it will be handled during the status request,
    // after which we will get on to our real work.

    var funcForceAuth = function(status) {
        if (self._responseOK(status, callback)) {
            //Xmarks.SetProgressMessage("progress.writing");
            ns.FetchFromNative(funcFetched);
        }
    };
    var funcFetched = function(e) {
        if (e) {
            callback(e);
            return;
        }
        ns.ProvideCommandset(funcContinue);
    };
    var funcContinue = function(status, cs) {
        if (self._responseOK(status, callback)) {
            self.request = new Request("POST", 
                {
                    path: "/sync/" + self._datasource.syncType + "/upload",
                    host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
                }, 
                self._args({ "commands" : cs },
                    Xmarks.gSettings.mustUpload(self._datasource.syncType)
                )
            );
            self.request.Start(funcDone);
        }
    };
    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            if (!response.toprev) {
                Xmarks.LogWrite("Error: Invalid response to upload");
                callback(500);
            } else {
                Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
                self._completeTransaction(ns, { toprev: response.toprev }, 
                    callback);
            }
        }
    };

    if (Xmarks.gSettings.viewId && !Xmarks.gSettings.mustUpload(self._datasource.syncType)) {
        this._uploadWithProfile(callback);
        return;
    }
    self.status(funcForceAuth);
}

Syncd2SyncEngine.prototype.purgepasswords = function(callback) {
    var self = this;
    var ns = new Nodeset(this._datasource);

    // Hack: we don't seem to be able to handle
    // authentication during an upload request.
    // So to avoid trouble, we do a throw-away
    // status request first. If auth is required,
    // it will be handled during the status request,
    // after which we will get on to our real work.

    var funcForceAuth = function(status) {
        if (self._responseOK(status, callback)) {
            self.request = new Request("POST", 
                {
                    path: "/sync/" + self._datasource.syncType + "/purge",
                    host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
                } , self._args({ mode: "data"}));
            self.request.Start(function(statuspurge){
                if (self._responseOK(statuspurge, callback)) {
                    Xmarks.gSettings.SetSyncRevision(
                        self._datasource.syncType, 0);
                    callback(0);
                }
           });
        }
    };
    self.status(funcForceAuth);
}
Syncd2SyncEngine.prototype._uploadWithProfile = function(callback) {
    var self = this;
    var revision;
    var ns;
    var lns;

    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            if (!response.toprev || !response.commands) {
                Xmarks.LogWrite("Error: Invalid response to download");
                callback(500);
                return;
            }
            revision = response.toprev;
            ns  = new Nodeset(self._datasource);
            var cs = new Commandset(response.commands);
            try {
                cs.execute(ns);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }

            // Retrieve local set.
            lns = new Nodeset(self._datasource);
            lns.FetchFromNative(funcGotLocal);
        }
    };
    var funcGotLocal = function(response) {
        // Calculate difference: server -> local.
        if (self._responseOK(response, callback)) {
            ns.Compare(lns, funcCompared);
        }
    };
    var funcCompared = function(status, cs) {
        if (self._responseOK(status, callback)) {
            if (cs.length) {
                //Xmarks.SetProgressMessage("progress.uploading");
                self.request = new Request("POST",
                    {
                        path: "/sync/" + self._datasource.syncType + "/putchanges",
                        host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
                    }, 
                    self._args({ "baserev": revision, "commands" : cs }));
                self.request.Start(funcFinished);
            } else {
                funcFinished({ status: 0, toprev: revision});
            }
        }
    };
    var funcFinished = function(response) {
        if (self._responseOK(response, callback)) {
            if (!response.toprev) {
                Xmarks.LogWrite("Error: Invalid response to putchanges");
                callback(500);
            } else {
                self._completeTransaction(ns, { toprev: response.toprev }, 
                    callback);
            }
        }
    };


    // Download current server state.
    Xmarks.SetProgressMessage("progress.downloading");
    self.request = new Request("POST",
        {
            path: "/sync/"  + self._datasource.syncType + "/download",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, self._args({}));
    self.request.Start(funcDone);
}

Syncd2SyncEngine.prototype.download = function(callback, rev) {
    var self = this;
    var ns;
    var revision;

    // resetPIN trumps download (only occurs in the case of
    // resetPIN in setup dialog)
    if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Upload: Pin Reset");
        self.upload(function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }
    else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Merge for Passwords");
        self.merge(true, function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }
    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            if (!response.toprev || !response.commands) {
                Xmarks.LogWrite("Error: Invalid response to download");
                callback(500);
                return;
            }
            revision = response.toprev;
            ns = new Nodeset(self._datasource);
            var cs = new Commandset(response.commands);
            try {
                cs.execute(ns);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }
            //Xmarks.SetProgressMessage("progress.loading");
            ns.FlushToNative(funcFinished);
        }
    };
    var funcFinished = function(response) {
        if (self._responseOK(response, callback)) {
            if(!rev){
                self._completeTransaction(ns, { toprev: revision }, callback);
            } else {
                callback(response);
            }
        }
    };

    //Xmarks.SetProgressMessage("progress.downloading");
    var params = {};
    if(rev){
        params.rev = rev;
    }
    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType + "/download",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, self._args(params));
    self.request.Start(funcDone);
}


Syncd2SyncEngine.prototype.verifypinbrokenroot = function(pin, callback) {
    var self = this;
    var ns;
    var revision;

    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            for(var nid in response.nodes){
                if(!response.nodes.hasOwnProperty(nid)){
                    continue;
                } else if(response.nodes[nid].data){
                    var result = self._datasource.verifyPin(pin, response.nodes[nid]); 
                    callback(result ? 0 : 100);
                    return;
                }
            }
            // must have been no nodes with data, so assume it's valide
            callback(0);
        }
    };

    //Xmarks.SetProgressMessage("progress.verifying");
    Xmarks.LogWrite("Verifying PIN Broken Root");
    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType + "/state",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, {"nodes":"ROOT", "depth":"children"});
    self.request.Start(funcDone);
}

Syncd2SyncEngine.prototype.restore  = function(rev,callback) {
    var self = this;

    this.download(
        function(response){
            if (self._responseOK(response, callback)) {
               // self.upload(callback);
               self.sync('dirty',
                   function(response){
                        if (self._responseOK(response, callback)) {
                            Xmarks.gSettings.SyncComplete(
                                self._datasource.syncType
                            );
                        }
                        callback(response);
                    }
               );
           }
        },
        rev
    );
}

Syncd2SyncEngine.prototype.getrevision  = function(rev,callback) {
    var self = this;
    var funcFetched = function(response) {
        if (self._responseOK(response, callback)) {
            callback(0, response);
        }
    }

    //Xmarks.SetProgressMessage("progress.gettingprofilenames");
    var request = new Request("POST",
        { path: "/sync/bookmarks/state",
          host: Xmarks.gSettings.getServerHost(self._datasource.syncType) },
        this._args({rev: rev}) );
    request.Start(funcFetched);

}
Syncd2SyncEngine.prototype.getrevisions  = function(callback) {
    var self = this;
    var funcFetched = function(response) {
        if (self._responseOK(response, callback)) {
            callback(0, response);
        }
    }

    //Xmarks.SetProgressMessage("progress.gettingprofilenames");
    var request = new Request("POST",
        { path: "/sync/bookmarks/getrevisions",
          host: Xmarks.gSettings.getServerHost(self._datasource.syncType) },
        this._args({limit: Xmarks.gSettings.getRevisionLimit}) );
    request.Start(funcFetched);

}
Syncd2SyncEngine.prototype.verifypin = function(pin, callback) {
    var self = this;
    var ns;
    var revision;

    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
           Xmarks.LogWrite("Verifying PIN (Received Test Node)");
           if(!response.nodes.ROOT.data){
                Xmarks.LogWrite("WARNING: Bad Root Problem");
                self.verifypinbrokenroot(pin, callback);
           } else {
                var result = self._datasource.verifyPin(pin, response.nodes.ROOT); 
                callback(result ? 0 : 100);
           }
        }
    };

    //Xmarks.SetProgressMessage("progress.verifying");
    Xmarks.LogWrite("Verifying PIN (Starting)");
    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType + "/state",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, {"nodes":"ROOT", "depth":"self"});
    self.request.Start(funcDone);
}
// Fetch changes from the server. Passes to callback an object containing:
// * continous: true if the changes on the server are continuous.
// * mscs: the minimal server commandset. 
// * other server context info (in this case, toprev) to passed into
//   _writeServerChanges().


Syncd2SyncEngine.prototype._getServerChanges = function(callback) {
    // Get changes.
    var self = this;
    var toprev = 0;
    var sns = new Nodeset(self._datasource, self._baseline);

    var funcProcessResponse = function(response) {
        if (self._responseOK(response, callback)) {
            if (response.continuous == false) {
                callback(0, { continuous: false });
            } else {
                if (!response.toprev || !response.commands) {
                    Xmarks.LogWrite("Error: Invalid response to getchanges");
                    callback(500);
                    return;
                }
                toprev = response.toprev;
                // Calculate mcs.
                if (response.commands.length) {
                    scs = new Commandset(response.commands);
                    try {
                        scs.execute(sns);
                    } catch (e) {
                        Xmarks.LogWrite("execute failed: " + e.toSource());
                        if(typeof e == "number"){
                            callback(e);
                        } else {
                            callback(4);
                        }
                        return;
                    }
                    var base = new Nodeset(self._datasource,self._baseline);
                    base.Compare(sns, funcCalculatedMcs);
                } else {
                    // Not modified
                    callback(0, { mscs: new Commandset(), toprev: toprev, 
                            sns: sns, continuous: true});
                }
            }
        }
    };
    var funcCalculatedMcs = function(status, cs) {
        if (self._responseOK(status, callback)) {
            Xmarks.LogWrite(">>> Finished mcs: " + cs.toSource());
            callback(0, { mscs: cs, toprev: toprev, continuous: true, 
                    sns: sns });
        }
    };

    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType + "/getchanges",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        },
        self._args({ "baserev": self._baseline.currentRevision}) );
    self.request.Start(funcProcessResponse);
}

OwnSyncEngine.prototype.testDupURL = function(){
    // force check to see if user has two different urls for password
    // and bookmark
    if(Xmarks.gSettings.isSyncEnabled("bookmarks") && 
        Xmarks.gSettings.isSyncEnabled("passwords")){
        if(Xmarks.gSettings.getUrlWithUsernameAndPassword("passwords") ==
            Xmarks.gSettings.getUrlWithUsernameAndPassword("bookmarks")){
                return true;
        }
    }
    return false;
}
OwnSyncEngine.prototype._getServerChanges = function(callback) {
    // Download the file, but only if the etag doesn't match
    var self = this;
    var headers = {};
    var token;
    var etag;
    var sns;

    var funcProcessFile = function(response) {
        if (response.status == 304) {   // Not modified.
            callback(0, { continuous: true, mscs: new Commandset(), 
                token: Xmarks.gSettings.getToken(self._datasource.syncType), etag: response.etag });
            return;
        }
        if (self._responseOK(response, callback)) {
            if (response.token != Xmarks.gSettings.getToken(self._datasource.syncType)) {
                callback(0, { continuous: false, etag: response.etag } );
                return;
            }

            token = response.token;
            etag = response.etag;
            var cs = new Commandset(response.commands);
            sns = new Nodeset(self._datasource);
            try {
                sns.Execute(cs);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }

            var base = new Nodeset(self._datasource,self._baseline);
            base.Compare(sns, funcCompared);
        }
    };
    var funcCompared = function(status, cs) {
        if (self._responseOK(status)) {
            callback(0, { continuous: true, mscs: cs, token: token, 
                    sns: sns, etag: etag });
        }
    };

    var serveretag = Xmarks.gSettings.getEtag(this._datasource.syncType);
    if (serveretag.length) {
        headers["If-None-Match"] = serveretag;
    }
    self.request = new Request("GET", 
        Xmarks.gSettings.getUrlWithUsernameAndPassword(
            self._datasource.syncType
        ),
        null, 
        {
            isAuthRequest: false,
            headers: headers
        }
    );
    self.request.Start(funcProcessFile);
}

Syncd2SyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
        callback) {

    var self = this;
    var funcWrote = function(response) {
        if (self._responseOK(response, callback)) {
            sco.toprev = response.toprev;
            if (!sco.toprev) {
                Xmarks.LogWrite("Error: Invalid response to putchanges");
                callback(500);
            } else {
                callback(0);
            }
        }
    };

    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType + "/putchanges",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        },
        self._args({ baserev : sco.toprev, commands : lcs, log : conflicts } ));
    self.request.Start(funcWrote);
}

OwnSyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
        callback) {
    var self = this;

    var funcGotCommandset = function(status, cs) {
        var header = {};
        if (!Xmarks.gSettings.disableIfMatchOnPut && sco.etag && sco.etag.length) {
            header = { "Etag" : sco.etag };
        }
        if (self._responseOK(status, callback)) {
            self.request = new Request("PUT", 
                Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
                { token: sco.token, commands: cs },
                {
                    isAuthRequest: false,
                    headers: header,
                    ignoreBody: true
                }
            );
            self.request.Start(funcWritten);
        }
    };
    var funcWritten = function(response) {
        if (self._responseOK(response, callback)) {
            sco.etag = response.etag;
            callback(0);
        }
    };

    lns.ProvideCommandset(funcGotCommandset);
}

Syncd2SyncEngine.prototype.status = function(callback) {
    var self = this;

    var funcCheckStatus = function(response) {
        if (self._responseOK(response, callback)) {
            if (!response.toprev) {
                Xmarks.LogWrite("Error: Invalid response to status");
                callback(500);
                return;
            }
            callback(0, response);
        }
    };

    Xmarks.LogWrite("Entered Status...");
    //Xmarks.SetProgressMessage("progress.verifying");

    self.request = new Request("POST",
        { path: "/sync/" + self._datasource.syncType + "/status",
          host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, {});
    self.request.Start(funcCheckStatus);
}
Syncd2SyncEngine.prototype.extstatus = function(callback) {
    var self = this;
    this.status(function(normal_status,response){
        // if we get an error, do callback right away
        if(normal_status){
            callback(normal_status, response);

        // if we are reset, no need to check if we are purged
        } else if(response.isreset){
            response.ispurged = false;
            callback(normal_status, response);
        // call for the root to see if we get a 444
        } else {
            self.request = new Request("POST", 
                {
                    path: "/sync/" + self._datasource.syncType + "/state",
                    host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
                }, {"nodes":"ROOT", "depth":"self"});
            self.request.Start(function(stateresponse){
                if (self._responseOK(stateresponse, function(errnum){
                    if(errnum == 444){
                        response.ispurged = true;
                        callback(0, response);
                    } else {
                        callback(errnum, response);
                    }
                })) {
                    response.ispurged = false;
                    callback(0, response);
                }
            });
        }
    });

}
OwnSyncEngine.prototype.status = function(callback) {
    var self = this;

    var funcHandleResponse = function(response) {
        if (response.status == 404) {
            callback(0, { status: 0, isreset: true, ispurged: false });
        } else if (self._responseOK(response, callback)) {
            callback(0, { status: 0, isreset: false, ispurged: false });
        }
    };

    Xmarks.LogWrite("Entered status...");
    if(this.testDupURL()){
        callback(1011);
        return;
    }
    //Xmarks.SetProgressMessage("progress.verifying");
    self.request = new Request("GET",
        Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
    self.request.Start(funcHandleResponse);
}
OwnSyncEngine.prototype.extstatus = OwnSyncEngine.prototype.status;


Syncd2SyncEngine.prototype._getServerState = function(callback) {
    var self = this;
    self.request = new Request("POST", 
        {
            path: "/sync/" + self._datasource.syncType +  "/download",
            host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
        }, self._args({}) );
    self.request.Start(callback);
}

OwnSyncEngine.prototype._getServerState = function(callback) {
    var self = this;
    self.request = new Request("GET",
        Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
    self.request.Start(callback);
}

SyncEngine.prototype.merge = function(local, callback) {
    // Perform a merge (an "additive sync").
    // local is a boolean which determines what merge uses
    // as the starting set: true for local, false for server.

    Xmarks.LogWrite("Entered Merge...");

    var self = this;
    var serverNS = new Nodeset(self._datasource);
    var localNS = new Nodeset(self._datasource);
    var mergedNS = null;
    var revision;
    var sco;

    // resetPIN trumps merge
    if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Upload: Pin Reset");
        self.upload(function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }

    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            sco = response;
            var cs = new Commandset(response.commands);
            try {
                cs.execute(serverNS);
            } catch (e) {
                if(typeof e == "number"){
                    callback(e);
                } else {
                    Xmarks.LogWrite("execute failed: " + e.toSource());
                    callback(4);
                }
                return;
            }
            localNS.FetchFromNative(funcFetched);
        }
    };
    var funcFetched = function( e ) {

        if (e) {
            callback(e);
            return;
        }
        //Xmarks.SetProgressMessage("progress.merging");
        if (local) {
            localNS.Merge(serverNS);
            mergedNS = localNS;
        } else {
            mergedNS = new Nodeset(self._datasource, serverNS);
            mergedNS.Merge(localNS);
        }

        // calculate server's MCS
        Xmarks.LogWrite("Finished merge; calculating mcs");
        serverNS.Compare(mergedNS, funcWriteChanges);
    };
    var funcWriteChanges = function(status, cs) {
        if (self._responseOK(status, callback)) {
            // Write changes to the server.
            //Xmarks.SetProgressMessage("progress.writing");
            if (cs.set.length > 0) {
                self._writeServerChanges(cs, mergedNS, sco, null, 
                    funcWroteServerChanges);
            } else {
                funcWroteServerChanges(0);
            }
        }
    };
    var funcWroteServerChanges = function(response) {
        // Write 'em back to native store, too
        if (self._responseOK(response, callback)) {
            //Xmarks.SetProgressMessage("progress.loading");
            mergedNS.FlushToNative(funcFlushed);
        }
    };
    var funcFlushed = function(response) {
        if (self._responseOK(response, callback)) {
            Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
            self._completeTransaction(mergedNS, sco, callback);
        }
    };

    //Xmarks.SetProgressMessage("progress.downloading");
    self._getServerState(funcDone);
}

Syncd2SyncEngine.prototype._fetchBaseline = function(callback) {
    // Fetch the baseline if necessary
    var self = this;

    var funcLoadedFromServer = function(response) {
        if (self._responseOK(response, callback)) {
            var cs = new Commandset(response.commands);
            try {
                cs.execute(self._baseline);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }
            self._baseline.currentRevision = response.rev;
            self._baseline.SaveToFile(funcLoaded);
            return;
        } else {
            Xmarks.LogWrite("Failed to load baseline from server.");
        }
    };
    var funcLoaded = function() {
        // Loaded from file or server
        // var nat = new NativeDatasource();
        self._baseline.BaselineLoaded(self._baseline, funcGotIt);
        return;
    };
    var funcGotIt = function(status) {
        if (self._responseOK(status, callback)) {
            self._baseline.hash = Xmarks.gSettings.hash;
            callback(0);
        }
    };

    if (self._baseline && self._baseline.hash == Xmarks.gSettings.hash) {
        Xmarks.LogWrite("Using Baseline Cache.");
        funcGotIt(0);
        return;
    }

    self._baseline = new Nodeset(self._datasource);
    try {
        self._baseline.LoadFromFile();
    } catch (e) {
        // We failed to load our baseline locally -- try getting
        // it from the server.
        Xmarks.LogWrite("Failed to load baseline from file: " + e.name);
        var request = new Request("POST",
            {
                path: "/sync/" + self._datasource.syncType + "/download",
                host: Xmarks.gSettings.getServerHost(self._datasource.syncType)
            },
            self._args({ rev: Xmarks.gSettings.GetSyncRevision(self._datasource.syncType), depth: "all", 
                    log: { "error": e.name }}));
        request.Start(funcLoadedFromServer);
        return;
    }

    funcLoaded();
}

OwnSyncEngine.prototype._fetchBaseline = function(callback) {
    // Fetch the baseline if necessary
    var self = this;

    var funcLoaded = function() {
        // Loaded from file 
        // var nat = new NativeDatasource();
        self._baseline.BaselineLoaded(self._baseline, funcGotIt);
    };
    var funcGotIt = function(status) {
        if (self._responseOK(status, callback)) {
            self._baseline.hash = Xmarks.gSettings.hash;
            callback(0);
        }
    };

    if (self._baseline && self._baseline.hash == Xmarks.gSettings.hash) {
        funcGotIt(0);
        return;
    }

    self._baseline = new Nodeset(self._datasource);
    try {
        self._baseline.LoadFromFile();
    } catch (e) {
        // We failed to load our baseline locally -- we're hosed.
        Xmarks.LogWrite("Failed to load baseline.");
        if(typeof e == "number"){
            callback(e);
        } else {
            callback(4);
        }
        return;
    }

    funcLoaded();
}

Syncd2SyncEngine.prototype._completeTransaction = function(ns, sco, callback) {
    var self = this;

    var funcFinishUp = function(status) {
        if (self._responseOK(status, callback)) {
            self._baseline = ns;
            self._baseline.hash = Xmarks.gSettings.hash;
            self._baseline.currentRevision = sco.toprev;
            self._baseline.SaveToFile(funcFinishedWrite);
        }
    };
    var funcFinishedWrite = function(status) {
        if (self._responseOK(status, callback)) {
            // Xmarks.gSettings.currentRevision = sco.toprev;
            Xmarks.gSettings.SetSyncRevision(self._datasource.syncType, sco.toprev);
            callback(0);
        }
    };

    ns.Declone(funcFinishUp);
}

Syncd2SyncEngine.prototype.getProfileNames = function(callback) {
    var self = this;
    var funcFetched = function(response) {
        if (self._responseOK(response, callback)) {
            callback(0, response);
        }
    }

    //Xmarks.SetProgressMessage("progress.gettingprofilenames");
    var request = new Request("POST",
        { path: "/user/profiles/getnames",
          host: Xmarks.gSettings.acctMgrHost },
        this._args({}) );
    request.Start(funcFetched);

}

OwnSyncEngine.prototype.upload = function(callback) {
    var self = this;
    var ns = new Nodeset(self._datasource);
    var token = Date.now().toString(16);

    // if we are doing a force upload, then that trumps forcedMerge
    if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
        Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
    }
    // even though the user says upload, we don't want
    // to do that for passwords during initial sync
    if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Merge for Passwords");
        self.merge(true, function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }


    var funcFetched = function(status) {
        if (self._responseOK(status, callback)) {
            ns.ProvideCommandset(funcContinue);
        }
    };
    var funcContinue = function(status, cs) {
        if (self._responseOK(status, callback)) {
            self.request = new Request("PUT", 
                Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
                { commands: cs, token: token }, 
                { ignoreBody: true }
            );
            self.request.Start(funcDone);
        }
    };
    var funcDone = function(response) {
        if (self._responseOK(response, callback)) {
            Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
            self._completeTransaction(ns, 
                { etag: response.etag, token: token }, callback);
        }
    };

    //Xmarks.SetProgressMessage("progress.writing");
    ns.FetchFromNative(funcFetched);
}

OwnSyncEngine.prototype.verifypin = function(pin, callback) {
    var self = this;
    var ns;

    var funcDone = function(response) {
        var funcFinished = function(resp) {
            if (self._responseOK(resp, callback)) {
                self._completeTransaction(ns, 
                    { etag: response.etag, token: response.token }, callback);
            }
        };
        if (self._responseOK(response, callback)) {
            ns = new Nodeset(self._datasource);
            var cs = new Commandset(response.commands);
            try {
                cs.execute(ns);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }

           var result = self._datasource.verifyPin(pin, ns.Node(NODE_ROOT)); 
           callback(result ? 0 : 100);
        }
    };
    //Xmarks.SetProgressMessage("progress.verifying");
    Xmarks.LogWrite("Verifying PIN (Own Server)");
    self.request = new Request("GET", Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
    self.request.Start(funcDone);
}

OwnSyncEngine.prototype.download = function(callback) {
    var self = this;
    var ns;

    // resetPIN trumps download (only occurs in the case of
    // resetPIN in setup dialog)
    if(Xmarks.gSettings.mustUpload(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Upload: Pin Reset");
        self.upload(function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustUpload(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }
    else if(Xmarks.gSettings.mustMerge(self._datasource.syncType)){
        Xmarks.LogWrite("Forced Merge for Passwords");
        self.merge(true, function(response){
            if(self._responseOK(response, callback)){
                Xmarks.gSettings.setMustMerge(self._datasource.syncType, false);
                callback(response);
            }
        });
        return;
    }

    var funcDone = function(response) {
        var funcFinished = function(resp) {
            if (self._responseOK(resp, callback)) {
                self._completeTransaction(ns, 
                    { etag: response.etag, token: response.token }, callback);
            }
        };
        if (self._responseOK(response, callback)) {
            ns = new Nodeset(self._datasource);
            var cs = new Commandset(response.commands);
            try {
                cs.execute(ns);
            } catch (e) {
                Xmarks.LogWrite("execute failed: " + e.toSource());
                if(typeof e == "number"){
                    callback(e);
                } else {
                    callback(4);
                }
                return;
            }
            //Xmarks.SetProgressMessage("progress.loading");
            ns.FlushToNative(funcFinished);
        }
    };

    //Xmarks.SetProgressMessage("progress.downloading");
    self.request = new Request("GET", Xmarks.gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
    self.request.Start(funcDone);
}

OwnSyncEngine.prototype._completeTransaction = 
        function(ns, sco, callback) {
    var self = this;

    var funcFinishUp = function(status) {
        if (self._responseOK(status, callback)) {
            self._baseline = ns;
            self._baseline.hash = Xmarks.gSettings.hash;
            self._baseline.SaveToFile(funcFinishedWrite);
        }
    };
    var funcFinishedWrite = function(status) {
        if (self._responseOK(status, callback)) {
            Xmarks.gSettings.setToken(self._datasource.syncType, sco.token);
            if (sco.etag) Xmarks.gSettings.setEtag(self._datasource.syncType, sco.etag);
            Xmarks.gSettings.SyncComplete(self._datasource.syncType);
            callback(0);
        }
    };
    ns.Declone(funcFinishUp);
}


