// 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/FlockPrefsUtils.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockMediaUtils.jsm");

const MODULE_NAME = "Revver";
const CLASS_NAME = "Flock Revver Service";
const CLASS_SHORT_NAME = "revver";
const CLASS_TITLE = "Revver";
const CLASS_ID = Components.ID("{1de18379-1b18-424c-80c8-3b3ded5f3fe1}");
const CONTRACT_ID = "@flock.com/webservice/revver;1";
const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
// From nsIXMLHttpRequest.idl
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
const HTTP_CODE_OK = 200;
const HTTP_CODE_FOUND = 302;

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

const FAVICON = "chrome://flock/content/services/revver/favicon.png";

const REVVER_API_URL = "https://api.revver.com/json/1.0/";
const REVVER_AFFILIATE_CODE = 223251;

const FEATURED_COLLECTION_ID = 7620002;
const MOST_VIEWED_COLLECTION_ID = 366;
const MOST_RECENT_COLLECTION_ID = 313164;
const PICKS_COLLECTION_ID = 593;

const REVVER_PROPERTIES = "chrome://flock/locale/services/revver.properties";

var gApi = null;

/*************************************************************************
 * Component: RevverAPI
 *************************************************************************/
function RevverAPI() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("RevverAPI");

  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  this._logger.debug("constructor");
}


/*************************************************************************
 * RevverAPI: XPCOM Component Creation
 *************************************************************************/
// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add this component to the list of constructors passed to
// FlockXPCOMUtils.genericModule().
RevverAPI.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " API",
  "",
  "",
  RevverAPI,
  0,
  []
);


/*************************************************************************
 * RevverAPI: flockIWebServiceAPI Implementation
 *************************************************************************/

/**
 * void call(in AString aMethod,
 *           in nsISupports aParams,
 *           in nsISupports aPostVars,
 *           in flockIListener aFlockListener);
 * @see flockIWebServiceAPI#call
 */
RevverAPI.prototype.call =
function RevverAPI_call(aMethod, aParams, aPostData, aFlockListener) {
  // If post data was passed in, assume this request is a POST.
  var isPost = (aPostData) ? true : false;

  var url = REVVER_API_URL;
  var error = {};

  var params = (aParams) ? aParams : {};
  params.method = aMethod;

  var idx = 0;
  for (var p in params) {
    // Only use "?" for the first param.  Use "&" after.
    url += (idx == 0) ? "?" : "&";
    url += p + "=" + params[p];
    idx++;
  }

  this._logger.debug(".call() url: " + url);

  var req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(CI.nsIXMLHttpRequest)
            .QueryInterface(CI.nsIJSXMLHttpRequest);

  // Don't pop nsIAuthPrompts if auth fails
  req.mozBackgroundRequest = true;

  req.open((isPost) ? "POST" : "GET", url, true);

  var inst = this;
  req.onreadystatechange = function api_call_orsc(aEvent) {
    inst._logger.debug(".call() ReadyState: " + req.readyState);

    if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      inst._logger.debug(".call() Status: " + req.status);

      if (Math.floor(req.status/100) == 2) {
        inst._logger.debug(".call() response:\n" + req.responseText);
        var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
        var result = nsJSON.decode(req.responseText);

        if (result) {
          // Revver can return a 200 with an error described in the JSON.
          if (result.error) {
            error = inst.getError(result.error);
            error.serviceErrorCode = result.error;
            error.serviceErrorString = result.message;
            aFlockListener.onError(error, arguments.callee.name);
          } else {
            aFlockListener.onSuccess(result, arguments.callee.name);
          }
        } else {
          inst._logger.debug(".call() error: "
                             + "empty or poorly formatted result");
          error = inst.getError(null);
          aFlockListener.onError(error, arguments.callee.name);
        }
      } else {
        // HTTP errors (0 for connection lost)
        inst._logger.debug(".call() HTTP Error: " + req.status);
        error = inst.getHttpError(req.status);
        error.serviceErrorString = req.responseText;
        aFlockListener.onError(error, arguments.callee.name);
      }
    }
  };

  if (isPost) {
    req.setRequestHeader("Content-Type",
                         "application/x-www-form-urlencoded; charset=UTF-8");
    var postBody = "";
    for (var key in aPostData) {
      if (postBody.length) {
        postBody += "&";
      }
      postBody += key + "=" + encodeURIComponent(aPostData[key]);
    }
  }

  if (isPost && postBody && postBody.length) {
    this._logger.debug(".call() postBody: " + postBody);
    req.send(postBody);
  } else {
    req.send(null);
  }
};

/**
 * flockIError getError(in AString aErrorCode)
 * @see flockIWebServiceAPI#getError
 */
RevverAPI.prototype.getError =
function RevverAPI_getError(aErrorCode) {
  this._logger.debug(".getError('"+ aErrorCode + "')");

  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.serviceErrorCode = aErrorCode;
  error.serviceName = CLASS_TITLE;

  switch (aErrorCode) {
    case "3":
      // Authentication Failed
      error.errorCode = CI.flockIError.PHOTOSERVICE_LOGIN_FAILED;
      break;

    case "1000":
      // Invalid argument error. Includes description of why argument was
      // invalid.
      error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_QUERY
      break;

    default:
      error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
      break;
  }

  return error;
};

/**
 * flockIError getHttpError(in AString aHttpErrorCode);
 * @see flockIWebServiceAPI#getHttpError
 */
RevverAPI.prototype.getHttpError =
function RevverAPI_getHttpError(aHttpErrorCode) {
  this._logger.debug(".getHttpError('" + aHttpErrorCode + "')");

  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  error.errorCode = aHttpErrorCode;
  error.serviceErrorCode = aHttpErrorCode;
  error.serviceName = CLASS_TITLE;

  return error;
};


/*************************************************************************
 * Component: RevverService
 *************************************************************************/
function RevverService() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("RevverService");

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

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

  this._obs = CC["@mozilla.org/observer-service;1"]
              .getService(CI.nsIObserverService);

  FlockSvcUtils.getCoop(this);
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getWD(this);

  // Convenience variable.
  this._wd = this._WebDetective;

  // Initialize API
  gApi = new RevverAPI();

  profiler.profileEventEnd(evtID, "");

  FlockSvcUtils.nsIObserver.addDefaultMethod(this, "observe");

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

  var mws = FlockSvcUtils.flockIMediaWebService;
  mws.addDefaultMethod(this, "getChannel");
  mws.addDefaultMethod(this, "enumerateChannels");
  mws.addDefaultMethod(this, "getIconForQuery");

  var mews = FlockSvcUtils.flockIMediaEmbedWebService;
  mews.addDefaultMethod(this, "getSharingContent");

  // Collections Ids for the Revver default mediastreams
  this._collectionIdMap = {
    "featured": FEATURED_COLLECTION_ID,
    "mostviewed": MOST_VIEWED_COLLECTION_ID,
    "mostrecent": MOST_RECENT_COLLECTION_ID,
    "picks": PICKS_COLLECTION_ID
  };

  var bundle = this.getStringBundle();
  // Used by getChannel() and enumerateChannels(), defined in FlockSvcUtils:
  // channels for the MediaBar
  this._channels = {
    "special:featured": {
      title: bundle.GetStringFromName("flock.revver.title.featured"),
      supportsSearch: false
    },
    "special:mostviewed": {
      title: bundle.GetStringFromName("flock.revver.title.mostViewed"),
      supportsSearch: false
    },
    "special:mostrecent": {
      title: bundle.GetStringFromName("flock.revver.title.mostRecent"),
      supportsSearch: false
    },
    "special:picks": {
      title: bundle.GetStringFromName("flock.revver.title.picks"),
      supportsSearch: false
    }
  };
}

/*************************************************************************
 * RevverService: XPCOM Component Creation
 *************************************************************************/

RevverService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  CLASS_ID,
  CONTRACT_ID,
  RevverService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIWebService,
    CI.flockIMediaWebService,
    CI.flockIMediaEmbedWebService
  ]
);

// FlockXPCOMUtils.genericModule() categories
RevverService.prototype._xpcom_categories = [
  { category: "flockWebService", entry: CLASS_SHORT_NAME },
  { category: "flockMediaProvider", entry: CLASS_SHORT_NAME }
];


/*************************************************************************
 * RevverService: Private Data and Functions
 *************************************************************************/

