// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockDeliciousApi.jsm");
CU.import("resource:///modules/FlockOnlineFavsUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");

const MODULE_NAME = "Delicious";

const CLASS_NAME = "Flock Delicious Service";
const CLASS_SHORT_NAME = "delicious";
const CLASS_TITLE = "Delicious";
const CLASS_ID = Components.ID("{5c54771f-2628-4200-af16-f94609177abd}");
const CONTRACT_ID = "@flock.com/delicious-service;1";

const SERVICE_ENABLED_PREF = "flock.service.delicious.enabled";

const DELICIOUS_FAVICON = "chrome://flock/content/services/delicious/favicon.png";
const DELICIOUS_API_URL = "https://api.del.icio.us/v1/";
const DELICIOUS_USER_URL_BASE = "http://del.icio.us";

const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";

const DELICIOUS_STRING_BUNDLE = "chrome://flock/locale/services/delicious.properties";

// migration constants
const WEBSERVICE_PREF = "flock.favorites.webservice.id";
const OLD_DELICIOUS_PW_HOST = ":favorites:webservice:delicious:";

// One second, expressed in nsITimer terms.
const TIMER_SECOND = 1000;
var PREFS = null;
/**************************************************************************
 * Component: Flock Delicious Service
 **************************************************************************/

// Constructor.
function flockDeliciousService() {
  PREFS = CC[PREFERENCES_CONTRACTID].getService(CI.nsIPrefBranch);
  this._init();

  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "url");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "isHidden");
  FlockSvcUtils.flockIWebService.addDefaultMethod(this, "getStringBundle");

  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "loginURL");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "tooltip");
  FlockSvcUtils.flockILoginWebService.addDefaultMethod(this, "getAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAuthenticatedAccount");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAccountUrns");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "logout");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getAccountIDFromDocument");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getCredentialsFromForm");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsDocument");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "ownsLoginForm");
  FlockSvcUtils.flockILoginWebService
               .addDefaultMethod(this, "getSessionValue");
}


/**************************************************************************
 * Flock Delicious Service: XPCOM Component Creation
 **************************************************************************/

flockDeliciousService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  CLASS_ID,
  CONTRACT_ID,
  flockDeliciousService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsITimerCallback,
    CI.flockIWebService,
    CI.flockILoginWebService,
    CI.flockIBookmarkWebService,
    CI.flockIPollingService,
    CI.flockIMigratable
  ]
);

// FlockXPCOMUtils.genericModule() categories
flockDeliciousService.prototype._xpcom_categories = [
  { category: "wsm-startup" },
  { category: "flockMigratable" },
  { category: "flockWebService", entry: CLASS_SHORT_NAME }
];

/**************************************************************************
 * Flock Delicious Service: Private Data and Functions
 **************************************************************************/

// Member variables.
flockDeliciousService.prototype._init =
function DeliciousSvc__init() {
  FlockSvcUtils.getLogger(this).info(".init()");

  // Determine whether this service has been disabled, and unregister if so.
  var prefService = CC["@mozilla.org/preferences-service;1"]
                .getService(CI.nsIPrefBranch);
  if (prefService.getPrefType(SERVICE_ENABLED_PREF) &&
     !prefService.getBoolPref(SERVICE_ENABLED_PREF))
  {
    this._logger.info("Pref " + SERVICE_ENABLED_PREF
                              + " set to FALSE... not initializing.");
    this.deleteCategories();
    return;
  }

  var profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
  var evtID = profiler.profileEventStart("delicious-init");

  this._api = new DeliciousAPI(DELICIOUS_API_URL, this._logger);
  this._accountClassCtor = flockDeliciousAccount;
  // getWD() also adds the "_acUtils" property.
  this.domains = FlockSvcUtils.getWD(this)
                              .getString("delicious", "domains", "icio.us");
  FlockSvcUtils.initService(this);
  this._coopObj.logoutOption = false;

  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = DELICIOUS_STRING_BUNDLE;

  profiler.profileEventEnd(evtID, "");
}

// Helper to retrieve tags on server.
flockDeliciousService.prototype._getAllTags =
function DeliciousSvc__getAllTags(aUrn, aAccountId,
                                  aPassword, aPollingListener) {
  this._logger.debug("getAllTags(...)");
  var svc = this;

  var flockListener = {
    onSuccess: function DeliciousSvc__getAllTags_onSuccess(aSubject, aTopic) {
      svc._logger.info("tags/get SUCCEEDED: " + aTopic);

      var tags = [];
      for (var i = 0; i < aSubject.length; i++) {
        tags.push(aSubject[i].tag);
      }
      onlineFavoritesBackend.updateTags(svc, aAccountId, tags);

      // Tell the poller we're done
      if (aPollingListener) {
        aPollingListener.onResult();
      }
    },
    onError: function DeliciousSvc__getAllTags_onError(aFlockError, aTopic) {
      svc._logger.error("tags/get FAILED");
      // BUG: 5705 -  report error via notification bar?
      // onlineFavoritesBackend.showNotification(message);
      if (aPollingListener) {
        aPollingListener.onError(aFlockError);
      }
    }
  }

  this._api.tagsGet(flockListener, null);
}

