Commit a9650886 authored by Sebastian Kummer's avatar Sebastian Kummer

Merging in latest from upstream (ZP/z-push:refs/heads/develop)

* commit '7cdb51e4':
  ZP-832 Run SyncCollections->CheckForChanges() stat comparing only on first run, improved comment.
  ZP-832 Add check if stats table can be loaded for shared users - log to INFO if that does not work, sync folders where no stat can be found only once a hour, include PR_CONTENT_COUNT, PR_CONTENT_UNREAD and PR_DELETED_MSG_COUNT into stat to reflect all kind of changes, SyncCollection->CheckForChanges() compares folderstats for changes when initiating, in case of error remove the folderstat to ensure folder is synchronized next Sync request, remove 'savestate' functionality as it is not used.
  ZP-833 Use http socket as default MAPI_SERVER.
  ZP-834 Remove SINK_FORCERECHECK configuration option and implementation.
  ZP-832 Request folder statistics from the backend and setup the exporter only if the stat indicate that the folder contains changes. Per default the statistics are disabled (backend has to enable them). If disabled, an exporter will be setup for each folder.
  ZP-832 Log GlobalWindow size override only once.
  ZP-818 Omit folder output on unchanged folders for AS 14.0 and higher.
  ZP-831 Combined - typo variable name. Released under the Affero GNU General Public License (AGPL) version 3.
  ZP-830 Combined; minor formatting fixes. Released under the Affero GNU General Public License (AGPL) version 3.
  ZP-829 CardDAV mappings for phone numbers. Released under the Affero GNU General Public License (AGPL) version 3.
  ZP-818 Supress collection containers if there are no changes for a folder, refactored code having the actual export and streaming of data in a private method.