RevverService.prototype._queryChannel =
function RevverService__queryChannel(aFlockListener, aQueryString, aCount, aPage,
                                     aRequestId)
{
  this._logger.debug(".queryChannel(..., '" + aQueryString + "', '"
                     + aCount + "', '" + aPage + "', '" + aRequestId + "')");

  var inst = this;
  var query = new QueryHelper(aQueryString);

  var embedPageUrl = this._wd.getString(CLASS_SHORT_NAME,
                                        "embeddedVideoPage", "");

  var processQueryResult =
  function processQueryResult(aResult) {
    var outArray = [];
    for (var i = 0; i < aResult.length; i++) {
      var node = aResult[i];
      var item = CC["@flock.com/photo;1"].createInstance(CI.flockIMediaItem);
      item.init(inst.shortName, flockMediaItemFormatter);
      item.thumbnail = node.thumbnailUrl;
      item.midSizePhoto = node.thumbnailUrl;
      item.embedTag = node.flashJsUrl;
      item.has_miniView = true;
      item.id = node.id;
      item.is_public = true;
      item.is_video = true;
      item.title = node.title;
      item.username = node.owner;
      item.webPageUrl = embedPageUrl.replace("%VIDEO_ID%", node.id)
                                    .replace("%AFFILIATE_ID%",
                                             REVVER_AFFILIATE_CODE);
      item.length_seconds = node.duration;
      item.description = node.description;
      item.view_count = node.completes;
      outArray.push(item);
    }
    return outArray;
  };

/*
  flockIMediaItem attributes:
  attribute string  svcShortName
  attribute string  thumbnail
  attribute string  midSizePhoto
  attribute string  largeSizePhoto
  attribute AUTF8String  embedTag
  attribute string  has_miniView
  attribute string  id
  attribute string  is_public
  attribute boolean  is_video
  attribute long  mediaType
  attribute string  lastUpdate
  attribute nsIPropertyBag2  metaData
  attribute string  icon
  attribute AUTF8String  title
  attribute string  uploadDate
  attribute AUTF8String  userid
  attribute AUTF8String  username
  attribute string  webPageUrl
  attribute AUTF8String  length_seconds
  attribute AUTF8String  rating_avg
  attribute AUTF8String  description
  attribute AUTF8String  comment_count
  attribute AUTF8String  view_count
  attribute AUTF8String  rating_count
*/

  var queryListener = {
    onSuccess: function queryListener_onResult(aResult) {
      var result = processQueryResult(aResult);
      var resultsEnum = {
        hasMoreElements: function resultsEnum_hasMoreElements() {
          return (result.length > 0);
        },
        getNext: function resultsEnum_getNext() {
          return result.shift();
        },
        QueryInterface: function resultsEnum_QueryInterface(aIid) {
          if (aIid.equals(CI.nsISimpleEnumerator)) {
            return this;
          }
          throw CR.NS_ERROR_NO_INTERFACE;
        }
      }
      aFlockListener.onSuccess(resultsEnum, aRequestId);
    },
    onError: function queryListener_onError(aFlockError, aTopic) {
      // Replace the passed topic with the request id.
      aFlockListener.onError(aFlockError, aRequestId);
    }
  }

  var returnFields = [
    "id", // Id of the media
    "title", // Returns media title
    "owner", // Returns the owner of the account which posted the media
    "description", // Media description
    "duration", // Media runtime in seconds
    "thumbnailUrl", // Absolute URL of video thumbnail
    "flashJsUrl", // Absolute URL to the swf player with necessary parameters included.
    "completes" // The number of times the video has had a complete view
  ];

  var options = {
    affiliate: REVVER_AFFILIATE_CODE,
    limit: (aCount ? aCount : 50),
    offset: (aPage ? aPage : 0)
  };

  var method = "open.video.find";
  var findObject = {};
  if (query.hasKey("special")) {
    method = "open.collection.collect";
    findObject = this._collectionIdMap[query.getKey("special")];
  } else if (query.username) {
    findObject.owners = [query.username];
  } else {
    findObject.search = [query.search];
  }

  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  var json = nsJSON.encode([findObject, returnFields, options]);
  this._logger.debug("JSON: " + json);

  var paramsStruct = {
    params: json
  };

  // This Revver method appears to want [query, returnFields, options]
  gApi.call(method, paramsStruct, null, queryListener);
}

/**
 * AString _getVideoIdFromUrl(in AString aUrl);
 */
RevverService.prototype._getVideoIdFromUrl =
function RevverService__getVideoIdFromUrl(aUrl) {
  var videoId = null;
  // http://www.revver.com/video/197473/the-cat/
  var results = FlockSvcUtils.newResults();
  if (this._wd.detectNoDOM(CLASS_SHORT_NAME, "videoId", "", aUrl, results)) {
    videoId = results.getPropertyAsAString("videoId");
  }
  return videoId;
};

