The backend provides the following groups of functions: 1) Access to the datastore Reading, adding, replacing and deleting of entries. Also retrieve information about changes in data store. This is done via the retrieveEntry(), addEntry(), replaceEntry(), deleteEntry() and getServerChanges() methods. 2) User management functions This is the checkAuthentication() method to verify that a given user password combination is allowed to access the backend data store, and the setUser() method which does a "login" to the backend data store if required by the type of backend data store. Please note that the password is only transferred once in a sync session, so when handling the subsequent packets messages, the user may need to be "logged in" without a password. (Or the session management keeps the user "logged in"). 3) Maintainig the client ID <-> server ID map The SyncML protocol does not require clients and servers to use the same primary keys for the data entries. So a map has to be in place to convert between client primary keys (called cuid's here) and server primary keys (called suid's). It's up to the server to maintain this map. Method for this is createUidMap(). 4) Sync anchor handling After a successful initial sync, the client and server sync timestamps are stored. This allows to perform subsequent syncs as delta syncs, where only new changes are replicated. Servers as well as clients need to be able to store two sync anchors (the client's and the server's) for a sync. Methods for this are readSyncAnchors() and writeSyncAnchors(). 5) Test supporting functions The SyncML module comes with its own testing framework. All you need to do is implement the two methods testSetup() and testTearDown() and you are able to test your backend with all the test cases that are part of the module. 6) Miscellaneous functions This involves session handling (sessionStart() and sessionClose()), logging (logMessage() and logFile()), timestamp creation (getCurrentTimeStamp()), charset handling (getCharset(), setCharset()) and database identification (isValidDatabaseURI()). For all of these functions, a default implementation is provided in Horde_SyncMl_Backend. If you want to create a backend for your own appliction, you can either derive from Horde_SyncMl_Backend and implement everything in groups 1 to 5 or you derive from Horde_SyncMl_Backend_Sql which implements an example backend based on direct database access using the PEAR MDB2 package. In this case you only need to implement groups 1 to 3 and can use the implementation from Horde_SyncMl_Backend_Sql as a guideline for these functions. Key Concepts ------------ In order to successfully create a backend, some understanding of a few key concepts in SyncML and the Horde_SyncMl package are certainly helpful. So here's some stuff that should make some issues clear (or at lest less obfuscated): 1) DatabaseURIs and Databases The SyncML protocol itself is completly independant from the data that is replicated. Normally the data are calendar or address book entries but it may really be anything from browser bookmarks to comeplete database tables. An ID (string name) of the database you want to actually replicate has to be configured in the client. Typically that's something like 'calendar' or 'tasks'. Client and server must agree on these names. In addition this string may be used to provide additional arguments. These are provided in a HTTP GET query style: like tasks?ignorecompletedtasks to replicate only pending tasks. Such a "sync identifier" is called a DatabaseURI and is really a database name plus some additional options. The Horde_SyncMl package completly ignores these options and simply passes them on to the backend. It's up to the backend to decide what to do with them. However when dealing with the internal maps (cuid<->suid and sync anchors), it's most likely to use the database name only rather than the full databaseURI. The map information saying that server entry 20070101203040xxa@mypc.org has id 768 in the client device is valid for the database "tasks", not for "tasks?somesillyoptions". So what you normally do is calling some kind of $database = $this->normalize($databaseURI) in every backend method that deals with databaseURIs and use $database afterwards. However actual usage of options is up to the backend implementation. SyncML works fine without. 2) Suid and Guid mapping This is the mapping of client IDs to server IDs and vice versa. Please note that this map is per user and per client device: the server entry 20070101203040xxa@mypc.org may have ID 720 in your PDA and AA10FC3A in your mobile phone. 3) Sync Anchors
Author: Karsten Fourmont (karsten@horde.org)
Beispiel #1
0
 /**
  * Sends an RPC request to the server and returns the result.
  *
  * @param string $request  The raw request string.
  *
  * @return string  The XML encoded response from the server.
  */
 function getResponse($request)
 {
     $backendparms = array('debug_dir' => Horde::getTempDir() . '/sync', 'debug_files' => true, 'log_level' => 'DEBUG');
     /* Create the backend. */
     $GLOBALS['backend'] = Horde_SyncMl_Backend::factory('Horde', $backendparms);
     /* Handle request. */
     $h = new Horde_SyncMl_ContentHandler();
     $response = $h->process($request, $this->getResponseContentType(), Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/rpc.php', true, -1));
     /* Close the backend. */
     $GLOBALS['backend']->close();
     return $response;
 }
Beispiel #2
0
 /**
  */
 public function update(Horde_Core_Prefs_Ui $ui)
 {
     global $notification, $registry;
     $auth = $registry->getAuth();
     $backend = Horde_SyncMl_Backend::factory('Horde');
     if ($ui->vars->removedb && $ui->vars->removedevice) {
         try {
             $backend->removeAnchor($auth, $ui->vars->removedevice, $ui->vars->removedb);
             $backend->removeMaps($auth, $ui->vars->removedevice, $ui->vars->removedb);
             $notification->push(sprintf(_("Deleted synchronization session for device \"%s\" and database \"%s\"."), $ui->vars->deviceid, $ui->vars->db), 'horde.success');
         } catch (Horde_Exception $e) {
             $notification->push(_("Error deleting synchronization session:") . ' ' . $e->getMessage(), 'horde.error');
         }
     } elseif ($ui->vars->deleteall) {
         try {
             $backend->removeAnchor($auth);
             $backend->removeMaps($auth);
             $notification->push(_("All synchronization sessions deleted."), 'horde.success');
         } catch (Horde_Exception $e) {
             $notification->push(_("Error deleting synchronization sessions:") . ' ' . $e->getMessage(), 'horde.error');
         }
     }
     return false;
 }
Beispiel #3
0
 public function logMessage($message, $priority = 'INFO')
 {
     parent::logMessage($message, $priority);
     echo $this->_logtext;
     $this->_logtext = '';
 }
Beispiel #4
0
 /**
  * Logs a message in the backend.
  *
  * @param mixed $message    Either a string or a PEAR_Error object.
  * @param string $priority  The priority of the message. One of:
  *                           - EMERG
  *                           - ALERT
  *                           - CRIT
  *                           - ERR
  *                           - WARN
  *                           - NOTICE
  *                           - INFO
  *                           - DEBUG
  */
 public function logMessage($message, $priority = 'INFO')
 {
     $trace = debug_backtrace();
     $trace = $trace[1];
     // Internal logging to $this->_logtext.
     parent::logMessage($message, $priority);
     // Logging to Horde log:
     Horde::log($message, $priority, array('file' => $trace['file'], 'line' => $trace['line']));
 }
Beispiel #5
0
/**
 * Executes one test case.
 *
 * A test cases consists of various pre-recorded .xml packets in directory
 * $name.
 */
function test($name)
{
    system($GLOBALS['this_script'] . ' --setup');
    $GLOBALS['testbackend'] = Horde_SyncMl_Backend::factory($GLOBALS['syncml_backend_driver'], $GLOBALS['syncml_backend_parms']);
    $GLOBALS['testbackend']->testStart(SYNCMLTEST_USERNAME, 'syncmltest');
    $packetNum = 10;
    $anchor = '';
    while (testsession($name, $packetNum, $anchor) === true) {
        $packetNum += 10;
    }
    /* Cleanup */
    if (!$GLOBALS['skipcleanup']) {
        $GLOBALS['testbackend']->testTearDown();
    }
    echo "testcase {$name}: passed\n";
}
Beispiel #6
0
 /**
  * Cleanup public function called after all message processing is finished.
  *
  * Allows for things like closing databases or flushing logs.  When
  * running in test mode, tearDown() must be called rather than close.
  */
 public function close()
 {
     parent::close();
     $this->_db->disconnect();
 }