// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// 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

// Constants
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

Components.utils.import("resource:///modules/FlockStringBundleHelpers.jsm");

const EDITOR_URL = "chrome://flock/content/blog/editor.xul";
const CUSTOM_BLOG_FAVICON = "chrome://flock/skin/blog/favicon.png";

const FLOCK_BLOG_CID                = Components.ID('{70b6c92b-8f34-4d1c-9057-e03e10770b96}');

const OLD_BLOG_RDF_FILE             = "flock_blog.rdf";
const OLD_BLOG_RDF_FILE_RELIC       = "flock_blog_old.rdf";

const FLOCK_BLOG_CONTRACTID         = '@flock.com/flock-blog;1';

const FLOCK_ACCOUNTS_ROOT = "http://flock.com/rdf#AccountsRoot";
const FLOCK_DRAFTS_ROOT             = 'http://flock.com/rdf#DraftsRoot';
const FLOCK_RECOVERY_ROOT           = 'http://flock.com/rdf#RecoveryRoot';
const FLOCK_UNPUBLISHED_ROOT        = 'http://flock.com/rdf#UnpublishedRoot';

const BLOG_CONTENT_FILE      = 'blogdrafts.sqlite';

var gBlogStorage   = null;

function BlogAPIDetector() {
  this.mReq = null;
  this.mTitle = null;
}


BlogAPIDetector.prototype.getTitle =
function() {
  return this.mTitle;
}


BlogAPIDetector.prototype.doRequest =
function(listener, url, processor) {
  var inst = this;
  var rootUrl = 'http://'+url.split('/')[2];
  this.mReq = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
  this.mReq.onreadystatechange = function (aEvt) {
    try {
      if(inst.mReq.readyState == 4) {
        if(inst.mReq.status == 200) {
          // debug(inst.mReq.responseText + "\n");
          try {
            processor(listener, rootUrl, inst);
          }
          catch(e) {
            debug("Error "+e+" "+e.fileName+" line "+e.lineNumber+"\n");
            listener.onError(e);
          }
        }
        else {
          debug("\nRESPONSE\n" + inst.mReq.responseText);
          listener.onError(inst.mReq.status);
        }
      }
    }
    catch(e) {
      debug("Error "+e+" "+e.fileName+" line "+e.lineNumber+"\n");
      listener.onError(-1);
    }
  }
  this.mReq.open('GET', url, true);
  this.mReq.send(null); 
}