/**
 * Create a lightweight quasi-nsISimpleEnumerator from a passed in array.
 *
 * Doing it here in JS prevents having to cross XPCOM boundaries, resulting
 * in a performance bump.
 *
 * @param aArray  The array to create the enumerator from.
 * @returns  the enumerator claiming to be a nsISimpleEnumerator.
 */
RevverService.prototype._createEnum =
function RevverService__createEnum(aArray) {
  return {
    QueryInterface: function QueryInterface(aIid) {
      if (aIid.equals(CI.nsISimpleEnumerator)) {
        return this;
      }
      throw CR.NS_ERROR_NO_INTERFACE;
    },
    hasMoreElements: function hasMoreElements() {
      return (aArray.length > 0);
    },
    getNext: function getNext() {
      return aArray.shift();
    }
  };
};


/*************************************************************************
 * RevverService: flockIWebService Implementation
 *************************************************************************/

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

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

// readonly attribute AString icon;
RevverService.prototype.icon = FAVICON;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
RevverService.prototype.contractId = CONTRACT_ID;

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


/*************************************************************************
 * RevverService: flockIMediaWebService Implementation
 *************************************************************************/

/**
 * @see flockIMediaWebService#decorateForMedia
 */
RevverService.prototype.decorateForMedia =
function RevverService_decorateForMedia(aDocument, aAccount) {
  document = aDocument.QueryInterface(CI.nsIDOMHTMLDocument);
  this._logger.debug(".decorateForMedia('" + document.URL + "')");

  var results = FlockSvcUtils.newResults();
  if (this._wd.detect(CLASS_SHORT_NAME, "media", document, results)) {
    var bundle = this._stringBundle;

    var mediaArr = [];
    var userid = results.getPropertyAsAString("userid");

    // media item for user videos
    var vidname = bundle.formatStringFromName("flock.revver.videosUC",
                                              [userid], 1);
    var media = {
      name: userid,
      query: "username:" + userid,
      label: vidname,
      favicon: this.icon,
      service: this.shortName
    };
    mediaArr.push(media);

    // media item for user favorites
    var favname = bundle.formatStringFromName("flock.revver.favoritesUC",
                                              [userid], 1);
    media = {
      name: userid,
      query: "favorites:" + userid,
      label: favname,
      favicon: this.icon,
      service: this.shortName
    };
    mediaArr.push(media);

    if (!document._flock_decorations) {
      document._flock_decorations = {};
    }

    if (document._flock_decorations.mediaArr) {
      document._flock_decorations.mediaArr =
        document._flock_decorations.mediaArr.concat(mediaArr);
    } else {
      document._flock_decorations.mediaArr = mediaArr;
    }

    this._obs.notifyObservers(document, "media", "media:update");
  }
};

/**
 * void findByUsername(in flockIListener aFlockListener, in AString aUsername);
 * @see flockIMediaWebService#findByUsername
 */
RevverService.prototype.findByUsername =
function RevverService_findByUsername(aFlockListener, aUsername) {
  this._logger.debug(".findByUsername(..., '" + aUsername + "')");

  // From http://developer.revver.com/api/xml/methods/user:
  // "The search results only include child users of the authenticated user."
  // Therefore, don't bother searching for users.
  aFlockListener.onError(null, null);
};

/**
 * void getAlbums(in flockIListener aFlockListener, in AString aUsername);
 * @see flockIMediaWebService#getAlbums
 */
RevverService.prototype.getAlbums =
function RevverService_getAlbums(aListener, aUsername) {
  this._logger.info(".getAlbums(aListener, " + aUsername + ")");

  // Revver doesn't support the concept of albums, so return an empty enum.
  aListener.onSuccess(this._createEnum([]), null);
}


// DEFAULT: flockIMediaChannel getChannel(in AString AChannelId);
// DEFAULT: nsISimpleEnumerator enumerateChannels();

var flockMediaItemFormatter = {
  canBuildTooltip: false,
  canBuildHTML: true,
  canBuildLargeHTML: false,
  canBuildBBCode: false,
  canBuildMiniPage: true,

  buildHTML: function fmif_buildHTML(aMediaItem) {
    return "<a title='" + aMediaItem.title + "' href='"
                        + aMediaItem.webPageUrl + "'>\n  <img src='"
                        + aMediaItem.thumbnail + "' border='0' />\n</a>";
  },

  buildMiniPage: function fmif_buildMiniPage(aMediaItem) {
    return '<html><head><title>'
           + aMediaItem.title
           + ' (' + aMediaItem.username + ')</title>'
           + '<script type="text/javascript" src="'
           + aMediaItem.embedTag
           + '"></script></head>'
           + '<body onLoad="window.sizeToContent()"></body></html>';
  }
};