// Migrate from Cardinal
flockDeliciousService.prototype._migrateDeliciousAccount =
function DeliciousSvc__migrateDeliciousAccount(aContext) {
  var pw = this._acUtils.getFirstPasswordForHost(OLD_DELICIOUS_PW_HOST);

  // migrate online bm privacy settings
  PREFS.setBoolPref("flock.favorites.do." + CONTRACT_ID + "--" + pw.username, true);
  PREFS.setBoolPref("flock.favorites.private." + CONTRACT_ID + "--" + pw.username,
                    PREFS.getBoolPref("flock.favorites.privateByDefault"));

  var accountURN = this.urn + ":" + pw.username;
  this._acUtils.setPassword(accountURN, pw.username, pw.password);
  this._acUtils.removeAllPasswordsForHost(OLD_DELICIOUS_PW_HOST);

  var acct = this.addAccount(pw.username, false, null);
  acct.keep(null);

  yield true;
}

// Migrate from Cormorant, Grackle, Grouse
flockDeliciousService.prototype._migrateFromCoop =
function DeliciousSvc__migrateFromCoop(aContext) {
  var accountUrns = this.getAccountUrns();

  while (accountUrns.hasMore()) {
    var account = this.getAccount(accountUrns.getNext());
    // This call will just update the existing account
    onlineFavoritesBackend.createAccount(this,
                                         account.getParam("accountId"),
                                         false);
  }

  yield true;
}

/**************************************************************************
 * Flock Delicious Service: flockIWebService Implementation
 **************************************************************************/

// readonly attribute AString urn;
flockDeliciousService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";

// readonly attribute AString title;
flockDeliciousService.prototype.title = CLASS_TITLE;

// readonly attribute AString icon;
flockDeliciousService.prototype.icon = DELICIOUS_FAVICON;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
// FIXME: File a bug for this as IDL is case-insensitive.
flockDeliciousService.prototype.contractId = CONTRACT_ID;

// readonly attribute AString shortName;
flockDeliciousService.prototype.shortName = CLASS_SHORT_NAME;

// readonly attribute boolean needPassword;
flockDeliciousService.prototype.needPassword = true;

// flockIWebServiceAccount addAccount(in AString aAccountId,
//                                    in boolean aIsTransient,
//                                    in flockIListener aFlockListener);
flockDeliciousService.prototype.addAccount =
function DeliciousSvc_addAccount(aAccountId, aIsTransient, aFlockListener) {
  this._logger.debug("addAccount('" + aAccountId + "', "
                     + aIsTransient + ", aFlockListener)");
  var acct = onlineFavoritesBackend.createAccount(this,
                                                  aAccountId, aIsTransient);
  if (aFlockListener) {
    aFlockListener.onSuccess(acct, "addAccount");
  }
  return acct;
}

// void removeAccount(in AString aUrn);
flockDeliciousService.prototype.removeAccount =
function DeliciousSvc_removeAccount(aUrn) {
  this._logger.debug("removeAccount('" + aUrn + "')");
  onlineFavoritesBackend.removeAccount(this, aUrn);
}

// DEFAULT: flockIWebServiceAccount getAccount(in AString aUrn);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: void logout();


/**************************************************************************
 * Flock Delicious Service: flockILoginWebService Implementation
 **************************************************************************/

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: flockILoginInfo getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);

/**
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
flockDeliciousService.prototype.updateAccountStatusFromDocument =
function DeliciousSvc_updateAccountStatusFromDocument(aDocument, 
                                                      aAccount, 
                                                      aFlockListener) 
{
  if (aAccount) {
    // We're logged in to this account
    if (!this._acUtils.ensureOnlyAuthenticatedAccount(aAccount)) {
      if (aFlockListener) {
        aFlockListener.onSuccess(aAccount, null);
      }
    }
  } else if (this._WebDetective
                 .detect("delicious", "loggedout", aDocument, null))
  {
    // We're logged out (of all accounts)
    this._acUtils.markAllAccountsAsLoggedOut(this);
  }
}

/**************************************************************************
 * Flock Delicious Service: flockIBookmarkWebService Implementation
 **************************************************************************/