BlogAPIDetector.prototype.onHomePage =
function (listener, rootUrl, inst)
{
  // The following code is to clean up dirty html to make it parser
  // friendly

  var req = inst.mReq;          
  var text = req.responseText;
  text = text.replace(/[\r\n]/g,"");
       
  if(text.match(/<title.*>(.+?)</)) {
    inst.mTitle = RegExp.$1;
  }

  var linkObj = function () {
  }

  linkObj.prototype = {
    href:null,
    rel:null,
    type:null,
    title:null,
    QueryInterface: function (iid) {
      if (!iid.equals(Ci.flockIBlogLink)) 
          throw Cr.NS_ERROR_NO_INTERFACE;
      return this;
    }
  }

  var linklist = new Array();

  var R = /<link.+?>/g;
  var ar = R.exec(text);
  while(ar) {
    var cur = ar[0] + "";
    var link = new linkObj();
    if(cur.match(/href=\"(.+?)\"/)) {
      link.href = RegExp.$1;
    }
    if(cur.match(/rel=\"(.+?)\"/)) {
      link.rel = RegExp.$1;
    }
    if(cur.match(/title=\"(.+?)\"/)) {
      link.title = RegExp.$1;
    }
    if(cur.match(/type=\"(.+?)\"/)) {
      link.type = RegExp.$1;
    }
    linklist.push(link);
    ar = R.exec(text);
  }

  var blogService = Cc[FLOCK_BLOG_CONTRACTID].getService(Ci.flockIBlogService);
  var accountList = new Array();
  var webServices = blogService.services;
  while(webServices.hasMoreElements()) {
    var service = webServices.getNext();
    service.QueryInterface(Ci.flockICustomBlogWebService);
    // Clone the array because our simpleEnumerator destroy it
    var linklistClone = new Array();
    for (i = 0; i < linklist.length; i++)
      linklistClone[i] = linklist[i];
    var account = service.detectAccount(rootUrl, simpleEnumerator(linklistClone));
    if (account) {
      debug(" ==> we found support for "+service.shortName+"\n");
      accountList.push(account);
    }
    else {
      debug(" xxx "+service.shortName+" support not found for this blog\n");
    }
  }

  // Let's look in the RSD before we use something else
  var rsd = null;
  for (i = 0; i < linklist.length; i++) {
    var link = linklist[i];
    if (link.title=="RSD" || link.type=="application/rsd+xml") {
      debug("We found a RSD!\n"+link.href+"\n");
      rsd = link;
    }
  }
  if (rsd) {
    var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance (Ci.nsIXMLHttpRequest);
    xhr.open ('GET', rsd.href, true);

    xhr.onreadystatechange = function (aEvent) {
      if (xhr.readyState == 4) {
        if (xhr.status != 200) {
          // Error
          listener.onResult(null);
          return;
        }
        var dom = xhr.responseXML;
        if(!dom) {
          // FIXME: Parse responseText is responseXML is not present (= bad XML)
          //var parser = new DOMParser();
          //dom = parser.parseFromString(req.responseText,"text/xml");
          debug("No dom!!\n");
          listener.onResult(null);
          return;
        }
        var apis = dom.getElementsByTagName("api");
        var prefApi = null;
        var apiList = [];
        for(var i=0;i<apis.length;++i) {
          var api = {
            api: apis[i].getAttribute("name").toLowerCase().replace(/\s/g,""),
            apiLink: apis[i].getAttribute("apiLink"),
            preferred: apis[i].getAttribute("preferred").toLowerCase(),
            blogid: apis[i].getAttribute("blogID")
          }
          apiList.push(api);
          if (api.preferred == 'true')
            prefApi = api;
        }
        if (prefApi) {
          var svc = blogService.getAPIFromShortname(prefApi.api);
          debug("prefApi: looking for the service having shortname {"+prefApi.api+"}\n");
          if (svc) {
            listener.onResult(prefApi);
            return;
          }
        }
        while (apiList.length > 0) {
          var currentApi = apiList.shift();
          debug("looking for the service having shortname {"+currentApi.api+"}\n");
          var svc = blogService.getAPIFromShortname(currentApi.api);
          if (svc) {
            listener.onResult(currentApi);
            return;
          }
        }
        // We couldn't find any supported API in the RSD. Loop in what we had before?
        listener.onResult(null);
      }
    }
    xhr.send (null);
  }
  else { // No RSD
    if (accountList.length > 0) {
      // Return the first account in the list
      listener.onResult(accountList[0]); 
    }
    else {
      listener.onResult(null); 
    }
  }
}


BlogAPIDetector.prototype.detect =
function(listener, url)
{
  this.doRequest(listener, url, this.onHomePage);
}

function BlogDraft () {
}

BlogDraft.prototype = {
  getInterfaces: function (count) {
    var interfaceList = [Components.interfaces.flockIBlogDraft, Components.interfaces.nsIClassInfo];
    count.value = interfaceList.length;
    return interfaceList;
  },
  QueryInterface: function (iid) {
    if (!iid.equals(Components.interfaces.flockIBlogDraft))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  },
  getHelperForLanguage: function (count) {return null;}
}


function createStatement(dbconn, sql) {
  var stmt = dbconn.createStatement(sql);
  var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
    .createInstance(Ci.mozIStorageStatementWrapper);

  wrapper.initialize(stmt);
  return wrapper;
}

function BlogStorage() {
  var dbfile = Cc['@mozilla.org/file/directory_service;1']
    .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  dbfile.append(BLOG_CONTENT_FILE);

  var storageService = Cc['@mozilla.org/storage/service;1']
    .getService(Ci.mozIStorageService);
  this._DBConn = storageService.openDatabase(dbfile);

  var drafts_schema = "id INTEGER PRIMARY KEY, lastupdate INTEGER, title STRING, " +
                      "content STRING, tags STRING, blog STRING DEFAULT ''";

  var recovery_schema = 'id INTEGER PRIMARY KEY, lastupdate INTEGER, title STRING, ' +
                        'content STRING, tags STRING';

  try {
    this._DBConn.createTable('drafts', drafts_schema);
    this._DBConn.createTable('recovery', recovery_schema);
  }
  catch (e) { }

  this._getDraftsFrom = createStatement(this._DBConn,
    'SELECT * FROM drafts WHERE blog = :blog');
  this._getDraft = createStatement(this._DBConn,
    'SELECT * FROM drafts WHERE id = :id');
  this._replaceDraft = createStatement(this._DBConn,
    'REPLACE INTO drafts (id, lastupdate, title, content, tags) ' +
    'VALUES (:id, :lastupdate, :title, :content, :tags)');
  this._removeDraft = createStatement(this._DBConn,
    'DELETE FROM drafts where id = :id');
  this._setDraftBlog = createStatement(this._DBConn,
    'UPDATE drafts SET blog=:blog WHERE id=:id');

  this._replaceRecovery = createStatement(this._DBConn,
    'REPLACE INTO recovery (id, lastupdate, title, content, tags) ' +
    'VALUES (:id, :lastupdate, :title, :content, :tags)');
  this._removeRecovery = createStatement(this._DBConn,
    'DELETE FROM recovery where id = :id');
  this._getAllRecovery = createStatement(this._DBConn,
    'SELECT * FROM recovery');
  this._emptyRecovery = createStatement(this._DBConn,
    'DELETE FROM recovery');
}

BlogStorage.prototype = {
  deleteDraft: function BS_deleteDraft(id) {
    this._removeDraft.params.id = id;
    this._removeDraft.step();
    this._removeDraft.reset();
  },
  deleteRecovery: function BS_deleteRecovery(id) {
    this._removeRecovery.params.id = id;
    this._removeRecovery.step();
    this._removeRecovery.reset();
  },
  emptyRecovery: function BS_emptyRecovery() {
    this._emptyRecovery.step();
    this._emptyRecovery.reset();
  },
  saveDraft: function FS_saveDraft(id, lastupdate, title, content, tags) {
    var pp = this._replaceDraft.params;
    pp.id = id;
    pp.lastupdate = lastupdate;
    pp.title = title;
    pp.content = content;
    pp.tags = tags;
    this._replaceDraft.step();
    this._replaceDraft.reset();
  },
  setDraftBlog: function FS_setDraftBlog(id, blog) {
    var pp = this._setDraftBlog.params;
    pp.id = id;
    pp.blog = blog;
    this._setDraftBlog.step();
    this._setDraftBlog.reset();
  },
  saveRecovery: function FS_saveRecovery(id, lastupdate, title, content, tags) {
    var pp = this._replaceRecovery.params;
    pp.id = id;
    pp.lastupdate = lastupdate;
    pp.title = title;
    pp.content = content;
    pp.tags = tags;
    this._replaceRecovery.step();
    this._replaceRecovery.reset();
  },
  getDraftsFrom: function FS_getDraftsFrom(blog) {
    var result = [];
    this._getDraftsFrom.reset();
    var pp = this._getDraftsFrom.params;
  
    pp.blog = blog;
    while (this._getDraftsFrom.step()) {
      var draft = new BlogDraft();
      draft.id = this._getDraftsFrom.row['id'];
      draft.lastupdate = this._getDraftsFrom.row['lastupdate'];
      draft.title = this._getDraftsFrom.row['title'];
      draft.content = this._getDraftsFrom.row['content'];
      draft.tags = this._getDraftsFrom.row['tags'];
      result.push(draft);
    }
    this._getDraftsFrom.reset();

    return result;
  },
  getAllRecovery: function FS_getAllRecovery() {
    var result = [];
    this._getAllRecovery.reset();
    while (this._getAllRecovery.step()) {
      var recovery = new BlogDraft();
      recovery.id = this._getAllRecovery.row['id'];
      recovery.lastupdate = this._getAllRecovery.row['lastupdate'];
      recovery.title = this._getAllRecovery.row['title'];
      recovery.content = this._getAllRecovery.row['content'];
      recovery.tags = this._getAllRecovery.row['tags'];
      result.push(recovery);
    }
    this._getAllRecovery.reset();

    return result;
  },
  getDraft: function FS_getDraft(draftid) {
    var draft = null;

    this._getDraft.reset();
    this._getDraft.params.id = draftid;

    if (this._getDraft.step()) {
      draft = new BlogDraft();
      draft.id = this._getDraft.row['id'];
      draft.lastupdate = this._getDraft.row['lastupdate'];
      draft.title = this._getDraft.row['title'];
      draft.content = this._getDraft.row['content'];
      draft.tags = this._getDraft.row['tags'];
    }
    
    this._getDraft.reset();
    return draft;
  },
  beginTransaction: function FS_beginTransation() {
    this._DBConn.beginTransaction();
  },
  commitTransaction: function FS_commitTransation() {
    this._DBConn.commitTransaction();
  },
  rollbackTransaction: function FS_rollbackTransation() {
    this._DBConn.rollbackTransaction();
  },
  _getData: function FS__getData(id, stmt, field) {
    stmt.reset();
    stmt.params.id = id;

    var data = null;
    if (stmt.step())
      data = stmt.row[field];
    stmt.reset();
    return data;
  }
}


function flockBlogService() {
  // For an observer on flock-data-ready for import
  var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
  obs.addObserver(this, 'flock-data-ready', false);

  this.acUtils = Cc["@flock.com/account-utils;1"].getService(Ci.flockIAccountUtils);

  this.initialized = false;
}


flockBlogService.prototype.init =
function()
{
  if (this.initialized)
    return;

  gBlogStorage = new BlogStorage();

  this._coop = Cc["@flock.com/singleton;1"]
               .getService(Ci.flockISingleton)
               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
               .wrappedJSObject;

  this.mName2ServiceMap = {};
  this.mService2NameMap = {};

  // Logger
  this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this.logger.init("blogservice");

  // Add Technorati.com on first run
  var prefsService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
  var blogBranch = prefsService.getBranch("flock.blog");
  try {
    var firstRun = blogBranch.getIntPref("firstRun");
  } catch(e) {}
  if (!firstRun) {
    // TODO
    //this.addNotification("http://www.technorati.com")
    //blogBranch.setIntPref("firstRun", 1);
    //blogBranch.setBoolPref("notify", true);
  }

  // Search for registered blog web services
  var catmgr = Cc["@mozilla.org/categorymanager;1"]
      .getService (Ci.nsICategoryManager);
  var enum_ = catmgr.enumerateCategory("flockICustomBlogWebService");
  while(enum_.hasMoreElements()) {
    var obj = enum_.getNext();
    supportsString = obj.QueryInterface(Ci.nsISupportsCString);
    var shortName = supportsString.toString();
    var cid = catmgr.getCategoryEntry("flockICustomBlogWebService", shortName); 
    var svc = Cc[cid].getService(Ci.flockICustomBlogWebService);
    this.registerAPI(svc, shortName);
  }

  this.initialized = true;
}

//flockIMigratable
flockBlogService.prototype.__defineGetter__("migrationName",
function flockBlogService_getter_migrationName() {
  return flockGetString("common/migrationNames", "migration.name.blog");
});

flockBlogService.prototype.needsMigration =
function (oldVersion)
{
  if (oldVersion.substr(0, 3) == "0.7") { // migration from Cardinal (0.7.x)
    var oldBlogFile = Cc["@mozilla.org/file/directory_service;1"]
                      .getService(Ci.nsIProperties)
                      .get("ProfD", Ci.nsIFile);
    oldBlogFile.append(OLD_BLOG_RDF_FILE);

    // only migrate if the old blog rdf file exists
    if(oldBlogFile.exists()) {
      return true;
    }
    return false;
  }

  if (oldVersion.substr(0, 5) == "0.9.0") {
    // Migrate posts content from RDF to Mozstorage
    return true;
  }

  if ((oldVersion.substr(0, 2) == "0.") || (oldVersion.substr(0, 3) == "1.0")) {
    // Bug 10685: Fix the favicon for custom blogs
    return true;
  }

  return false;
}

flockBlogService.prototype.startMigration = 
function(oldVersion, aFlockMigrationProgressListener)
{
  this.init();

  var oldBlogFile = Cc["@mozilla.org/file/directory_service;1"] 
                       .getService(Ci.nsIProperties)
                       .get("ProfD", Ci.nsIFile);
  oldBlogFile.append(OLD_BLOG_RDF_FILE);

  var ctxt = {
    oldVersion: oldVersion,
    listener: aFlockMigrationProgressListener,
    oldBlogFile: oldBlogFile
  };

  ctxt.listener.onUpdate(0, 'Migrating blog settings');

  return { wrappedJSObject: ctxt };
}

flockBlogService.prototype.finishMigration = 
function(ctxtWrapper)
{
}

flockBlogService.prototype.doMigrationWork =
function(ctxtWrapper)
{
  var ctxt = ctxtWrapper.wrappedJSObject;

  if (ctxt.oldVersion.substr(0, 3) == "0.7") {
    // migration from direct RDF to Coop
    if (!ctxt.oldBlogFile.exists()) {
      return false;
    }
    if (!ctxt.blogMigrator) {
      ctxt.blogMigrator = this._migrateFromCardinal(ctxt);
    }
    if (ctxt.blogMigrator.next()) {
      ctxt.blogMigrator = null;
    }
    return Boolean(ctxt.blogMigrator);
  }

  if (ctxt.oldVersion.substr(0, 5) == "0.9.0") {
    // Migrate posts content from RDF to Mozstorage
    if (!ctxt.blogMigrator) {
      ctxt.blogMigrator = this._migrateFromCormorant(ctxt);
    }
    if (ctxt.blogMigrator.next()) {
      ctxt.blogMigrator = null;
    }
    // Bug 10685: Fix the favicon for custom blogs
    this._fixCustomBlogsFavicon(ctxt);
    return Boolean(ctxt.blogMigrator);
  }

  if ((ctxt.oldVersion.substr(0, 2) == "0.")
   || (ctxt.oldVersion.substr(0, 3) == "1.0"))
  {
    // Bug 10685: Fix the favicon for custom blogs
    this._fixCustomBlogsFavicon(ctxt);
    return false;
  }

  return false;
}

// Migrate from 0.7.x (Cardinal)
flockBlogService.prototype._migrateFromCardinal =
function blogService__migrateFromCardinal (ctxt)
{
  var blogsvc = this;
  var acUtils = Cc["@flock.com/account-utils;1"]
                .getService(Ci.flockIAccountUtils);

  function createAccountAndBlog (aServiceId,
                                 aServiceUrn,
                                 aUsername,
                                 aPassword,
                                 aBlogName,
                                 aBlogid,
                                 aUrl,
                                 aApilink)
  {
    // We create the blog "manually" instead of calling the appropriate
    // function on the service because that function would make a call to
    // the server to get the blogs list - but we already have all that
    // information
    var svc = Cc[aServiceId].getService(Ci.flockIWebService);
    svc.init();
    acUtils.setPassword(svc.urn+":"+aUsername, aUsername, aPassword);

    var accountURN = svc.urn+":"+aUsername;
    var account = new blogsvc._coop.Account(accountURN, {
      name: aUsername,
      serviceId: aServiceId,
      service: blogsvc._coop.get(aServiceUrn),
      accountId: aUsername,
      favicon: svc.icon,
      URL: svc.url
    });
    blogsvc._coop.accounts_root.children.addOnce(account);

    var theCoopBlog = new blogsvc._coop.Blog(accountURN+":"+aBlogid, {
      name: aBlogName,
      title: aBlogName,
      blogid: aBlogid,
      URL: aUrl,
      apiLink: aApilink
    });
    account.children.addOnce(theCoopBlog);
  }

  var rdfs = Cc["@mozilla.org/rdf/rdf-service;1"].getService (Ci.nsIRDFService);
  var ios = Cc['@mozilla.org/network/io-service;1'].getService (Ci.nsIIOService);
  var ds = rdfs.GetDataSourceBlocking(ios.newFileURI(ctxt.oldBlogFile).spec);

  var accountsRes = rdfs.GetResource("urn:flock:blog:settings");
  var container = Cc["@mozilla.org/rdf/container;1"].createInstance(Ci.nsIRDFContainer);
  container.Init(ds, accountsRes);

  var children = container.GetElements();
  this.logger.info("Start migration of the blog accounts");
  while (children.hasMoreElements()){
    var child = children.getNext().QueryInterface(Ci.nsIRDFResource);
    var apiRes = rdfs.GetResource(child.ValueUTF8 + ":api");

    var nameRes = rdfs.GetResource("http://www.flock.com/rdf#title");
    var name = ds.GetTarget(child, nameRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;

    this.logger.info("Found the blog: "+name+" for import");
    ctxt.listener.onUpdate(0, "Migrating " + name);
    yield false;

    var urlRes = rdfs.GetResource("http://www.flock.com/rdf#url");
    var url = ds.GetTarget(child, urlRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;

    var blogidRes = rdfs.GetResource("http://www.flock.com/rdf#blogid");
    var blogid = ds.GetTarget(apiRes, blogidRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;

    var apilinkRes = rdfs.GetResource("http://www.flock.com/rdf#apiLink");
    var apilink = ds.GetTarget(apiRes, apilinkRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;

    // Get the username/password...
    var usernameRes = rdfs.GetResource("http://www.flock.com/rdf#username");
    var username = ds.GetTarget(child, usernameRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;
    var password = "";
    this.logger.debug("    Username = "+username);
    if (username != "") {
      var pwhostRes = rdfs.GetResource("http://www.flock.com/rdf#pwhost");
      var pwhost = ds.GetTarget(child, pwhostRes, true).QueryInterface(Ci.nsIRDFLiteral).Value;

      var password = getPassword(pwhost, username)
    }
    if (password == null || password == "") {
      // The password was not stored or we couldn't find it
      this.logger.info("    The password seems to be empty/unknown, let's ask the user");
      var promptService = Cc['@mozilla.org/embedcomp/prompt-service;1'].getService(Ci.nsIPromptService);
      var winMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);

      var theUsername = {value: username};
      var thePassword = {value: password};
      promptService.promptUsernameAndPassword (
          winMediator.getMostRecentWindow('navigator:browser'),
          "Blog Account Import",
          "Please enter your login information for "+name+"\n("+url+")",
          theUsername, thePassword,
          null, {value: false}
      );
      username = theUsername.value;
      password = thePassword.value;
    }

    // The user pressed cancel or gave a blank password => don't add the blog
    if (password == null || password == "") {
      break;
    }

    // Now we can create the blog
    if (url.match(/wordpress\.com/)) {
      createAccountAndBlog ("@flock.com/people/wordpress;1",
                            "urn:wordpress:service",
                            username,
                            password,
                            name,
                            blogid,
                            url,
                            apilink);
    }
    else if (url.match(/livejournal\.com/)) {
      apilink = "http://www.livejournal.com/interface/xmlrpc";
      createAccountAndBlog ("@flock.com/people/livejournal;1",
                            "urn:livejournal:service",
                            username,
                            password,
                            name,
                            blogid,
                            url,
                            apilink);
    }
    else if (url.match(/blogspot\.com/)) {
      createAccountAndBlog ("@flock.com/people/blogger;1",
                            "urn:blogger:service",
                            username,
                            password,
                            name,
                            name,
                            url,
                            apilink);
    }
    else if (url.match(/typepad\.com/)) {
      createAccountAndBlog ("@flock.com/blog/typepad;1",
                            "urn:typepad:service",
                            username,
                            password,
                            name,
                            blogid,
                            url,
                            apilink);
    }
    else { // Custom blog
      try {
        var apiRes = rdfs.GetResource("http://www.flock.com/rdf#api");
        var api = ds.GetTarget(child, apiRes, true).QueryInterface(Ci.nsIRDFResource);
        var apinameRes = rdfs.GetResource("http://www.flock.com/rdf#apiname");
        var apiname = ds.GetTarget(api, apinameRes, true).QueryInterface(Ci.nsIRDFLiteral);
        var blogidRes = rdfs.GetResource("http://www.flock.com/rdf#blogid");
        var blogid = ds.GetTarget(api, blogidRes, true).QueryInterface(Ci.nsIRDFLiteral);
        var apiLinkRes = rdfs.GetResource("http://www.flock.com/rdf#apiLink");
        var apiLink = ds.GetTarget(api, apiLinkRes, true).QueryInterface(Ci.nsIRDFLiteral);

        // Create a custom account...
        var customURN = "urn:flock:customblog:"+name;
        if (!this._coop.Account.exists (customURN)) {
          var custom = new this._coop.Account(customURN, {
            name : name,
            accountId : username,
            serviceId: '@flock.com/blog/service/'+apiname.Value+';1',
            URL : url,
            favicon: CUSTOM_BLOG_FAVICON
          });
          var root = this._coop.get("http://flock.com/rdf#AccountsRoot");
          root.children.add(custom);
        }

        // ...save the password in the password manager...
        var blogURN = customURN+":"+name;
        var acUtils = Cc["@flock.com/account-utils;1"].getService(Ci.flockIAccountUtils);
        acUtils.setPassword(customURN, username, password);
        // ...and create a new blog as a child
        theCoopBlog = new this._coop.Blog(blogURN, {
          name: name,
          title: name,
          blogid: blogid.Value,
          URL: url,
          apiLink: apiLink.Value
        });
        custom.children.addOnce(theCoopBlog);
      }
      catch(e) {
        debug(e);
      }
    }
  }

  // Import blog posts
  var prefs = Cc["@mozilla.org/preferences-service;1"]
              .getService(Ci.nsIPrefService)
              .getBranch("flock.blog.");
  var init_dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);

  try {
    var value = prefs.getComplexValue("saveLocation", Ci.nsISupportsString).data;
  } catch(e) {}

    if (value) {

    init_dir.initWithPath(value);
    var importDraftsFolderId;
    var myenum = init_dir.directoryEntries;
    while (myenum.hasMoreElements()) {
      var file = myenum.getNext().QueryInterface(Ci.nsILocalFile);

      try {
        // Get the content of the file
        var instream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);    
        var scriptinstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
        instream.init(file, 0x01, 00400 | 00200 | 00040 | 00004, 0);
        scriptinstream.init(instream);
        var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
        converter.charset = "UTF-8";
        var result = scriptinstream.read(scriptinstream.available());
        var finalresult; // Mark is getting an exception around here
        finalresult = converter.ConvertToUnicode(result);
        finalresult += converter.Finish();
        scriptinstream.close();

        // Separate the title from the content
        var theTitle = finalresult.substring(0, finalresult.indexOf('\n'));
        theTitle = theTitle.replace(/<h1>/, '').replace(/<\/h1>/, '');
        var theBody = finalresult.substring(finalresult.indexOf('\n')+1, finalresult.length);

        // Add to the drafts
        var newId = this.saveDraft(null, theTitle, theBody, "");
        this.moveTo(newId, "imported");
      }
      catch(e) {
        // TODO: give feedback to the user, saying we failed to import one draft
        debug("**** Error while importing the blog draft: "+file.path+". I will just ignore this draft and go on.\n\n"+e+"\n");
      }
    }
  }

  // rename the file and delete the old copy
  rdfs.UnregisterDataSource(ds);
  ds = null;
  
  ctxt.oldBlogFile.moveTo(null, OLD_BLOG_RDF_FILE_RELIC);
  yield true;
}


// Migrate drafts from RDF to mozstorage (From 0.9.0)
flockBlogService.prototype._migrateFromCormorant =
function blogService__migrateFromCormorant(ctxt) {
  var draftsRoot = this._coop.get(FLOCK_DRAFTS_ROOT);
  if (draftsRoot) {
    // Migrate from RDF to mozstorage (0.9.0 => 0.9.1)
    var folders = draftsRoot.children.enumerate();
    while (folders.hasMoreElements()) {
      var folder = folders.getNext();
      var blogid = "";
      if (!folder.id().match('UnpublishedRoot')) {
        blogid = folder.id().split(":folder:").pop();
      }
      var drafts = folder.children.enumerate();
      while (drafts.hasMoreElements()) {
        var draft = drafts.getNext();
        // Add the draft to BlogStorage
        var draftid = this.saveDraft(null, draft.name, draft.content, draft.tags);
        this.moveTo(draftid, blogid);
        // Then remove it from RDF
        folder.children.remove(draft);
        draft.destroy();
      }
      draftsRoot.children.remove(folder);
      folder.destroy();
    }
    draftsRoot.destroy();
  }
  yield true;
}


// At migration: fix any custom blog favicon
flockBlogService.prototype._fixCustomBlogsFavicon =
function blogService__fixCustomBlogsFavicon (ctxt)
{
  // We can't use acUtils.getAccountsByInterface because the custom
  // blog services (MovableType, metaWeblog...) don't implement
  // flockIWebService.getAccount() properly
  var acctRoot = this._coop.get(FLOCK_ACCOUNTS_ROOT);
  var coopAccounts = acctRoot.children.enumerate();
  while (coopAccounts.hasMoreElements()) {
    var coopAccount = coopAccounts.getNext();
    if (!coopAccount) {
      // getNext() can return NULL when hasMoreElements() is TRUE.
      continue;
    }
    if (Components.classes[coopAccount.serviceId]) {
      var svc = Components.classes[coopAccount.serviceId]
                          .getService(Components.interfaces.flockIWebService);
      if (svc instanceof Ci.flockICustomBlogWebService) {
        this.logger.info("Update the favicon for " + coopAccount.id());
        coopAccount.favicon = CUSTOM_BLOG_FAVICON;
      }
    }
  }
}

// nsIObserver
flockBlogService.prototype.observe =
function(subject, topic, state)
{
  switch (topic) {
    case 'flock-data-ready':
      var obs = Cc["@mozilla.org/observer-service;1"]
        .getService(Ci.nsIObserverService);
      obs.removeObserver(this, 'flock-data-ready');
      this.init();
      return;
  }
}


// nsISimpleEnumerator implementation
function simpleEnumerator (aArray)
{
  aArray.hasMoreElements = function () { 
    return this.length != 0; 
  }
  aArray.getNext = function () { 
    var rval = this.shift (); 
    return rval;
  }
  return aArray;
}

// nsIStringEnumerator implementation
function stringEnumerator (aArray)
{
  aArray.hasMore = function () { 
    return this.length != 0; 
  }
  aArray.getNext = function () { 
    var rval = this.shift (); 
    return rval;
  }
  return aArray;
}

loadLibraryFromSpec('chrome://browser/content/flock/contrib/rdfds.js');

function loadLibraryFromSpec(aSpec)
{
  var loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
    .getService(Ci.mozIJSSubScriptLoader);

  loader.loadSubScript(aSpec);
}


function getPassword(aHost, aUsername)
{
  var psm = Cc["@mozilla.org/passwordmanager;1"].getService(Ci.nsIPasswordManager);
  var myenum = psm.enumerator;
  while (myenum.hasMoreElements()) {
    var pw = myenum.getNext();
    pw = pw.QueryInterface(Ci.nsIPassword);
    if(pw.host==aHost && aUsername==pw.user) {
      return pw.password;
    }
  }
  return null;
}

// Ensure that the "recent" list as no more than BLOG_RECENT_COUNT elements
flockBlogService.prototype.cleanRecent = function () {
  debug("*** flockBlogService.cleanRecent: NOT IMPLEMENTED. DO IT NOW!! ***\n");
}

// the flockIBlogService implementation

flockBlogService.prototype.accountExists = function (aTitle) { 
  var accounts = this._coop.Blog.find({ title: aTitle });
  return (accounts.length > 0); 
}


flockBlogService.prototype.accountCount = function () {
  var count = 0;
  var accounts = this._coop.get('http://flock.com/rdf#AccountsRoot');
  var myenum = accounts.children.enumerate();
  while (myenum.hasMoreElements()) {
    var account = myenum.getNext();
    if (!account) continue; // getNext() can return NULL when hasMoreElements() is TRUE.
    var enum2 = account.children.enumerate();
    while (enum2.hasMoreElements()) {
      var blog = enum2.getNext();
      if (blog && blog.isInstanceOf(this._coop.Blog)) {
        ++count;
      }
    }
  }
  return count;
}


flockBlogService.prototype.getAccountList = function () {
  var result = new Array();

  var accounts = this._coop.get('http://flock.com/rdf#AccountsRoot');
  var myenum = accounts.children.enumerate();
  while (myenum.hasMoreElements()) {
    var account = myenum.getNext();
    if (!account) continue; // getNext() can return NULL when hasMoreElements() is TRUE.
    var enum2 = account.children.enumerate();
    while (enum2.hasMoreElements()) {
      var child = enum2.getNext();
      if (child.isInstanceOf(this._coop.Blog))
        result.push(child);
    }
  }

  return simpleEnumerator(result);
}


flockBlogService.prototype.getDefaultBlog = function () { 
  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefService)
                .getBranch("flock.blog.");
  var value = null;
  try{
    value = prefs.getCharPref ("defaultblog");
  } catch(e) {
    return "";
  }
  return value;
}


flockBlogService.prototype.setDefaultBlog = function (aValue) { 
  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefService)
                .getBranch("flock.blog.");
  prefs.setCharPref ("defaultblog", aValue);
}



flockBlogService.prototype.addRecent = function (aName, aFile) { 
  debug("*** flockBlogService.addRecent: NOT IMPLEMENTED. DO IT NOW!! ***\n");
}


flockBlogService.prototype.recentIsEmpty =
function ()
{ 
  debug("*** flockBlogService.recentIsEmpty: NOT IMPLEMENTED. DO IT NOW!! ***\n");
}


flockBlogService.prototype.registerAPI =
function(aService, aShortname)
{
  this.mName2ServiceMap[aShortname] = aService;
  this.mService2NameMap[aService] = aShortname;
}


flockBlogService.prototype.getAPIFromShortname =
function(aShortname)
{
  for (i in this.mName2ServiceMap) {
    debug(i+": "+this.mName2ServiceMap[i]+"\n");
  }
  return this.mName2ServiceMap[aShortname];
}


flockBlogService.prototype.getShortnameFromAPI =
function(aService)
{
  return this.mService2NameMap[aService];
}


flockBlogService.prototype.__defineGetter__('services', function ()
{
  var ar = new Array();
  for(var p in this.mName2ServiceMap) {
    ar.push(this.mName2ServiceMap[p]);
  }
  var rval = {
    getNext: function() {
      var rval = ar.shift();
      return rval;
    },
    hasMoreElements: function() {
      return (ar.length>0);
    }
  }
  return rval;
})


flockBlogService.prototype.getCandidatesAPI =
function(aListener, aUrl)
{
  var detector = new BlogAPIDetector();
  detector.detect(aListener, aUrl);
}


flockBlogService.prototype.saveAccount =
function (aParentId, aId, aBlogAccount)
{
  // Get or create the coop object
  var account = null;
  if (aId) {
    account = this._coop.get(aId);
    if (!account)
      account = new this._coop.Blog(aId);
  }
  else
    account = new this._coop.Blog(aParentId+":"+aBlogAccount.username);

  // Save the password in the password manager
  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  var uri = ios.newURI(aBlogAccount.URL, null, null);
  var pwhost = "flockinternal." + uri.host;
  if(aBlogAccount.username && aBlogAccount.username.length) {
    var psm = Cc["@mozilla.org/passwordmanager;1"].getService(Ci.nsIPasswordManager);
    psm.addUser(pwhost, aBlogAccount.username, aBlogAccount.password);
  }

  // Save the rest in flock-data
  account.title = aBlogAccount.title;
  account.name = aBlogAccount.title;
  account.favicon = aBlogAccount.favicon;
  account.username = aBlogAccount.username;
  account.URL = aBlogAccount.URL;
  account.pwhost = pwhost;
  account.api = aBlogAccount.api;
  account.apiLink = aBlogAccount.apiLink;
  account.blogid = aBlogAccount.blogid;
  account.authtoken = aBlogAccount.authtoken;

  var parent = this._coop.get(aParentId);
  parent.children.add(account);
}
 
flockBlogService.prototype.getAccount = function (aBlogID) {
  if(!aBlogID) throw new Error("Error in BlogSettings.getAccount()");
  var coopblog = this._coop.get(aBlogID);
  var coopaccount = coopblog.getParent();

  if (coopaccount) {
    var key;
    var coopservice = coopaccount.service;
    if (coopservice) {
      // Service (Blogger, Typepad, Wordpress.com...)
      key = coopservice.id() + ":" + coopaccount.accountId;
    } else {
      // Self hosted
      key = coopaccount.id();
    }
    var pw = this.acUtils.getPassword(key);
    if (pw) {
      var password = pw.password;
    }
  }

  var account = {
    blogid: coopblog.blogid,
    username: coopaccount.accountId,
    password: password,
    authtoken: coopblog.authtoken,
    apiLink: coopblog.apiLink,
    getInterfaces: function (count) {
      var interfaceList = [Ci.flockIBlogAccount, Ci.nsIClassInfo];
      count.value = interfaceList.length;
      return interfaceList;
    },
    QueryInterface: function (iid) {
      if (!iid.equals(Ci.flockIBlogAccount))
        throw Cr.NS_ERROR_NO_INTERFACE;
      return this;
    }
  }

  return account;
};


flockBlogService.prototype.getBlogAPI = function (aBlogID) {
  try {
    var account = this._coop.get(aBlogID);

    return this.getAPIFromShortname(account.api);
  }
  catch(e) {
    debug("Caught exception: "+e+" "+e.fileName+" "+e.lineNumber+"\n");
    return null;
  }
};

flockBlogService.prototype.addCategory = function (aBlogID, aCategoryId, aCategoryName) {
  var account = this._coop.get(aBlogID);
  var myenum = account.categories.enumerate();
  var category = null;
  while (myenum.hasMoreElements()) {
    var cat = myenum.getNext();
    if (cat.categoryId == aCategoryId)
      category = cat;
  }
  if (!category) {
    category = new this._coop.BlogCategory();
    category.categoryId = aCategoryId;
  }
  category.name = aCategoryName;

  account.categories.addOnce(category);
};

flockBlogService.prototype.flushCategories = function (aBlogAccount) {
  debug("*** flockBlogService.flushCategories: DEPRECATED. DON'T USE IT!! ***\n");
};

flockBlogService.prototype.addNotification = function (aURL) {
  debug("*** flockBlogService.addNotification: NOT IMPLEMENTED. DO IT NOW!! ***\n");
};

flockBlogService.prototype.removeNotification = function (aURL) {
  debug("*** flockBlogService.removeNotification: NOT IMPLEMENTED. DO IT NOW!! ***\n");
};


flockBlogService.prototype.getNotifications = function () {
  debug("*** flockBlogService.getNotifications: NOT IMPLEMENTED. DO IT NOW!! ***\n");
  return stringEnumerator([]);
};


flockBlogService.prototype._openBlogEditor =
function (aParams) {
  // Recover if needed
  var needRecovery = false;
  if (!this.editorIsOpen()) {
    var _enum = this.getAllRecovery();
    if (_enum.hasMoreElements()) // There is at least one recovered draft
      needRecovery = true;
  }
 
  if (needRecovery) {
    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
    var win = wm.getMostRecentWindow('navigator:browser');
    win.openDialog("chrome://flock/content/blog/blogRecovery.xul",
                   "openRecovery", "chrome,modal,centerscreen", null);
  }
  var ww = Cc['@mozilla.org/embedcomp/window-watcher;1'].getService(Ci.nsIWindowWatcher);
  return ww.openWindow(null, EDITOR_URL, "_blank", "chrome,close,titlebar,resizable=yes,toolbar=no,dialog=no,scrollbars=yes", aParams);
}


flockBlogService.prototype.openPost =
function (aId) {
  var windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
    .getService(Components.interfaces.nsIWindowMediator);

  // See if the post is not already open
  var oldWin = null;
  
  var singleUploadWindow = false;
  var allWindows = windowMediator.getEnumerator(null);
  debug("Looking for {"+aId+"}\n");
  while (allWindows.hasMoreElements() && !oldWin) {
    var win = allWindows.getNext();
    if (win.gDraftId && (win.gDraftId == aId)) {
      oldWin = win;
    }
  }

  if (oldWin) { // Bring the existing window to the front
    oldWin.focus();
  }
  else { // Open a new window
    var params = Cc["@mozilla.org/embedcomp/dialogparam;1"].createInstance(Ci.nsIDialogParamBlock);
    params.SetNumberStrings(4);
    params.SetString(0, aId);
    params.SetString(1, "");
    params.SetString(2, "");
    params.SetString(3, "");

    this._openBlogEditor(params);
  }
}


flockBlogService.prototype.openEditor =
function (aTitle, aContent, aTags) {
  var setup = 1; // Default to "Cancel"

  if (this.accountCount() == 0) {
    // Tell the user to configure an account
    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
    var win = wm.getMostRecentWindow('navigator:browser');
    var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"]
             .getService(Ci.nsIPromptService);
    var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
              .getService(Ci.nsIStringBundleService);
    var sb = sbs.createBundle("chrome://flock/locale/blog/blog.properties");
    var brand = sbs.createBundle("chrome://branding/locale/brand.properties");

    var flags = Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
                Ci.nsIPromptService.BUTTON_POS_0 +
                Ci.nsIPromptService.BUTTON_TITLE_CANCEL *
                Ci.nsIPromptService.BUTTON_POS_1 +
                Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
                Ci.nsIPromptService.BUTTON_POS_2;

    var title = sb.GetStringFromName("flock.blog.account.req.title");
    var button1 = sb.GetStringFromName("flock.blog.account.req.setupButton");
    var appName = brand.GetStringFromName("brandShortName");
    var button3 = sb.GetStringFromName("flock.blog.account.req.continueButton");
    var substitutions = [button1, appName, button3];
    var bodyText = sb.formatStringFromName("flock.blog.account.req.text",
                                           substitutions,
                                           substitutions.length);

    setup = ps.confirmEx(win,
                         title,
                         bodyText,
                         flags,
                         button1,
                         null,
                         button3,
                         null,
                         {});
  }
  else {
    setup = 2;
  }

  switch (setup) {
  case 0:
    // "Setup Account"
    win.flock_blogLaunchSettings();
    break;
  case 2:
    // "Continue"
    //dump("aContent = "+aContent+"\n");
    var params = Cc["@mozilla.org/embedcomp/dialogparam;1"].createInstance(Ci.nsIDialogParamBlock);
    params.SetNumberStrings(4);
    params.SetString(0, "");
    params.SetString(1, aTitle?aTitle:"");
    params.SetString(2, aContent?aContent:"");
    params.SetString(3, aTags?aTags:"");
    return this._openBlogEditor(params);
  default:
    // Cancel
  }
  return null;
}


flockBlogService.prototype.editorIsOpen =
function () {
  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  return (!!wm.getMostRecentWindow("Flock:BlogEditor"));
}

flockBlogService.prototype._save =
function (aId, aTitle, aContent, aTags, aRecovery) {
  var now = (new Date()).getTime();
  var id = (aId && aId > 0)?aId:(now * 100 + Math.floor(Math.random() * 100));

  if (aRecovery) {
    gBlogStorage.saveRecovery(id, now, aTitle, aContent, aTags);
  } else {
    gBlogStorage.saveDraft(id, now, aTitle, aContent, aTags);
  }
  return id;
}

flockBlogService.prototype.saveDraft =
function (aId, aTitle, aContent, aTags) {
  return this._save(aId, aTitle, aContent, aTags, false);
}

flockBlogService.prototype.saveRecovery =
function (aId, aTitle, aContent, aTags) {
  return this._save(aId, aTitle, aContent, aTags, true);
}

flockBlogService.prototype.getDraft =
function flockBlogService_getDraft (aDraftId) {
  return gBlogStorage.getDraft(aDraftId);
}

flockBlogService.prototype.getDraftsFrom =
function flockBlogService_getDraftsFrom (aBlogId) {
  var drafts = gBlogStorage.getDraftsFrom (aBlogId);
  return simpleEnumerator(drafts);
}

flockBlogService.prototype.moveTo =
function flockBlogService_moveTo (aId, aBlogId) {
  gBlogStorage.setDraftBlog(aId, aBlogId);
}


flockBlogService.prototype.removeDraft =
function (aId) {
  gBlogStorage.deleteDraft(aId);
}


flockBlogService.prototype.removeRecovery =
function (aId) {
  gBlogStorage.deleteRecovery(aId);
}


flockBlogService.prototype.looseRecovery =
function () {
  gBlogStorage.emptyRecovery();
}

flockBlogService.prototype.getAllRecovery =
function flockBlogService_getAllRecovery () {
  var recoveries = gBlogStorage.getAllRecovery ();
  return simpleEnumerator(recoveries);
}


flockBlogService.prototype.flags = Ci.nsIClassInfo.SINGLETON;
flockBlogService.prototype.classDescription = "Flock Blog Service";
flockBlogService.prototype.getInterfaces = function (count) {
    var interfaceList = [Ci.flockIBlogService, Ci.flockIMigratable,
                         Ci.nsIObserver, Ci.nsIClassInfo];
    count.value = interfaceList.length;
    return interfaceList;
}
flockBlogService.prototype.getHelperForLanguage = function (count) {return null;}

// the nsISupports implementation
flockBlogService.prototype.QueryInterface =
function (iid) {
    if (!iid.equals(Ci.flockIBlogService) && 
        !iid.equals(Ci.flockIMigratable) &&
        !iid.equals(Ci.nsIClassInfo) &&
        !iid.equals(Ci.nsIObserver) &&
        !iid.equals(Ci.nsISupports))
        throw Cr.NS_ERROR_NO_INTERFACE;
    return this;
}

// Module implementation
var BlogModule = new Object();

BlogModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
    compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);

    compMgr.registerFactoryLocation(FLOCK_BLOG_CID, 
                                    "Flock Blog JS Component",
                                    FLOCK_BLOG_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);

    // Make the blog service a startup observer (for migration)
    var categoryManager = Cc["@mozilla.org/categorymanager;1"]
      .getService(Ci.nsICategoryManager);
    categoryManager.addCategoryEntry("flock-startup", "Flock Blog Service", "service," + FLOCK_BLOG_CONTRACTID, true, true);
    categoryManager.addCategoryEntry('flockMigratable', 'blogservice', FLOCK_BLOG_CONTRACTID, true, true);
}

BlogModule.getClassObject =
function (compMgr, cid, iid) {
    if (!cid.equals(FLOCK_BLOG_CID))
        throw Cr.NS_ERROR_NO_INTERFACE;
    
    if (!iid.equals(Ci.nsIFactory))
        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    
    return BlogServiceFactory;
}

BlogModule.canUnload =
function(compMgr)
{
    return true;
}
    
/* factory object */
var BlogServiceFactory = new Object();

BlogServiceFactory.createInstance =
function (outer, iid) {
    if (outer != null)
        throw Cr.NS_ERROR_NO_AGGREGATION;

    return (new flockBlogService()).QueryInterface(iid);
}

/* entrypoint */
function NSGetModule(compMgr, fileSpec) {
    return BlogModule;
}
