CloudServices/Sync/FxSync/Developer/ClientAPI: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
 
(57 intermediate revisions by 11 users not shown)
Line 1: Line 1:
== Overview ==
{{warning|Content has moved to https://developer.mozilla.org/en/Firefox_Sync/JavaScript_Client_API}}
 
This page describes how to the client-side Weave API for sync engines (and their helper classes).  The focus is on using this API to create a new sync engine to synchronize a new data type.  The data type can be anything that extension JS code has access to through any Mozilla API; this means this page must of necessity be pretty vague about reading and writing the underlying data.  You'll have to fill in those blanks yourself.  Try browsing the [link] xpcom documentation to find out how to get at the many types of useful data that Mozilla stores.
 
To sync a new data type, you'll need to write an engine class* that extends the base SyncEngine class; you'll also need to extend three helper classes.  Here are the classes you need to extend, and the files in which they're defined:
 
# <tt>SyncEngine</tt>, in <tt>weave/modules/engines.js</tt>
# <tt>CryptoWrapper</tt>, in <tt>weave/modules/base_records/crypto.js</tt>
# <tt>Store</tt>, in <tt>weave/modules/stores.js</tt>
# <tt>Tracker</tt>, in <tt>weave/modules/trackers.js</tt>
 
It will be very helpful to look at the existing sync engines -- such as the one for bookmarks and the one for history -- and their helper classes, for guidance.  You can find these files at:
 
* <tt>weave/modules/engines/bookmarks.js</tt> -- the <tt>BookmarkEngine</tt>, <tt>BookmarkStore</tt>, and <tt>BookmarkTracker</tt>.
* <tt>weave/modules/type_records/bookmark.js</tt> -- the Record classes for the various subtypes of bookmarks
* <tt>weave/modules/engines/history.js</tt> -- the <tt>HistoryEngine</tt>, <tt>HistoryStore</tt>, and <tt>HistoryTracker</tt>.
* <tt>weave/modules/type_records/history.js</tt> -- the <tt>HistoryRec</tt> record class.
 
After implementing your classes, you'll have to register them with Weave; you should also add a check-box to the Weave preferences screen to let the user turn your engine on and off.  How to do these thing is explained at the end of the page.
 
* - Javascript is prototype-based, not class-based, so technically it's not correct to talk about "subclassing" or "extending a class", but what we're doing is very much the equivalent of that, and I don't know any other terminology to explain it as clearly.
 
== Writing a Record class ==
 
A Record object is a wrapper around a single instance of whatever data it is that you are syncing -- a single bookmark in the case of the bookmark engine, a single browser tab in the case of the tab engine, and so on.
 
There's a class called <tt>CryptoWrapper</tt> defined in <tt>weave/modules/base_records/crypto.js</tt>, which handles all the encryption and decryption of your record for you.  All you have to do is write a class that extends <tt>CryptoWrapper</tt> and maintains a property called <tt>cleartext</tt>.  <tt>cleartext</tt> must be a JSON-dictionary-style object; put into it all values that you want to have encrypted, stored on the server, decrypted, and synced up.
 
You may find it useful to write getters and setters for various properties of your Record class.
 
The skeleton of a sample Record class:
 
<pre>
function FooRecord() {
  this._FooRecord_init();
}
FooRecord.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logname: "Record.Foo",
 
  _FooRecord_init: function FooItem_init(uri) {
    this._CryptoWrap_init(uri);
    this.cleartext = {};
  },
 
  get bar() this.cleartext.bar,
  set bar(value) {
    this.cleartext.bar = value;
  }
};
</pre>
 
== Writing a Store class ==
 
=== Create / Update / Remove ===
 
The next three methods are called by the sync algorithm when it determines that the state of the local application needs to be changed to keep it up-to-date with the user's remote activities.  The sync algorithm will call your Store object with a record to be created, updated, or removed, and it is your Store object's responsibility to apply this change to the local application using whatever methods are neccessary.
 
<tt>create(record)</tt>
:Not to be confused with <tt>createRecord()</tt>, this method (which should probably be renamed for clarity soon) tells your Store to create a new item in the local application, based on the data in record.  (So for example, <tt>BookmarkStore.create()</tt> adds a new bookmark to the Firefox profile).
 
<tt>update(record)</tt>
:The argument is a record which has been remotely modified; your Store should locate the matching local item (presumably using the GUID, which is available in record.id) and update it to the new values provided in <tt>record</tt>.
 
<tt>remove(record)</tt>
:The argument is a record which has been remotely deleted; your Store should locate the matching local item and delete it.
 
=== Example Store class skeleton ===
 
<pre>
function FooStore() {
  // Maintains the store of all your Foo-type items and their GUIDs.
}
FooStore.prototype = {
  __proto__: Store.prototype,
  _logName: "foo",
  _lookup: "huh",
  itemExists: function(id) {
    // Return true if an item with guid = id exists in the store.
  },
  createRecord: function(id) {
    // this has something to do with WBOs?
    // now sets the record id
    // use utils.makeGuid to create the guid for something
    // return the record
  },
  createMetaRecords: function(guid, items) {
    // Return... what exactly?
  },
  changeItemId: function(oldId, newId) {
    // Find the item with guid = oldId and change its guid to newId.
  },
  getAllIds: function() {
    // Return a dictionary-style object...
  },
  wipe: function() {
    // Delete everything!
  },
  create: function(record) {
   
  },
  update: function(record) {
   
  },
  remove: function(record) {
   
  }
};
</pre>
 
== Writing a Tracker class ==
 
Your tracker class must inherit from <tt>Tracker</tt>, which is defined in <tt>weave/engines/trackers.js</tt>.  Its purpose in life is to track changes to whatever data type you are syncing.  It must maintain a list of GUIDs for objects that have been changed and therefore require syncing.  It also has to maintain a "score", which is a number from 0 to 100 which represents "how badly does this data type need to be synced right now".
 
Getting your tracker to track changes in the underlying data is probably easiest to do if you register your tracker as an observer, so that it can receive notifications from one or more Mozilla components.  What events you listen for is entirely up to you.  You can find out more about registering as an observer here:  [link].
 
=== The Score ===
 
The '''score''' is stored in <tt>this._score</tt>, a variable defined by the base Tracker class.  Set your score by assigning a value to <tt>this._score</tt>.
 
The score number is used by the scheduler to decide when your engine will be synced.  Setting it to zero means "I have no need to sync".  The higher you set this number, the more urgently the scheduler treats your request.  Setting it to 100 means "Sync me immediately".  It's up to you to figure out the best way to assign a score based on the current state of whatever data type you're tracking.
 
Your tracker score is automatically reset to zero after each time your engine syncs.
 
=== The changed GUID list ===
 
The base class maintains a list of GUIDs that need syncing.  When your tracker detects that an item has changed, you should add it to this list by calling:
 
this.addChangedID(guid);
 
These GUIDs correspond to the <tt>.id</tt> fields of your Record objects; see the section on the Store class for more about defining and maintaining the mapping between GUIDs and Records.
 
=== Example ===
 
Here's the skeleton of a sample Tracker class.
 
<pre>
function FooTracker() {
  this._init();
}
FooTracker.prototype = {
  __proto__: Tracker.prototype,
  _logName: "FooTracker",
  file: "foo",
 
  _init: function FooTracker_init() {
    // The ugly syntax on the next line calls the base class's init method:
    this.__proto__.__proto__.init.call(this);
    /* Here is where you would register your tracker as an observer, so that
        its onEvent() (or other appropriately named) method can be called
        in response to events. */
  },
 
  onEvent: function FooTracker_onEvent() {
    /* Here is where you'd handle the event.  See the documentation for
        whatever service you are observing to find out what to call this
        method, what arguments to expect, and how to interpret them. */
    var guid = 0;
 
    /* Here is where you'd include code to figure out the GUID of the item
        that has changed... */
    this.addChangedId(guid);
 
    // Update the score as you see fit:
    this._score += 10;
  }
};
</pre>
 
== Writing an Engine class ==
 
Your Engine class serves to tie together your Store, Tracker, and Record classes into a bundle that the Weave sync algorithm can instantiate and use.
 
You're probably sick of writing subclasses by this point, but don't worry: this one is very easy.  I saved it for last because it requires the least code.
 
Your class must derive from the <tt>SyncEngine</tt> class, defined in <tt>weave/modules/engines.js</tt>.  <tt>SyncEngine</tt> contains a lot of code which handles logic for the core sync algorithm, but your subclass won't need to call any of this directly, unless you are overriding part of the sync algorithm to provide custom sync behavior (an advanced technique outside the scope of this article).
 
A sample Engine class:
 
function FooEngine() {
  this._init();
}
FooEngine.prototype = {
  __proto__: SyncEngine.prototype,
  name: "foo",
  displayName: "Foo",
  logName: "Foo",
  _storeObj: FooStore,
  _trackerObj: FooTracker
};
 
As you can see, there isn't actually any new code here at all; the prototype simply defines some metadata such as the Store and Tracker classes to use, and the human-readable name that will be used in the log files to identify errors and status messages coming from this engine.
 
(Don't forget that you'll need to import SyncEngine and export FooEngine.  So the top of your file should include:)
 
const EXPORTED_SYMBOLS = ["FooEngine", /* ... etc... */ ];
const Cu = Components.utils;
// etc...
Cu.import("resource://weave/engines.js");
 
== Installing your classes into Weave ==
 
== Updating the Weave preferences screen ==
 
== Making sure it works on Fennec ==

Latest revision as of 20:11, 19 November 2013