// void publish (in flockIListener aFlockListener, in AString aAccountId,
//               in flockIBookmark aBookmark);
// flockIBookmarkWebService
flockDeliciousService.prototype.publish =
function DeliciousSvc_publish(aFlockListener, aAccountId, aBookmark) {
  this._logger.info("Publish (" + aBookmark.URL + "," + aBookmark.name
                    + ") to " + aAccountId + "@Delicious");
  var accountUrn = this.urn + ":" + aAccountId;
  var password = this._acUtils.getPassword(accountUrn);
  var svc = this;
  var tags = "";
  if (aBookmark.tags) {
    // Remove the FLOCK_UNFILED tag, that we add to favorites that have no tag.
    // Theorically favorites shouldn't have FLOCK_UNFILED with other tags,
    // but even when that happen we want to be sure not to push
    // FLOCK_UNFILED to the service.
    var tagsArray = aBookmark.tags.split(/[\s,]/).sort();
    if (tagsArray[0] && tagsArray[0] == onlineFavoritesBackend.FLOCK_UNFILED) {
      tagsArray.pop();
    }
    tags = tagsArray.join(" ").replace(/^ /, "");
  }

  var args = {
    url: aBookmark.URL,
    description: aBookmark.name,
    extended: aBookmark.description,
    tags: tags,
    shared: aBookmark.shared ? "yes" : "no"
  };

  var deliciousApiListener = {
    onError: function DeliciousSvc_publish_onError(aFlockError) {
      svc._logger.error("posts/add FAILED");
      // BUG: 5705 -  report error via notification bar?
      // onlineFavoritesBackend.showNotification(message);
      if (aFlockListener) {
        aFlockListener.onError(aFlockError, aBookmark.URL);
      }
    },
    onSuccess: function DeliciousSvc_publish_onSuccess(aXML) {
      svc._logger.info("posts/add SUCCEEDED");

      // Validate the nsIDOMDocument response.
      if (!svc._api.isExpectedResponse(aXML, "result")) {
        svc._logger.error("posts/add succeeded - but with invalid xml response");
        // BUG: 5705 -  report error via notification bar?
        // onlineFavoritesBackend.showNotification(message);
        if (aFlockListener) {
          var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
          // FIXME: fill in this flockIError.
          error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR;
          aFlockListener.onError(error, aBookmark.URL);
        }
      } else {
        // Process the validated response.
        var result = aXML.documentElement.getAttribute("code");
        svc._logger.debug(" Result for posts/add: " + result);
        if (result == "done") {
          var account = svc.getAccount(accountUrn);
          onlineFavoritesBackend.updateBookmark(svc,
                                                account,
                                                aBookmark);
          if (aFlockListener) {
            aFlockListener.onSuccess(svc, "publish");
          }
        } else if (aFlockListener) {
          var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
          // FIXME: fill in this flockIError.
          error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR;
          aFlockListener.onError(error, "publish");
        }
      }
    }
  }

  this._api.call("posts/add", args, deliciousApiListener, password);
}

// void publishList (in flockIListener aFlockListener, in AString aAccountId,
//                   in nsISimpleEnumerator aBookmarkList);
flockDeliciousService.prototype.publishList =
function DeliciousSvc_publishList(aFlockListener, aAccountId, aBookmarkList) {
  var svc = this;
  var i = 0;
  var bookmarks = [];

  var syncCallback = {
    notify: function syncCb_notify(timer) {
      var bm = bookmarks.shift();
      svc.publish(aFlockListener, aAccountId, bm);
    }
  }

  while (aBookmarkList.hasMoreElements()) {
    var bookmark = aBookmarkList.getNext().QueryInterface(CI.flockIBookmark);
    // Duplicate it because it's going to be removed too early
    bookmarks[i] = {};
    bookmarks[i].URL = bookmark.URL;
    bookmarks[i].name = bookmark.name;
    bookmarks[i].description = bookmark.description;
    bookmarks[i].tags = bookmark.tags;
    bookmarks[i].time = bookmark.time;
    bookmarks[i].shared = bookmark.shared;

    this._logger.debug("Set a timer to " + i * TIMER_SECOND
                       + " for " + bookmarks[i].URL);
    var publishTimer = CC["@mozilla.org/timer;1"]
                       .createInstance(CI.nsITimer);
    // 1 second between each publish
    publishTimer.initWithCallback(syncCallback, i * TIMER_SECOND,
                                  CI.nsITimer.TYPE_ONE_SHOT);
    i++;
  }
}

// void remove (in flockIListener aFlockListener, in AString aAccountId,
//              in AString aBookmark);
flockDeliciousService.prototype.remove =
function DeliciousSvc_remove(aFlockListener, aAccountId, aBookmark) {
  this._logger.info("Remove " + aBookmark + " from "
                    + aAccountId + "@Delicious");
  var password = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var svc = this;
  var args = { url: aBookmark };

  var deliciousApiListener = {
    onError: function DeliciousSvc_remove_onError(aError) {
      svc._logger.error("posts/delete FAILED");
      // BUG: 5705 -  report error via notification bar?
      // onlineFavoritesBackend.showNotification(message);
      if (aFlockListener) {
        aFlockListener.onError(aError, "remove");
      }
    },
    onSuccess: function DeliciousSvc_remove_onSuccess(aXML) {
      svc._logger.info("posts/delete SUCCEEDED");

      // Validate the nsIDOMDocument response.
      if (!svc._api.isExpectedResponse(aXML, "result")) {
        svc._logger.error("posts/delete succeeded - but with invalid xml response");
        // BUG: 5705 -  report error via notification bar?
        // onlineFavoritesBackend.showNotification(message);
        if (aFlockListener) {
          var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
          // FIXME: fill in this flockIError.
          error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR;
          aFlockListener.onError(error, "remove");
        }
      } else {
        // Process the validated response.
        var result = aXML.documentElement.getAttribute("code");
        svc._logger.debug("Result for posts/delete: " + result);
        if (result == "done") {
          onlineFavoritesBackend.destroyBookmark(svc, aAccountId, aBookmark);
          CC["@flock.com/poller-service;1"].getService(CI.flockIPollerService)
          .forceRefresh(this.urn + ":" + aAccountId + ":bookmarks");
          if (aFlockListener) {
            aFlockListener.onSuccess(svc, "remove");
          }
        } else if (aFlockListener) {
          var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
          // FIXME: fill in this flockIError.
          error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR;
          aFlockListener.onError(error, "remove");
        }
      }
    }
  }

  this._api.call("posts/delete", args, deliciousApiListener, password);
}

// boolean exists (in AString aAccountId, in AString aURL);
flockDeliciousService.prototype.exists =
function DeliciousSvc_exists(aAccountId, aURL) {
  return onlineFavoritesBackend.favoriteExists(this, aAccountId, aURL);
}

// boolean isPrivate(in AString aAccountId, in AString aURL);
flockDeliciousService.prototype.isPrivate =
function DeliciousSvc_isPrivate(aAccountId, aURL) {
  return onlineFavoritesBackend.isPrivate(this, aAccountId, aURL);
}

// AString getUserURL (in AString aAccountId);
flockDeliciousService.prototype.getUserUrl =
function DeliciousSvc_getUserUrl(aAccountId) {
  return DELICIOUS_USER_URL_BASE + "/" + aAccountId;
}


/**************************************************************************
 * Flock Delicious Service: flockIPollingService Implementation
 **************************************************************************/

// void refresh(in AString aUrn, in flockIPollingListener aPollingListener);
flockDeliciousService.prototype.refresh =
function DeliciousSvc_refresh(aURN, aPollingListener) {
  var svc = this;
  this._logger.info("refresh(" + aURN + ")");

  if (!this._coop.Account.exists(aURN)) {
    aPollingListener.onResult();
    return;
  }

  var coopAccount = this._coop.get(aURN);
  var accountId = coopAccount.accountId;
  var accountUrn = this.urn + ":" + accountId;
  // nsILoginInfo for auth for this sync
  var password = this._acUtils.getPassword(accountUrn);

  var flockListener = {
    onSuccess: function DeliciousSvc_refresh_onSuccess(aSubject, aTopic) {
      svc._logger.info("posts/all SUCCEEDED: " + aTopic);
      if (aTopic != "nonew") {
        onlineFavoritesBackend.updateLocal(svc, aSubject, aTopic, accountUrn);
      }
      aPollingListener.onResult();
    },
    onError: function DeliciousSvc_refresh_onError(aFlockError, aTopic) {
      svc._logger.error("posts/all FAILED: ["
                        + aFlockError.errorCode + "] "
                        + aFlockError.errorString + " (["
                        + aFlockError.serviceErrorCode + "] "
                        + aFlockError.serviceErrorString + ")");

      if (aPollingListener) {
        aPollingListener.onError(aFlockError);
      }
    }
  }
  // even for the first refresh, we need to get the last update time from 
  // the server. Bruno
  var lastUpdate = coopAccount.accountParamList
                              .getCustomParam("last_update_time")
                              .value;
  this._api.postsUpdate(flockListener,
                        password,
                        lastUpdate);
}