parents 80899b1e 7cdb51e4
......@@ -850,11 +850,11 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider {
else {
$items[$rc][SYNC_GAL_LASTNAME] = "";
}
if (isset($contact->business2phonenumber)) {
$items[$rc][SYNC_GAL_PHONE] = $contact->business2phonenumber;
if (isset($contact->businessphonenumber)) {
$items[$rc][SYNC_GAL_PHONE] = $contact->businessphonenumber;
}
if (isset($contact->home2phonenumber)) {
$items[$rc][SYNC_GAL_HOMEPHONE] = $contact->home2phonenumber;
if (isset($contact->homephonenumber)) {
$items[$rc][SYNC_GAL_HOMEPHONE] = $contact->homephonenumber;
}
if (isset($contact->mobilephonenumber)) {
$items[$rc][SYNC_GAL_MOBILEPHONE] = $contact->mobilephonenumber;
......
......@@ -533,21 +533,9 @@ class BackendCombined extends Backend implements ISearchProvider {
$pos = strpos($folderid, $this->config['delimiter']);
if($pos === false)
return false;
return substr($folderid,0,$pos);
return substr($folderid, 0, $pos);
}
/**
* Returns the BackendCombined as it implements the ISearchProvider interface
* This could be overwritten by the global configuration
*
* @access public
* @return object Implementation of ISearchProvider
*/
public function GetSearchProvider() {
return $this;
}
/**
* Indicates which AS version is supported by the backend.
* Return the lowest version supported by the backends used.
......@@ -566,6 +554,17 @@ class BackendCombined extends Backend implements ISearchProvider {
return $version;
}
/**
* Returns the BackendCombined as it implements the ISearchProvider interface
* This could be overwritten by the global configuration
*
* @access public
* @return object Implementation of ISearchProvider
*/
public function GetSearchProvider() {
return $this;
}
/*-----------------------------------------------------------------------------------------
-- ISearchProvider
......@@ -688,5 +687,4 @@ class BackendCombined extends Backend implements ISearchProvider {
return false;
}
}
......@@ -173,7 +173,7 @@ class ExportChangesCombined implements IExportChanges {
public function InitializeExporter(&$importer) {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)");
foreach ($this->exporters as $i => $e) {
if(!isset($this->_importwraps[$i])){
if(!isset($this->importwraps[$i])){
$this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer);
}
$e->InitializeExporter($this->importwraps[$i]);
......
......@@ -169,7 +169,7 @@ class ImportChangesCombined implements IImportChanges {
public function ImportFolderChange($folder) {
$id = $folder->serverid;
$parent = $folder->parentid;
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ImportFolderChange() ".print_r($folder, 1));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportFolderChange() id: '%s', parent: '%s'", $id, $parent));
if($parent == '0') {
if($id) {
$backendid = $this->backend->GetBackendId($id);
......
......@@ -2,11 +2,11 @@
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : VCardDir backend configuration file
* Descr : Zarafa backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
* Copyright 2007 - 2016 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
......@@ -46,4 +46,4 @@
// ************************
// Defines the server to which we want to connect
define('MAPI_SERVER', 'file:///var/run/zarafa');
define('MAPI_SERVER', 'http://127.0.0.1:236/zarafa');
......@@ -1252,3 +1252,6 @@ define('PR_ZC_CONTACT_FOLDER_NAMES' ,mapi_prop_tag(PT_MV_TSTRI
//Properties defined for Z-Push
define('PR_TODO_ITEM_FLAGS' ,mapi_prop_tag(PT_LONG, 0x0E2B));
define('PR_LOCAL_COMMIT_TIME_MAX' ,mapi_prop_tag(PT_SYSTIME, 0x670A));
define('PR_DELETED_MSG_COUNT' ,mapi_prop_tag(PT_LONG, 0x6640));
......@@ -89,6 +89,7 @@ class BackendZarafa implements IBackend, ISearchProvider {
private $changesSinkStores;
private $wastebasket;
private $addressbook;
private $folderStatCache;
// ZCP config parameter for PR_EC_ENABLED_FEATURES / PR_EC_DISABLED_FEATURES
const ZPUSH_ENABLED = 'mobile';
......@@ -115,6 +116,7 @@ class BackendZarafa implements IBackend, ISearchProvider {
$this->changesSinkStores = array();
$this->wastebasket = false;
$this->session = false;
$this->folderStatCache = array();
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendZarafa using PHP-MAPI version: %s", phpversion("mapi")));
}
......@@ -1316,6 +1318,80 @@ class BackendZarafa implements IBackend, ISearchProvider {
return $this->storeName;
}
/**
* Indicates if the Backend supports folder statistics.
*
* @access public
* @return boolean
*/
public function HasFolderStats() {
return true;
}
/**
* Returns a status indication of the folder.
* If there are changes in the folder, the returned value must change.
* The returned values are compared with '===' to determine if a folder needs synchronization or not.
*
* @param string $store the store where the folder resides
* @param string $folderid the folder id
*
* @access public
* @return string
*/
public function GetFolderStat($store, $folderid) {
list($user, $domain) = Utils::SplitDomainUser($store);
if ($user === false) {
$user = $this->mainUser;
}
if (!isset($this->folderStatCache[$user])) {
$this->folderStatCache[$user] = array();
}
// TODO remove nameCache
if (!isset($this->nameCache))
$this->nameCache = array();
// if there is nothing in the cache for a store, load the data for all folders of it
if (empty($this->folderStatCache[$user])) {
// get the store
$userstore = $this->openMessageStore($user);
$rootfolder = mapi_msgstore_openentry($userstore);
$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
$rows = mapi_table_queryallrows($hierarchy, array(PR_SOURCE_KEY, PR_LOCAL_COMMIT_TIME_MAX, PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_DELETED_MSG_COUNT, PR_DISPLAY_NAME));
if (count($rows) == 0) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ZarafaBackend->GetFolderStat(): could not access folder statistics for user '%s'. Probably missing 'read' permissions on the root folder! Folders of this store will be synchronized ONCE per hour only!", $user));
}
foreach($rows as $folder) {
$commit_time = isset($folder[PR_LOCAL_COMMIT_TIME_MAX])? $folder[PR_LOCAL_COMMIT_TIME_MAX] : "0000000000";
$content_count = isset($folder[PR_CONTENT_COUNT])? $folder[PR_CONTENT_COUNT] : -1;
$content_unread = isset($folder[PR_CONTENT_UNREAD])? $folder[PR_CONTENT_UNREAD] : -1;
$content_deleted = isset($folder[PR_DELETED_MSG_COUNT])? $folder[PR_DELETED_MSG_COUNT] : -1;
$this->folderStatCache[$user][bin2hex($folder[PR_SOURCE_KEY])] = $commit_time ."/". $content_count ."/". $content_unread ."/". $content_deleted;
$this->nameCache[bin2hex($folder[PR_SOURCE_KEY])] = $folder[PR_DISPLAY_NAME];
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->GetFolderStat() fetched status information of %d folders for store '%s'", count($this->folderStatCache[$user]), $user));
// TODO remove logging
foreach($this->folderStatCache[$user] as $fid => $stat) {
ZLog::Write(LOGLEVEL_INFO, sprintf("FolderStat: %s %s %s\t%s", $user, $fid, $stat, $this->nameCache[$fid]));
}
}
if (isset($this->folderStatCache[$user][$folderid])) {
// TODO remove nameCache output
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->GetFolderStat() found stat for '%s': %s", $this->nameCache[$folderid], $this->folderStatCache[$user][$folderid]));
return $this->folderStatCache[$user][$folderid];
}
else {
// a timestamp that changes once per hour is returned in case there is no data found for this folder. It will be synchronized only once per hour.
return gmdate("Y-m-d-H");
}
}
/**----------------------------------------------------------------------------------------------------------
* Private methods
*/
......
......@@ -165,11 +165,6 @@
// a higher value if you have a high load on the server.
define('PING_INTERVAL', 30);
// Interval in seconds to force a re-check of potentially missed notifications when
// using a changes sink. Default are 300 seconds (every 5 min).
// This can also be disabled by setting it to false
define('SINK_FORCERECHECK', 300);
// Set the fileas (save as) order for contacts in the webaccess/webapp/outlook.
// It will only affect new/modified contacts on the mobile which then are synced to the server.
// Possible values are:
......
......@@ -70,6 +70,7 @@ class SyncCollections implements Iterator {
private $lastSyncTime;
private $waitingTime = 0;
private $loggedGlobalWindowSizeOverwrite = false;
/**
......@@ -368,7 +369,10 @@ class SyncCollections implements Iterator {
}
if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $globalWindowSize) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->GetGlobalWindowSize() overwriting requested global window size of %d by %d forced in configuration.", $globalWindowSize, SYNC_MAX_ITEMS));
if (!$this->loggedGlobalWindowSizeOverwrite) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->GetGlobalWindowSize() overwriting requested global window size of %d by %d forced in configuration.", $globalWindowSize, SYNC_MAX_ITEMS));
$this->loggedGlobalWindowSizeOverwrite = true;
}
$globalWindowSize = SYNC_MAX_ITEMS;
}
......@@ -454,31 +458,43 @@ class SyncCollections implements Iterator {
$pingTracking = new PingTracking();
$this->changes = array();
$changesAvailable = false;
ZPush::GetDeviceManager()->AnnounceProcessAsPush();
ZPush::GetTopCollector()->AnnounceInformation(sprintf("lifetime %ds", $lifetime), true);
ZLog::Write(LOGLEVEL_INFO, sprintf("SyncCollections->CheckForChanges(): Waiting for %s changes... (lifetime %d seconds)", (empty($classes))?'policy':'store', $lifetime));
// use changes sink where available
$changesSink = false;
$forceRealExport = 0;
// do not create changessink if there are no folders
if (!empty($classes) && ZPush::GetBackend()->HasChangesSink()) {
$changesSink = true;
$changesSink = ZPush::GetBackend()->HasChangesSink();
// create changessink and check folder stats if there are folders to Ping
if (!empty($classes)) {
// initialize all possible folders
foreach ($this->collections as $folderid => $spa) {
if ($onlyPingable && $spa->GetPingableFlag() !== true)
continue;
// switch user store if this is a additional folder and initialize sink
ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid));
if (! ZPush::GetBackend()->ChangesSinkInitialize($folderid))
throw new StatusException(sprintf("Error initializing ChangesSink for folder id '%s'", $folderid), self::ERROR_WRONG_HIERARCHY);
// get the user store if this is a additional folder
$store = ZPush::GetAdditionalSyncFolderStore($folderid);
// initialize sink if no immediate changes were found so far
if ($changesSink && empty($this->changes)) {
ZPush::GetBackend()->Setup($store);
if (! ZPush::GetBackend()->ChangesSinkInitialize($folderid))
throw new StatusException(sprintf("Error initializing ChangesSink for folder id '%s'", $folderid), self::ERROR_WRONG_HIERARCHY);
}
// check if the folder stat changed since the last sync, if so generate a change for it (only on first run)
if ($this->waitingTime == 0 && ZPush::GetBackend()->HasFolderStats() && $spa->HasFolderStat() && ZPush::GetBackend()->GetFolderStat($store, $spa->GetFolderId()) !== $spa->GetFolderStat()) {
$this->changes[$spa->GetFolderId()] = 1;
}
}
}
if (!empty($this->changes)) {
ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found changes verifying the folder stats");
return true;
}
// wait for changes
$started = time();
$endat = time() + $lifetime;
......@@ -523,16 +539,6 @@ class SyncCollections implements Iterator {
// Use changes sink if available
if ($changesSink) {
// in some occasions we do realize a full export to see if there are pending changes
// every 5 minutes this is also done to see if there were "missed" notifications
if (SINK_FORCERECHECK !== false && $forceRealExport+SINK_FORCERECHECK <= $now) {
if ($this->CountChanges($onlyPingable)) {
ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found relevant changes on regular export");
return true;
}
$forceRealExport = $now;
}
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Sink %d/%ds on %s", ($now-$started), $lifetime, $checkClasses));
$notifications = ZPush::GetBackend()->ChangesSink($nextInterval);
......@@ -632,28 +638,23 @@ class SyncCollections implements Iterator {
if ($ste->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) {
ZLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): exporter can not be re-configured due to state error, emulating change in folder to force Sync.");
$this->changes[$folderid] = 1;
// make sure this folder is fully synched on next Sync request
if($spa->HasFolderStat()) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CountChange(): removing folder stat '%s' for folderid '%s'", $spa->GetFolderStat(), $spa->GetFolderId()));
$spa->DelFolderStat();
$this->SaveCollection($spa);
}
return true;
}
throw new StatusException("SyncCollections->CountChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
}
// start over if exporter can not be configured atm
if ($changecount === false )
if ($changecount === false)
ZLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): no changes received from Exporter.");
$this->changes[$folderid] = $changecount;
if(isset($this->addparms[$folderid]['savestate'])) {
try {
// Discard any data
while(is_array($exporter->Synchronize()));
$this->addparms[$folderid]['savestate'] = $exporter->GetState();
}
catch (StatusException $ste) {
throw new StatusException("SyncCollections->CountChange(): could not get new state from exporter", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
}
}
return ($changecount > 0);
}
......
......@@ -71,6 +71,7 @@ class SyncParameters extends StateObject {
'contentparameters' => array(),
'foldersynctotal' => false,
'foldersyncremaining' => false,
'folderstat' => false,
);
/**
......
......@@ -301,12 +301,6 @@ class ZPush {
if (!is_array($specialLogUsers))
throw new FatalMisconfigurationException("The WBXML log users is not an array.");
if (!defined('SINK_FORCERECHECK')) {
define('SINK_FORCERECHECK', 300);
}
else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1))
throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0.");
if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
}
......
......@@ -238,6 +238,34 @@ abstract class Backend implements IBackend {
return $this->GetUserDetails(Request::GetAuthUser());
}
/**
* Indicates if the Backend supports folder statistics.
*
* @access public
* @return boolean
*/
public function HasFolderStats() {
return false;
}
/**
* Returns a status indication of the folder.
* If there are changes in the folder, the returned value must change.
* The returned values are compared with '===' to determine if a folder needs synchronization or not.
*
* @param string $store the store where the folder resides
* @param string $folderid the folder id
*
* @access public
* @return string
*/
public function GetFolderStat($store, $folderid) {
// As this is not implemented, the value returned will change every hour.
// This will only be called if HasFolderStats() returns true.
return "not implemented-".gmdate("Y-m-d-H");
}
/**----------------------------------------------------------------------------------------------------------
* Protected methods for BackendStorage
*
......
......@@ -307,4 +307,25 @@ interface IBackend {
* @return Array
*/
public function GetCurrentUsername();
/**
* Indicates if the Backend supports folder statistics.
*
* @access public
* @return boolean
*/
public function HasFolderStats();
/**
* Returns a status indication of the folder.
* If there are changes in the folder, the returned value must change.
* The returned values are compared with '===' to determine if a folder needs synchronization or not.
*
* @param string $store the store where the folder resides
* @param string $folderid the folder id
*
* @access public
* @return string
*/
public function GetFolderStat($store, $folderid);
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment