public function updated() { if (empty($_REQUEST['lastsync'])) { $this->error(400, 'NO_LAST_SYNC_TIME', 'Last sync time not provided'); } $lastsync = false; if (is_numeric($_REQUEST['lastsync'])) { $lastsync = (int) $_REQUEST['lastsync']; } else { $this->error(400, 'INVALID_LAST_SYNC_TIME', 'Last sync time is invalid'); } $this->sessionCheck(); if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) { require_once '../model/ToolkitVersionComparator.inc.php'; if (ToolkitVersionComparator::compare($_SERVER['HTTP_X_ZOTERO_VERSION'], "2.0.4") < 0) { $futureUsers = Z_Core::$MC->get('futureUsers'); if (!$futureUsers) { $futureUsers = Zotero_DB::columnQuery("SELECT userID FROM futureUsers"); Z_Core::$MC->set('futureUsers', $futureUsers, 1800); } if (in_array($this->userID, $futureUsers)) { Z_Core::logError("Blocking sync for future user " . $this->userID . " with version " . $_SERVER['HTTP_X_ZOTERO_VERSION']); $upgradeMessage = "Due to improvements made to sync functionality, you must upgrade to Zotero 2.0.6 or later (via Firefox's Tools menu -> Add-ons -> Extensions -> Find Updates or from zotero.org) to continue syncing your Zotero library."; $this->error(400, 'UPGRADE_REQUIRED', $upgradeMessage); } } } $doc = new DOMDocument(); $domResponse = dom_import_simplexml($this->responseXML); $domResponse = $doc->importNode($domResponse, true); $doc->appendChild($domResponse); try { $result = Zotero_Sync::getSessionDownloadResult($this->sessionID); } catch (Exception $e) { $this->handleUpdatedError($e); } // XML response if (is_string($result)) { $this->clearWaitTime($this->sessionID); $this->responseXML = new SimpleXMLElement($result); $this->end(); } // Queued if ($result === false) { $queued = $this->responseXML->addChild('locked'); $queued['wait'] = $this->getWaitTime($this->sessionID); $this->end(); } // Not queued if ($result == -1) { // See if we're locked Zotero_DB::beginTransaction(); if (Zotero_Sync::userIsWriteLocked($this->userID) || !empty($_REQUEST['upload']) && Zotero_Sync::userIsReadLocked($this->userID)) { Zotero_DB::commit(); $locked = $this->responseXML->addChild('locked'); $locked['wait'] = $this->getWaitTime($this->sessionID); $this->end(); } Zotero_DB::commit(); $queue = true; if (Z_ENV_TESTING_SITE && !empty($_GET['noqueue'])) { $queue = false; } // TEMP $cacheKeyExtra = (!empty($_POST['ft']) ? json_encode($_POST['ft']) : "") . (!empty($_POST['ftkeys']) ? json_encode($_POST['ftkeys']) : ""); // If we have a cached response, return that try { $startedTimestamp = microtime(true); $cached = Zotero_Sync::getCachedDownload($this->userID, $lastsync, $this->apiVersion, $cacheKeyExtra); // Not locked, so clear wait index $this->clearWaitTime($this->sessionID); if ($cached) { $this->responseXML = simplexml_load_string($cached, "SimpleXMLElement", LIBXML_COMPACT | LIBXML_PARSEHUGE); // TEMP if (!$this->responseXML) { error_log("Invalid cached XML data -- stripping control characters"); // Strip control characters in XML data $cached = preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/', '', $cached); $this->responseXML = simplexml_load_string($cached, "SimpleXMLElement", LIBXML_COMPACT | LIBXML_PARSEHUGE); } $duration = round((double) microtime(true) - $startedTimestamp, 2); Zotero_Sync::logDownload($this->userID, round($lastsync), strlen($cached), $this->ipAddress ? $this->ipAddress : 0, 0, $duration, $duration, (int) (!$this->responseXML)); StatsD::increment("sync.process.download.cache.hit"); if (!$this->responseXML) { $msg = "Error parsing cached XML for user " . $this->userID; error_log($msg); $this->handleUpdatedError(new Exception($msg)); } $this->end(); } } catch (Exception $e) { $msg = $e->getMessage(); if (strpos($msg, "Too many connections") !== false) { $msg = "'Too many connections' from MySQL"; } else { $msg = "'{$msg}'"; } Z_Core::logError("Warning: {$msg} getting cached download"); StatsD::increment("sync.process.download.cache.error"); } try { $num = Zotero_Items::countUpdated($this->userID, $lastsync, 5); } catch (Exception $e) { // We can get a MySQL lock timeout here if the upload starts // after the write lock check above but before we get here $this->handleUpdatedError($e); } // If nothing updated, or if just a few objects and processing is enabled, process synchronously if ($num == 0 || $num < 5 && Z_CONFIG::$PROCESSORS_ENABLED) { $queue = false; } $params = []; if (isset($_POST['ft'])) { $params['ft'] = $_POST['ft']; } if (isset($_POST['ftkeys'])) { $queue = true; $params['ftkeys'] = $_POST['ftkeys']; } if ($queue) { Zotero_Sync::queueDownload($this->userID, $this->sessionID, $lastsync, $this->apiVersion, $num, $params); try { Zotero_Processors::notifyProcessors('download'); } catch (Exception $e) { Z_Core::logError($e); } $locked = $this->responseXML->addChild('locked'); $locked['wait'] = 1000; } else { try { Zotero_Sync::processDownload($this->userID, $lastsync, $doc, $params); $this->responseXML = simplexml_import_dom($doc); StatsD::increment("sync.process.download.immediate.success"); } catch (Exception $e) { StatsD::increment("sync.process.download.immediate.error"); $this->handleUpdatedError($e); } } $this->end(); } throw new Exception("Unexpected session result {$result}"); }