From 1baa158195d211386a5225910a288baa3e715912 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sun, 16 Nov 2008 10:42:29 +0000 Subject: [PATCH] Big SyncML patch from Philip Herbert : - change the processing of slowsync, to use the content_map instead of trying to build a new one. This caused duplication issues on the client if multiple similar records where stored, because only the first one found in the server-db was matched, These duplicate entries at client side had no entry at serverside, so deleting the wrong one on the client (the content with a valid map entry) could cause unwanted data loss at server side, because it is impossible for the user to see what is a duplicate, and what is not. see also: http://www.nabble.com/again---syncml-duplication-issue-to20333619s3741.html - reenabled UID from syncml clients, because it was partly used this caused issues during SlowSync if the content was changed. - infolog, calendar if a uid is found in the provided data, allway try to find the corresponding content first using only the UID, instead of using the content-id taken from content_map. also fixed: - a few fixes in ./notes - creating an entry on the client that can not be imported, (Example, Nokia E Series Appointment without a Title) will no longer create an invalid content-map entry However, at client side this is still counted in the Protocol as Server-Add --- addressbook/inc/class.addressbook_sif.inc.php | 5 +++- .../inc/class.addressbook_vcal.inc.php | 6 +++- calendar/inc/class.calendar_ical.inc.php | 24 +++++++++++---- calendar/inc/class.calendar_sif.inc.php | 8 +++-- infolog/inc/class.infolog_ical.inc.php | 30 ++++++++++++++++--- infolog/inc/class.infolog_sif.inc.php | 5 +++- .../inc/horde/Horde/SyncML/Command/Alert.php | 8 +++-- phpgwapi/inc/horde/Horde/SyncML/State.php | 4 +-- phpgwapi/inc/horde/Horde/SyncML/State_egw.php | 29 +++++++++++++----- .../inc/horde/Horde/SyncML/Sync/SlowSync.php | 13 ++++---- 10 files changed, 99 insertions(+), 33 deletions(-) diff --git a/addressbook/inc/class.addressbook_sif.inc.php b/addressbook/inc/class.addressbook_sif.inc.php index eb109b54c8..e527a242cc 100644 --- a/addressbook/inc/class.addressbook_sif.inc.php +++ b/addressbook/inc/class.addressbook_sif.inc.php @@ -164,12 +164,15 @@ class addressbook_sif extends addressbook_bo * @param string $_sifdata * @return boolean/int/string contact-id or false, if not found */ - function search($_sifdata) + function search($_sifdata,$contentID=null) { if(!$contact = $this->siftoegw($_sifdata)) { return false; } + if ($contentID) { + $contact['contact_id'] = $contentID; + } // patch from Di Guest says: we need to ignore the n_fileas unset($contact['n_fileas']); // we probably need to ignore even more as we do in vcaladdressbook diff --git a/addressbook/inc/class.addressbook_vcal.inc.php b/addressbook/inc/class.addressbook_vcal.inc.php index 2770e16c60..bf7fefed9d 100644 --- a/addressbook/inc/class.addressbook_vcal.inc.php +++ b/addressbook/inc/class.addressbook_vcal.inc.php @@ -170,12 +170,16 @@ class addressbook_vcal extends addressbook_bo return $result; } - function search($_vcard) + function search($_vcard, $contentID=null) { if(!($contact = $this->vcardtoegw($_vcard))) { return false; } + if ($contentID) { + $contact['contact_id'] = $contentID; + } + unset($contact['private']); unset($contact['note']); unset($contact['n_fn']); diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index 241170a76b..a95663930f 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -169,7 +169,6 @@ class calendar_ical extends calendar_boupdate $event['start'] = $event['start'] + $DSTCorrection; $event['end'] = $event['end'] + $DSTCorrection; */ - $eventGUID = $GLOBALS['egw']->common->generate_uid('calendar',$event['id']); $vevent = Horde_iCalendar::newComponent('VEVENT',$vcal); $parameters = $attributes = array(); @@ -440,7 +439,7 @@ class calendar_ical extends calendar_boupdate } } - $attributes['UID'] = $event['uid']; + //$attributes['UID'] = $event['uid']; foreach($attributes as $key => $value) { foreach(is_array($value)&&$parameters[$key]['VALUE']!='DATE' ? $value : array($value) as $valueID => $valueData) @@ -490,7 +489,7 @@ class calendar_ical extends calendar_boupdate } $version = $vcal->getAttribute('VERSION'); - + if(!is_array($this->supportedFields)) { $this->setSupportedFields(); @@ -1298,7 +1297,6 @@ class calendar_ical extends calendar_boupdate $vcardData['end'] = $attributes['value']; break; case 'DTSTART': - //error_log (" CALENDAR ROBV DTSTART: ". $attributes['value']); $vcardData['start'] = $attributes['value']; break; case 'LOCATION': @@ -1405,7 +1403,13 @@ class calendar_ical extends calendar_boupdate $event['id'] = $uid_event['id']; unset($uid_event); } - break; + // not use weak uids that might come from syncml clients + if (isset($event['uid']) && (strlen($event['uid']) < 20 || is_numeric($event['uid']))) + { + error_log ("unset weak uid"); + unset ($event['uid']); + } + break; case 'TRANSP': $vcardData['non_blocking'] = $attributes['value'] == 'TRANSPARENT'; break; @@ -1516,17 +1520,25 @@ class calendar_ical extends calendar_boupdate return false; } - function search($_vcalData) + function search($_vcalData, $contentID=null) { if(!$event = $this->icaltoegw($_vcalData)) { return false; } + if ($event['uid'] && ($uidmatch = $this->read($event['uid']))) + { + return $uidmatch['id']; + } $query = array( 'cal_start='.$this->date2ts($event['start'],true), // true = Server-time 'cal_end='.$this->date2ts($event['end'],true), ); + if ($contentID) { + $query[] = 'egw_cal.cal_id='.(int)$contentID; + } + #foreach(array('title','location','priority','public','non_blocking') as $name) { foreach(array('title','location','public','non_blocking') as $name) { if (isset($event[$name])) $query['cal_'.$name] = $event[$name]; diff --git a/calendar/inc/class.calendar_sif.inc.php b/calendar/inc/class.calendar_sif.inc.php index 7e63ee9d98..f47a8ef9e6 100644 --- a/calendar/inc/class.calendar_sif.inc.php +++ b/calendar/inc/class.calendar_sif.inc.php @@ -205,7 +205,7 @@ class calendar_sif extends calendar_boupdate return $finalEvent; } - function search($_sifdata) { + function search($_sifdata, $contentID=null) { if(!$event = $this->siftoegw($_sifdata)) { return false; } @@ -214,7 +214,11 @@ class calendar_sif extends calendar_boupdate 'cal_start='.$this->date2ts($event['start'],true), // true = Server-time 'cal_end='.$this->date2ts($event['end'],true), ); - + + if ($contentID) { + $query[] = 'egw_cal.cal_id='.(int)$contentID; + } + #foreach(array('title','location','priority','public','non_blocking') as $name) { foreach(array('title','location','public','non_blocking') as $name) { if (isset($event[$name])) $query['cal_'.$name] = $event[$name]; diff --git a/infolog/inc/class.infolog_ical.inc.php b/infolog/inc/class.infolog_ical.inc.php index fe6f535db1..d2dc783646 100644 --- a/infolog/inc/class.infolog_ical.inc.php +++ b/infolog/inc/class.infolog_ical.inc.php @@ -128,11 +128,22 @@ class infolog_ical extends infolog_bo return $this->write($taskData); } - function searchVTODO($_vcalData) + function searchVTODO($_vcalData, $contentID=null) { if(!$egwData = $this->vtodotoegw($_vcalData)) { return false; } + + $myfilter = array('col_filter' => array('info_uid'=>$egwData['info_uid'])) ; + if ($egwData['info_uid'] && ($found=parent::search($myfilter)) && ($uidmatch = array_shift($found))) + { + return $uidmatch['info_id']; + }; + unset($egwData['info_uid']); + + if ($contentID) { + $egwData['info_id'] = $contentID; + } #unset($egwData['info_priority']); @@ -223,6 +234,12 @@ class infolog_ical extends infolog_bo $taskData['info_id'] = $uid_task['id']; unset($uid_task); } + // not use weak uids that might come from syncml clients + if (isset($event['uid']) && (strlen($event['uid']) < 20 || is_numeric($event['uid']))) + { + unset ($event['uid']); + } + break; case 'PERCENT-COMPLETE': $taskData['info_percent'] = (int) $attributes['value']; @@ -300,13 +317,16 @@ class infolog_ical extends infolog_bo return $this->write($note); } - function searchVNOTE($_vcalData, $_type) + function searchVNOTE($_vcalData, $_type, $contentID=null) { - if(!$note = $this->vnotetoegw($_vcalData)) { + if(!$note = $this->vnotetoegw($_vcalData,$_type)) { return false; } + if ($contentID) { + $note['info_id'] = $contentID; + } - $filter = array('col_filter' => $egwData); + $filter = array('col_filter' => $note); if($foundItems = $this->search($filter)) { if(count($foundItems) > 0) { $itemIDs = array_keys($foundItems); @@ -335,6 +355,8 @@ class infolog_ical extends infolog_bo } else { + // should better be imported as subject, but causes duplicates + // TODO: should be examined $note['info_des'] = $txt; } diff --git a/infolog/inc/class.infolog_sif.inc.php b/infolog/inc/class.infolog_sif.inc.php index de71f99d52..f595c80545 100644 --- a/infolog/inc/class.infolog_sif.inc.php +++ b/infolog/inc/class.infolog_sif.inc.php @@ -243,10 +243,13 @@ class infolog_sif extends infolog_bo } } - function searchSIF($_sifData, $_sifType) { + function searchSIF($_sifData, $_sifType, $contentID=null) { if(!$egwData = $this->siftoegw($_sifData, $_sifType)) { return false; } + if ($contentID) { + $egwData['info_id'] = $contentID; + } $filter = array('col_filter' => $egwData); if($foundItems = $this->search($filter)) { diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php index 01c2304d8a..cc5c90d089 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Alert.php @@ -112,9 +112,9 @@ class Horde_SyncML_Command_Alert extends Horde_SyncML_Command { Horde::logMessage("SyncML: Anchor match, TwoWaySync since " . $clientlast, __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { Horde::logMessage("SyncML: Anchor mismatch, enforcing SlowSync clientlast $clientlast serverlast ".$this->_metaAnchorLast, __FILE__, __LINE__, PEAR_LOG_DEBUG); - // Mismatch, enforce slow sync. + // Mismatch, enforce slow sync. 508=RESPONSE_REFRESH_REQUIRED 201=ALERT_SLOW_SYNC $this->_alert = 201; - $code = 508; + $code = 508; // create new synctype $sync = &Horde_SyncML_Sync::factory($this->_alert); $sync->_targetLocURI = $this->_targetLocURI; @@ -122,7 +122,9 @@ class Horde_SyncML_Command_Alert extends Horde_SyncML_Command { if(isset($this->_targetLocURIParameters)) $sync->_targetLocURIParameters = $this->_targetLocURIParameters; $state->setSync($this->_targetLocURI, $sync); - $state->removeAllUID($this->_targetLocURI); + // PH : no longer delete entire content_map entries for client before SlowSync, + // use content_map to verify if content is mapped correctly + //$state->removeAllUID($this->_targetLocURI); } $status = &new Horde_SyncML_Command_Status($code, 'Alert'); diff --git a/phpgwapi/inc/horde/Horde/SyncML/State.php b/phpgwapi/inc/horde/Horde/SyncML/State.php index acd4c4578e..cdda954520 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State.php @@ -1000,7 +1000,7 @@ class Horde_SyncML_State { case 'text/x-vcalendar': case 'text/x-vevent': case 'text/x-vtodo': - $content = preg_replace('/^UID:.*\n/m', '', $content, 1); + // $content = preg_replace('/^UID:.*\n/m', '', $content, 1); break; } @@ -1023,7 +1023,7 @@ class Horde_SyncML_State { case 'text/x-vcalendar': case 'text/x-vevent': case 'text/x-vtodo': - $content = preg_replace('/^UID:.*\n/m', '', $content, 1); + // $content = preg_replace('/^UID:.*\n/m', '', $content, 1); break; } diff --git a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php index 185afda0ec..3e5be1abdc 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php @@ -301,6 +301,25 @@ class EGW_SyncML_State extends Horde_SyncML_State $GLOBALS['egw']->db->delete('egw_contentmap', array('map_id' => $mapID), __LINE__, __FILE__, 'syncml'); + return true; + } + + /** + * Used in SlowSync + * Removes all locid<->guid mappings for the given type, + * that are older than $ts. + * + * Returns always true. + */ + function removeOldUID($type, $ts) + { + $mapID = $this->_locName . $this->_sourceURI . $type; + $where[] = "map_id = '".$mapID."' AND map_timestamp < '".$GLOBALS['egw']->db->to_timestamp($ts)."'"; + + Horde::logMessage("SyncML: state->removeOldUID(type=$type)", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml'); + return true; } @@ -325,7 +344,7 @@ class EGW_SyncML_State extends Horde_SyncML_State } Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); - + $GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml'); return $guid; @@ -344,13 +363,7 @@ class EGW_SyncML_State extends Horde_SyncML_State { #Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG); #Horde::logMessage("SyncML: setUID ". $this->getUIDMapping($guid), __FILE__, __LINE__, PEAR_LOG_DEBUG); - // fix $guid, it maybe was to long for some devices - // format is appname-id-systemid - #$guidParts = explode('-',$guid); - #if(count($guidParts) == 3) { - # $guid = $GLOBALS['egw']->common->generate_uid($guidParts[0],$guidParts[1]); - #} - + // problem: entries created from client, come here with the (long) server guid, // but getUIDMapping does not know them and can not map server-guid <--> client guid $guid = $this->getUIDMapping($_guid); diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php index a05f5b6259..68485d2c17 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -25,10 +25,12 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { $history = $GLOBALS['egw']->contenthistory; $state = &$_SESSION['SyncML.state']; - $adds = &$state->getAddedItems($hordeType); - - Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); $serverAnchorNext = $state->getServerAnchorNext($syncType); + + // now we remove all UID from contentmap that have not been verified in this slowsync + $state->removeOldUID($syncType, $serverAnchorNext); + $adds = &$state->getAddedItems($hordeType); + Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); $counter = 0; if(is_array($adds)) { @@ -147,7 +149,7 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { $guid = false; $guid = $registry->call($hordeType . '/search', - array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType, $state->getGlobalUID($type, $syncItem->getLocURI()) )); if ($guid) { # entry exists in database already. Just update the mapping @@ -156,10 +158,11 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { $state->log("Client-Replace"); } else { # Entry does not exist in database: add a new one. + $state->removeUID($type, $syncItem->getLocURI()); Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); $guid = $registry->call($hordeType . '/import', array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); - if (!is_a($guid, 'PEAR_Error')) { + if (!is_a($guid, 'PEAR_Error') && $guid != false) { $ts = $state->getSyncTSforAction($guid, 'add'); $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); $state->log("Client-AddReplace");