[sync] Turba and Syncml patch proposal
Karsten Fourmont
fourmont at gmx.de
Sun Jan 2 10:04:26 PST 2005
Hi,
I adjusted the SyncML to reflect the new style "plain" UIDs (i.e. no
longer kronolith:...)
I ran into a few bumps:
1) turba has the already discussed "which source should I use" problem.
I added an additional optional $source='' parameter to
_turba_{import|delete|replace|export}. If $source is not specified
$conf['client']['addressbook'] is used. This works fine for SyncML. The
attached patch replaces my previous suggestion.
2) the _import functions now return a plain UID. However the history
functions still require a "extended" guid like "memo:karsten:xxxx". This
is a bit of a fracture: most API functions deal with plain UIDs, but
when calling the /listBy Api function, I get an "extended" guid (as it
comes from the datatree). This is an issue for SyncML: after adding a
new entry to the Horde Database, Syncml needs to get the log timestamp
for that action. We need that as we have to manually remove these
entries from /listBy results to avoid duplication by echoing back new
entries to the client. However /import now returns only a plain guid and
I can't feed this to the history object (for timestamp retrival) unless
I add a prefix like 'turba:karsten:'. But this would be a bad solution:
SyncML deals with the external API and only knows 'contacts', not 'turba'.
So what I did is this: History gets a getLastLoggedTS() functions that
returns the timestamp of the last logged action. This value is stored in
a global variable _lastLoggedTS and updated by
DataTreeObject_History::log. See attached patch for History.php.
Initially I tried to made _lastLoggedTS a member var of History, but for
some reason even though History is a singleton, two instances are
created (by the singleton function itself!) during a SyncML-run. Don't
know why.
Attached is a patch for History.php and the appropriate changes to
SyncML. I also included a patch for turba's api (adding the source
parameter, added missing functionality). Is it OK to commit this or is
my history modification too bad a hack?
Anyhow, IMHO the clean solution in the long run would be to expose the
history through the external api:
1) somehow provide the timestamp of add/replace/delete operations. My
suggestion: _import, _delete, _replace etc. should return an hash with
lots of useful information (uid, timestamp, source for turba) instead of
just the uid string.
2) the listBy API-Calls should provide similar information as well.
Instead a simple array of GUIDs they should return an array
of hashes: key is the uid, values are ts,action,user,[source].
As this would be a significant change to the external api I'd like to
hear your opinions about it before going ahead. Any thoughts or objections?
Doing this right may take some time, so the applied patch should do for
the moment.
Cheers
Karsten
-------------- next part --------------
Index: framework/History/History.php
===================================================================
RCS file: /repository/framework/History/History.php,v
retrieving revision 1.28
diff -u -r1.28 History.php
--- framework/History/History.php 7 Dec 2004 16:09:13 -0000 1.28
+++ framework/History/History.php 2 Jan 2005 17:53:46 -0000
@@ -308,6 +308,17 @@
}
/**
+ * Timestamp for last logged action.
+ * Helps retrieve the TS after an update/modify/delete-operation
+ * @return int Timestamp of last logging action
+ */
+
+ function getLastLoggedTS() {
+ global $_lastLoggedTS;
+ return $_lastLoggedTS;
+ }
+
+ /**
* Returns a new history entry object.
*
* @access private
@@ -440,6 +451,10 @@
$attributes['ts'] = time();
}
+ /* save ts in History Object: */
+ global $_lastLoggedTS;
+ $_lastLoggedTS = $attributes['ts'];
+
/* If we want to replace an entry with the same action, try and find
* one. Track whether or not we succeed in $done, so we know whether
* or not to add the entry later. */
Index: framework/SyncML/SyncML/Sync.php
===================================================================
RCS file: /repository/framework/SyncML/SyncML/Sync.php,v
retrieving revision 1.8
diff -u -r1.8 Sync.php
--- framework/SyncML/SyncML/Sync.php 30 Nov 2004 19:59:03 -0000 1.8
+++ framework/SyncML/SyncML/Sync.php 2 Jan 2005 17:53:48 -0000
@@ -103,7 +103,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 = $history->getLastLoggedTS();
if(!$ts) {
Horde::logMessage('SyncML: unable to find add-ts for ' . $guid . ' at ' . $ts, __FILE__, __LINE__, PEAR_LOG_ERROR);
}
@@ -122,7 +122,7 @@
if (!is_a($guid, 'PEAR_Error')) {
$registry->call($hordeType . '/delete', array($guid));
- $ts = $history->getTSforAction($guid, 'delete');
+ $ts = $history->getLastLoggedTS();
$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 +138,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 = $history->getLastLoggedTS();
$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 +155,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 = $history->getLastLoggedTS();
$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.13
diff -u -r1.13 TwoWaySync.php
--- framework/SyncML/SyncML/Sync/TwoWaySync.php 30 Nov 2004 19:59:03 -0000 1.13
+++ framework/SyncML/SyncML/Sync/TwoWaySync.php 2 Jan 2005 17:53:48 -0000
@@ -20,6 +20,14 @@
*/
class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync {
+ /**
+ * remove leading source modifier to deal with
+ * old style guids and guids as returned from history calls
+ */
+ function cleanguid($guid) {
+ return preg_replace('/^.*:/','',$guid);
+ }
+
function endSync($currentCmdID, &$output)
{
global $registry;
@@ -56,6 +64,7 @@
$changes = $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts));
foreach ($changes as $guid) {
$guid_ts = $history->getTSforAction($guid, 'modify');
+ $guid = $this->cleanguid($guid);
$sync_ts = $state->getChangeTS($syncType, $guid);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
@@ -91,6 +100,7 @@
$deletes = $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts));
foreach ($deletes as $guid) {
$guid_ts = $history->getTSforAction($guid, 'delete');
+ $guid = $this->cleanguid($guid);
$sync_ts = $state->getChangeTS($syncType, $guid);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
@@ -116,9 +126,11 @@
// Get adds.
$adds = $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts));
foreach ($adds as $guid) {
+
$guid_ts = $history->getTSforAction($guid, 'add');
+ $guid = $this->cleanguid($guid);
$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: turba/lib/api.php
===================================================================
RCS file: /repository/turba/lib/api.php,v
retrieving revision 1.120
diff -u -r1.120 api.php
--- turba/lib/api.php 13 Dec 2004 23:34:27 -0000 1.120
+++ turba/lib/api.php 2 Jan 2005 17:54:01 -0000
@@ -50,17 +50,17 @@
);
$_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',
);
@@ -309,8 +309,18 @@
function _turba_import($content, $contentType = 'array', $source = '')
{
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources,$conf;
+
+ // get default address book from config
+ if(empty($source)) {
+ $source = $conf['client']['addressbook'];
+ }
+ // 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 +388,7 @@
}
$object = &$driver->getObject($result);
- return $object->getValue('__key');
+ return $object->getValue('__uid');
}
/**
@@ -391,10 +401,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,$conf;
if (is_array($contentType)) {
$options = $contentType;
@@ -404,18 +414,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 = $conf['client']['addressbook'];
+ }
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 +437,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 +483,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 +499,18 @@
}
require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
+ global $cfgSources,$conf;
- // 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 = $conf['client']['addressbook'];
+ }
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 +524,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,38 +549,57 @@
*
* @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,$conf;
- // 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 = $conf['client']['addressbook'];
+ }
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