/**
 * @see flockIMediaWebService#getMediaItemFormatter
 */
RevverService.prototype.getMediaItemFormatter =
function RevverService_getMediaItemFormatter() {
  return flockMediaItemFormatter;
}

// void migrateAccount(in AString aId, in AString aUsername);
RevverService.prototype.migrateAccount =
function RevverService_migrateAccount(aId, aUsername) {
  this._logger.debug(".migrateAccount('" + aId + "', '" + aUsername + "')");
  // no-op
}

/**
 * void search(in flockIListener aFlockListener,
 *             in AString aQuery,
 *             in long aCount,
 *             in long aPage,
 *             in AString aRequestId);
 * @see flockIMediaWebService#search
 */
RevverService.prototype.search =
function RevverService_search(aFlockListener, aQueryString, aCount, aPage, aReqId) {
  // Revver only supports channel queries for now
  if (aPage == 1) {
    this.count = 1;
  }
  this._queryChannel(aFlockListener, aQueryString, aCount, (aPage ? this.count : 1),
                     aReqId);
  this.count = aCount + this.count;
}

/**
 * boolean supportsSearch(in AString aQuery);
 * @see flockIMediaWebService#supportsSearch
 */
RevverService.prototype.supportsSearch =
function RevverService_supportsSearch(aQueryString) {
  var query = new QueryHelper(aQueryString);

  if (query.special) {
    var channel = this._channels["special:" + query.special];
    if (channel) {
      return channel.supportsSearch;
    }
  }

  if (query.search) {
    return false;
  }

  if (query.user) {
    return true;
  }

  return false;
}

/**
 * readonly attribute boolean supportsUsers;
 * @see flockIMediaWebService#supportsUsers
 */
RevverService.prototype.supportsUsers = true;


/*************************************************************************
 * RevverService: flockIMediaEmbedWebService Implementation
 *************************************************************************/

/**
 * boolean checkIsStreamUrl(in AString aUrl);
 * @see flockIMediaEmbedWebService#checkIsStreamUrl
 */
RevverService.prototype.checkIsStreamUrl =
function RevverService_checkIsStreamUrl(aUrl) {
  this._logger.debug(".checkIsStreamUrl('" + aUrl + "')");
  if (this._wd.detectNoDOM(CLASS_SHORT_NAME, "isStreamUrl", "", aUrl, null)) {
    this._logger.debug(".checkIsStreamUrl(): YES, this URL is a "
                       + CLASS_SHORT_NAME + " stream! \n" + aUrl);
    return true;
  }
  this._logger.debug(".checkIsStreamUrl(): NO, this URL is NOT a "
                     + CLASS_SHORT_NAME + " stream! \n" + aUrl);
  return false;
};

/**
 * void getMediaQueryFromURL(in AString aUrl, in flockIListener aListener);
 * @see flockIMediaEmbedWebService#getMediaQueryFromURL
 */
RevverService.prototype.getMediaQueryFromURL =
function RevverService_getMediaQueryFromURL(aUrl, aListener) {
  this._logger.debug(".getMediaQueryFromURL('" + aUrl + "', ...)");

  var videoId = this._getVideoIdFromUrl(aUrl);
  if (videoId) {
    var inst = this;
    var myListener = {
      onSuccess: function getMediaQueryFromURL_onResult(aResult) {
        var results = FlockSvcUtils.newResults();
        results.setPropertyAsAString("query", "username:" + aResult.owner);
        aListener.onSuccess(results, "query");
      },
      onError: function getMediaQueryFromURL_onError(aError) {
        inst._logger.error(".getMediaQueryFromURL() myListener.onError()");
        aListener.onError(null, aError, null);
      }
    };

    var paramsStruct = {
      params: JSON.toString(videoId, ["owner"])
    };

    // This Revver method wants [id, returnFields]
    gApi.call("open.video.get", paramsStruct, null, myListener);
  } else {
    this._logger.error(".getMediaQueryFromURL() Unable to get user.");
    aListener.onError(null, "Unable to get user.", null);
  }
};

// DEFAULT: boolean getSharingContent(in nsIDOMHTMLElement aSrc,
//                                    in nsIWritablePropertyBag2 aProp);


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

// Create array of components.
var componentsArray = [RevverService];

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