[sync] Turba and Syncml patch proposal
Karsten Fourmont
fourmont at gmx.de
Sun Jan 23 11:50:18 PST 2005
Hi,
skimming Germany's biggest computer magazine this weekend, I was
(pleasently) surprised to see a nice Horde screenshot in there. It
contained the todo list of some guy called "Chuck Hagenbuch". :-))) And
it had "syncing" as a priority 2 entry. Reminded like this, I feel very
much motivated to continue work a bit. ;-)
Attached's a new patch proposal based on Chuck's suggestions. Here's an
outline of the changes:
1) the api for nag, mnemo, kronolith, and turba now has a
getTSforAction($uid, $action)
> How about just getHistory($uid), for getting the history of that
> object?
imho the (current) history object itself is too much an internal
structure and should not be exposed by the external api. For sql
backends it contains the conncection user and password.
For the moment I stick with getTSforAction. We can extend/generalize it
when the need arises.
2) listBy in the api of nag, mnemo, kronolith, and turba now returns the
plain uids rather than s.th. like turba:karsten:uid
This is done by
return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
3) as a result the sync code has slightly changed and is now only
dependant on the horde external api: it no longer directly calls
history. Much cleaner!
4) the turba api functions allow specifying a source (i.e. addressbook).
If no such source is specified, the pref 'default_dir' is used:
if (empty($source)) {
$source = $prefs->getValue('default_dir');
}
5) some more stuff to get the turba api actual working. Used to be in my
previous patch proposals already.
Let me know what you think. If there are no major objections I'll go
ahead and commit this. Once this is done we can start looking at the
problems with various clients again.
Two notes for anyboding applying this patch:
1) turba address book syncing uses the default address book specified in
turba/options/Searching Options: "Default directory for your personal
addressbook". So this has to be set to an address book writeable by you.
2) syncing only works for entries created with "new style" uids. These
are used for roughly 3 months now. Anything created before may work or
not. Sometimes the 2004-10-26_create_default_histories.php script helps,
but maybe not in all cases.
Cheers
Karsten
P.S.
For those interested: the magazine is c't 3/2005, page 46.
P.P.S.
Sorry to everyone for the long response times. But atm I'm working
onsite at a project with no ssh during the day and horrible phone rates
from the hotel room. So horde work is basically limited to weekends.
P.P.P.S.
Though I really tried this time, I know I'm terrible bad at getting code
conventions right.
As compensation I promise to pay a beer for every remaining stupid error
(missing spaces) I make and somebody else has to fix. Caveat: location
for redemption is Munich only ;-)
Anybody knows of something similar to Checkstlye for php?
-------------- next part --------------
Index: framework/SyncML/SyncML/Sync.php
===================================================================
RCS file: /repository/framework/SyncML/SyncML/Sync.php,v
retrieving revision 1.9
diff -u -r1.9 Sync.php
--- framework/SyncML/SyncML/Sync.php 3 Jan 2005 13:09:17 -0000 1.9
+++ framework/SyncML/SyncML/Sync.php 23 Jan 2005 19:31:18 -0000
@@ -88,9 +88,6 @@
{
global $registry;
- require_once 'Horde/History.php';
- $history = &Horde_History::singleton();
-
$state = &$_SESSION['SyncML.state'];
$hordeType = $type = $this->_targetLocURI;
$contentType = $state->getPreferedContentType($type);
@@ -103,9 +100,9 @@
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($command->getContent(), $contentType), $contentType));
if (!is_a($guid, 'PEAR_Error')) {
- $ts = $history->getTSforAction($guid, 'add');
+ $ts = $registry->call($hordeType . '/getTSforAction',array($guid,'add'));
if(!$ts) {
- Horde::logMessage('SyncML: unable to find add-ts for ' . $guid . ' at ' . $ts, __FILE__, __LINE__, PEAR_LOG_ERROR);
+ Horde::logMessage('SyncML: unable to find add-ts for ' . $guid . ' at ' . $ts, __FILE__, __LINE__, PEAR_LOG_ERR);
}
$state->setUID($type, $command->getLocURI(), $guid, $ts);
$state->log("Client-Add");
@@ -122,7 +119,7 @@
if (!is_a($guid, 'PEAR_Error')) {
$registry->call($hordeType . '/delete', array($guid));
- $ts = $history->getTSforAction($guid, 'delete');
+ $ts = $registry->call($hordeType . '/getTSforAction',array($guid,'delete'));
$state->setUID($type, $command->getLocURI(), $guid, $ts);
$state->log("Client-Delete");
Horde::logMessage('SyncML: deleted entry ' . $guid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG);
@@ -138,7 +135,7 @@
$ok = $registry->call($hordeType . '/replace',
array($guid, $state->convertClient2Server($command->getContent(), $contentType), $contentType));
if (!is_a($ok, 'PEAR_Error')) {
- $ts = $history->getTSforAction($guid, 'modify');
+ $ts = $registry->call($hordeType . '/getTSforAction',array($guid,'modify'));
$state->setUID($type, $command->getLocURI(), $guid, $ts);
Horde::logMessage('SyncML: replaced entry ' . $guid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->log("Client-Replace");
@@ -155,7 +152,7 @@
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($command->getContent(), $contentType), $contentType));
if (!is_a($guid, 'PEAR_Error')) {
- $ts = $history->getTSforAction($guid, 'add');
+ $ts = $registry->call($hordeType . '/getTSforAction',array($guid,'add'));
$state->setUID($type, $command->getLocURI(), $guid, $ts);
$state->log("Client-AddReplace");
Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Index: framework/SyncML/SyncML/Sync/TwoWaySync.php
===================================================================
RCS file: /repository/framework/SyncML/SyncML/Sync/TwoWaySync.php,v
retrieving revision 1.14
diff -u -r1.14 TwoWaySync.php
--- framework/SyncML/SyncML/Sync/TwoWaySync.php 3 Jan 2005 13:09:18 -0000 1.14
+++ framework/SyncML/SyncML/Sync/TwoWaySync.php 23 Jan 2005 19:31:19 -0000
@@ -47,15 +47,12 @@
{
global $registry;
- require_once 'Horde/History.php';
- $history = &Horde_History::singleton();
-
$state = &$_SESSION['SyncML.state'];
// Get changes.
$changes = $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts));
foreach ($changes as $guid) {
- $guid_ts = $history->getTSforAction($guid, 'modify');
+ $guid_ts = $registry->call($hordeType . '/getTSforAction',array($guid,'modify'));
$sync_ts = $state->getChangeTS($syncType, $guid);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
@@ -90,7 +87,7 @@
// Get deletes.
$deletes = $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts));
foreach ($deletes as $guid) {
- $guid_ts = $history->getTSforAction($guid, 'delete');
+ $guid_ts = $registry->call($hordeType . '/getTSforAction',array($guid,'delete'));
$sync_ts = $state->getChangeTS($syncType, $guid);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
@@ -116,9 +113,9 @@
// Get adds.
$adds = $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts));
foreach ($adds as $guid) {
- $guid_ts = $history->getTSforAction($guid, 'add');
+ $guid_ts = $registry->call($hordeType . '/getTSforAction',array($guid,'add'));
$sync_ts = $state->getChangeTS($syncType, $guid);
-Horde::logMessage("SyncML: add: guid_ts: $guid_ts sync_ts:$sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ Horde::logMessage("SyncML: add: guid_ts: $guid_ts sync_ts:$sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
Index: kronolith/lib/api.php
===================================================================
RCS file: /repository/kronolith/lib/api.php,v
retrieving revision 1.128
diff -u -r1.128 api.php
--- kronolith/lib/api.php 20 Jan 2005 10:30:27 -0000 1.128
+++ kronolith/lib/api.php 23 Jan 2005 19:31:23 -0000
@@ -40,6 +40,11 @@
'type' => 'stringArray'
);
+$_services['getTSforAction'] = array(
+ 'args' => array('uid', 'timestamp'),
+ 'type' => 'integer',
+);
+
$_services['import'] = array(
'args' => array('content' => 'string', 'contentType' => 'string', 'calendar' => 'string'),
'type' => 'integer'
@@ -116,7 +121,26 @@
return $histories;
}
- return array_keys($histories);
+ /* strip leading kronolith:username: */
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
+}
+
+/**
+ * Returns the timestamp of an operation for a given uid an action
+ *
+ * @param string $uid The uid to look for
+ * @param string $action The action to check for - add, modify, or delete.
+ *
+ * @return integer The timestamp for this action
+ */
+function _kronolith_getTSforAction($uid, $action)
+{
+ require_once 'Horde/History.php';
+ $history = &Horde_History::singleton();
+
+ $guid = 'kronolith:' . Auth::getAuth() . ':' . $uid;
+
+ return $history->getTSforAction($guid, $action);
}
/**
Index: mnemo/lib/api.php
===================================================================
RCS file: /repository/mnemo/lib/api.php,v
retrieving revision 1.53
diff -u -r1.53 api.php
--- mnemo/lib/api.php 21 Dec 2004 15:26:47 -0000 1.53
+++ mnemo/lib/api.php 23 Jan 2005 19:31:26 -0000
@@ -20,6 +20,11 @@
'type' => 'stringArray'
);
+$_services['getTSforAction'] = array(
+ 'args' => array('uid', 'timestamp'),
+ 'type' => 'integer',
+);
+
$_services['import'] = array(
'args' => array('content', 'contentType'),
'type' => 'integer'
@@ -90,7 +95,26 @@
return $histories;
}
- return array_keys($histories);
+ /* strip leading mnemo:username: */
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
+}
+
+/**
+ * Returns the timestamp of an operation for a given uid an action
+ *
+ * @param string $uid The uid to look for
+ * @param string $action The action to check for - add, modify, or delete.
+ *
+ * @return integer The timestamp for this action
+ */
+function _mnemo_getTSforAction($uid, $action)
+{
+ require_once 'Horde/History.php';
+ $history = &Horde_History::singleton();
+
+ $guid = 'mnemo:' . Auth::getAuth() . ':' . $uid;
+
+ return $history->getTSforAction($guid, $action);
}
/**
Index: nag/lib/api.php
===================================================================
RCS file: /repository/nag/lib/api.php,v
retrieving revision 1.100
diff -u -r1.100 api.php
--- nag/lib/api.php 19 Oct 2004 15:22:11 -0000 1.100
+++ nag/lib/api.php 23 Jan 2005 19:31:29 -0000
@@ -44,6 +44,11 @@
'type' => 'stringArray',
);
+$_services['getTSforAction'] = array(
+ 'args' => array('uid', 'timestamp'),
+ 'type' => 'integer',
+);
+
$_services['import'] = array(
'args' => array('content', 'contentType', 'tasklist'),
'type' => 'integer',
@@ -224,7 +229,26 @@
return $histories;
}
- return array_keys($histories);
+ /* strip leading nag:username: */
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
+}
+
+/**
+ * Returns the timestamp of an operation for a given uid an action
+ *
+ * @param string $uid The uid to look for
+ * @param string $action The action to check for - add, modify, or delete.
+ *
+ * @return integer The timestamp for this action
+ */
+function _nag_getTSforAction($uid, $action)
+{
+ require_once 'Horde/History.php';
+ $history = &Horde_History::singleton();
+
+ $guid = 'nag:' . Auth::getAuth() . ':' . $uid;
+
+ return $history->getTSforAction($guid, $action);
}
/**
Index: turba/lib/api.php
===================================================================
RCS file: /repository/turba/lib/api.php,v
retrieving revision 1.122
diff -u -r1.122 api.php
--- turba/lib/api.php 20 Jan 2005 15:24:35 -0000 1.122
+++ turba/lib/api.php 23 Jan 2005 19:31:34 -0000
@@ -44,23 +44,28 @@
'type' => '{urn:horde}stringArray',
);
+$_services['getTSforAction'] = array(
+ 'args' => array('uid', 'timestamp'),
+ 'type' => 'integer',
+);
+
$_services['import'] = array(
'args' => array('content', 'contentType', 'source'),
'type' => 'string',
);
$_services['export'] = array(
- 'args' => array('uid', 'contentType'),
+ 'args' => array('uid', 'contentType', 'source'),
'type' => 'string',
);
$_services['delete'] = array(
- 'args' => array('uid'),
+ 'args' => array('uid', 'source'),
'type' => 'boolean',
);
$_services['replace'] = array(
- 'args' => array('uid', 'content', 'contentType'),
+ 'args' => array('uid', 'content', 'contentType', 'source'),
'type' => 'boolean',
);
@@ -291,10 +296,30 @@
return $histories;
}
- return array_keys($histories);
+ /* strip leading turba:username: */
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
}
/**
+ * Returns the timestamp of an operation for a given uid an action
+ *
+ * @param string $uid The uid to look for
+ * @param string $action The action to check for - add, modify, or delete.
+ *
+ * @return integer The timestamp for this action
+ */
+function _turba_getTSforAction($uid, $action)
+{
+ require_once 'Horde/History.php';
+ $history = &Horde_History::singleton();
+
+ $guid = 'turba:' . Auth::getAuth() . ':' . $uid;
+
+ return $history->getTSforAction($guid, $action);
+}
+
+
+/**
* Import a contact represented in the specified contentType.
*
* @param string $content The content of the contact.
@@ -309,8 +334,18 @@
function _turba_import($content, $contentType = 'array', $source = '')
{
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources, $prefs;
+ // get default address book from config
+ if (empty($source)) {
+ $source = $prefs->getValue('default_dir');
+ }
+
+ // check if source is writeable
+ if (!empty($source) && !empty($cfgSources[$source]['readonly'])) {
+ $source = false;
+ }
+
/* Check existance of and permissions on the specified source. */
if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources) || !isset($cfgSources[$source])) {
return PEAR::raiseError(_("Invalid address book"), 'horde.warning');
@@ -378,7 +413,7 @@
}
$object = &$driver->getObject($result);
- return $object->getValue('__key');
+ return $object->getValue('__uid');
}
/**
@@ -391,10 +426,10 @@
*
* @return string The requested data.
*/
-function _turba_export($uid, $contentType)
+function _turba_export($uid, $contentType, $source = '')
{
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources, $prefs;
if (is_array($contentType)) {
$options = $contentType;
@@ -404,18 +439,16 @@
$options = array();
}
- // Need to reference some sort of mapping here to turn the UID
- // into a source id and contact id:
- //
- // $source = $uid['addressbook'];
- // $objectId = $uid['contactId'];
- return PEAR::raiseError('Turba needs a UID map');
+ // get default address book from config
+ if (empty($source)) {
+ $source = $prefs->getValue('default_dir');
+ }
if (empty($source) || !isset($cfgSources[$source])) {
return PEAR::raiseError(_("Invalid address book"), 'horde.error', null, null, $source);
}
- if (empty($objectId)) {
+ if (empty($uid)) {
return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
}
@@ -429,7 +462,7 @@
// true. Otherwise, try to delete it and return success or
// failure.
$driver = &Turba_Driver::singleton($source, $cfgSources[$source]);
- $result = $driver->search(array('__key' => $objectId));
+ $result = $driver->search(array('__uid' => $uid));
if (is_a($result, 'PEAR_Error')) {
return $result;
} elseif ($result->count() == 0) {
@@ -475,13 +508,13 @@
*
* @return boolean Success or failure.
*/
-function _turba_delete($uid)
+function _turba_delete($uid, $source = '')
{
// Handle an arrray of UIDs for convenience of deleting multiple
// contacts at once.
if (is_array($uid)) {
foreach ($uid as $g) {
- $result = _turba_delete($uid);
+ $result = _turba_delete($uid, $source);
if (is_a($result, 'PEAR_Error')) {
return $result;
}
@@ -491,20 +524,18 @@
}
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources, $prefs;
- // Need to reference some sort of mapping here to turn the UID
- // into a source id and contact id:
- //
- // $source = $uid['addressbook'];
- // $objectId = $uid['contactId'];
- return PEAR::raiseError('Turba needs a UID map');
+ // get default address book from config
+ if (empty($source)) {
+ $source = $prefs->getValue('default_dir');
+ }
if (empty($source) || !isset($cfgSources[$source])) {
return PEAR::raiseError(_("Invalid address book"), 'horde.error', null, null, $source);
}
- if (empty($objectId)) {
+ if (empty($uid)) {
return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
}
@@ -518,13 +549,16 @@
// true. Otherwise, try to delete it and return success or
// failure.
$driver = &Turba_Driver::singleton($source, $cfgSources[$source]);
- $result = $driver->search(array('__key' => $objectId));
+ $result = $driver->search(array('__uid' => $uid));
if (is_a($result, 'PEAR_Error')) {
return $result;
} elseif ($result->count() == 0) {
return true;
} else {
- return $driver->delete($objectId);
+ // we need to delete by uid rather than by key. So
+ // use _delete instead of delete
+ $result = $driver->_delete($driver->toDriver('__uid'), $uid);
+ return $result;
}
}
@@ -540,39 +574,59 @@
*
* @return boolean Success or failure.
*/
-function _turba_replace($uid, $content, $contentType)
+function _turba_replace($uid, $content, $contentType, $source = '')
{
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources, $prefs;
- // Need to reference some sort of mapping here to turn the UID
- // into a source id and contact id:
- //
- // $source = $uid['addressbook'];
- // $objectId = $uid['contactId'];
- return PEAR::raiseError('Turba needs a UID map');
+ // get default address book from config
+ if (empty($source)) {
+ $source = $prefs->getValue('default_dir');
+ }
if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources) || !isset($cfgSources[$source])) {
return PEAR::raiseError(_("Invalid address book"), 'horde.warning');
}
- if (empty($objectId)) {
+ if (empty($uid)) {
return PEAR::raiseError(_("Invalid objectId"), 'horde.error', null, null, $source);
}
/* Check permissions. */
$driver = &Turba_Driver::singleton($source, $cfgSources[$source]);
- $object = $driver->getObject($objectId);
- if (is_a($object, 'PEAR_Error')) {
- return $object;
- } elseif (!Turba::hasPermission($object, 'object', PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission Denied"), 'horde.warning');
+ $result = $driver->search(array('__uid' => $uid));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif ($result->count() == 0) {
+ return PEAR::raiseError(_("Object not found"), 'horde.error', null, null, $source);
+ return true;
+ } elseif ($result->count() > 1) {
+ return PEAR::raiseError("Internal Horde Error: multiple turba objects with same objectId.", 'horde.error', null, null, $source);
}
+ $object = $result->objects[0];
switch ($contentType) {
case 'array':
break;
+ case 'text/x-vcard':
+ require_once 'Horde/iCalendar.php';
+ $iCal = &new Horde_iCalendar();
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+ switch ($iCal->getComponentCount()) {
+ case 0:
+ return PEAR::raiseError(_("No vCard data was found."));
+ case 1:
+ $content = $iCal->getComponent(0);
+ $content = $driver->toHash($content);
+ break;
+ default:
+ return PEAR::raiseError(_("Only one vcard supported."));
+ }
+ break;
+
default:
return PEAR::raiseError(_("Invalid Content-Type"));
}
More information about the sync
mailing list