/**************************************************************************
 * Flock Delicious Service: flockIMigratable Implementation
 * FIXME: Spinoff bug for cleanup of flockIMigrationManager.idl
 **************************************************************************/

flockDeliciousService.prototype.__defineGetter__("migrationName",
function DeliciousSvc_getter_migrationName() {
  return flockGetString("common/migrationNames", "migration.name.delicious");
});

// boolean needsMigration(in string oldVersion);
flockDeliciousService.prototype.needsMigration =
function DeliciousSvc_needsMigration(aOldVersion) {
  var major = parseInt(aOldVersion[0], 10);
  var middle = parseInt(aOldVersion[2], 10); // aOldVersion[1] == "."

  if (major == 0 && middle == 7) {
    // If online bookmarks webservice is configured AND using Delicious
    var pw = this._acUtils.getFirstPasswordForHost(OLD_DELICIOUS_PW_HOST);
    if (PREFS.getPrefType(WEBSERVICE_PREF) &&
        PREFS.getCharPref(WEBSERVICE_PREF) == "delicious")
    {
      return (pw != null);
    } else {
      // Delicious is not configured so we might as well delete the pm entry
      // since account info is stored in rdf after migration
      if (pw) {
        var pm = CC["@mozilla.org/passwordmanager;1"]
                .getService(CI.nsIPasswordManager);
        pm.removeUser(OLD_DELICIOUS_PW_HOST, pw.username);
      }
      return false;
    }
  } else if ((major == 0) || // Cormorant
             (major == 1 && middle <= 2)) // Grackle, Grouse
  {
    return true;
  }
  return false;
}

// nsISupports startMigration(in string aOldVersion,
//                            in flockIMigrationProgressListener aListener);
flockDeliciousService.prototype.startMigration =
function DeliciousSvc_startMigration(aOldVersion,
                                     aFlockMigrationProgressListener) {
  var ctxt = {
    oldVersion: aOldVersion,
    listener: aFlockMigrationProgressListener
  };

  return { wrappedJSObject: ctxt };
}

// boolean doMigrationWork(in nsISupports aMigrationContext);
flockDeliciousService.prototype.doMigrationWork =
function DeliciousSvc_doMigrationWork(aMigrationContext) {
  var context = aMigrationContext.wrappedJSObject;

  if (!context.deliciousMigrator) {
    if (context.oldVersion.substr(0, 3) == "0.7") {
      // Cardinal
      context.deliciousMigrator = this._migrateDeliciousAccount(context);
    } else {
      context.deliciousMigrator = this._migrateFromCoop(context);
    }
  }
  if (context.deliciousMigrator.next()) {
    context.deliciousMigrator = null;
  }

  return Boolean(context.deliciousMigrator);
}

// void finishMigration(in nsISupports aMigrationContext);
flockDeliciousService.prototype.finishMigration =
function DeliciousSvc_finishMigration(aMigrationContext) {
}

/**************************************************************************
 * Component: Flock Delicious Account
 **************************************************************************/

function flockDeliciousAccount() {
  FlockSvcUtils.getLogger(this).info(".init()");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "login");
  wsa.addDefaultMethod(this, "logout");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "setParam");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "setCustomParam");
  wsa.addDefaultMethod(this, "isAuthenticated");
}

/**************************************************************************
 * Flock Delicious Account: XPCOM Component Creation
 **************************************************************************/

// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add flockDeliciousAccount to the list of constructors passed
// to FlockXPCOMUtils.genericModule().
flockDeliciousAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  flockDeliciousAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIBookmarkAccount
  ]
  );

/**************************************************************************
 * Flock Delicious Account: flockIWebServiceAccount Implementation
 **************************************************************************/

// readonly attribute AString urn;
flockDeliciousAccount.prototype.urn = "";

// DEFAULT: void login(in flockIListener aFlockListener);
// DEFAULT: void logout(in flockIListener aFlockListener);

// void keep();
flockDeliciousAccount.prototype.keep =
function DeliciousAcct_keep() {
  this._logger.debug("keep()");
  this.coopObj.isTransient = false;
  this._acUtils.makeTempPasswordPermanent(this.urn);
}


/**************************************************************************
 * Flock Delicious Account: flockIBookmarkAccount Implementation
 **************************************************************************/

/* No methods or properties on this interface! */


/**************************************************************************
 * XPCOM Support - Module Construction
 **************************************************************************/

// Create array of components.
var gComponentsArray = [flockDeliciousService];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      gComponentsArray);

/**************************************************************************
 * END XPCOM Support
 **************************************************************************/
