'; /** * LinksIO constructor. * * @param ConfigManager $conf instance */ public function __construct(ConfigManager $conf, Mutex $mutex = null) { if ($mutex === null) { // This should only happen with legacy classes $mutex = new NoMutex(); } $this->conf = $conf; $this->datastore = $conf->get('resource.datastore'); $this->mutex = $mutex; } /** * Reads database from disk to memory * * @return Bookmark[] * * @throws NotWritableDataStoreException Data couldn't be loaded * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark * @throws DatastoreNotInitializedException File does not exists */ public function read() { if (! file_exists($this->datastore)) { throw new DatastoreNotInitializedException(); } if (!is_writable($this->datastore)) { throw new NotWritableDataStoreException($this->datastore); } $content = null; $this->synchronized(function () use (&$content) { $content = file_get_contents($this->datastore); }); // Note that gzinflate is faster than gzuncompress. // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 $links = unserialize(gzinflate(base64_decode( substr($content, strlen(self::$phpPrefix), -strlen(self::$phpSuffix)) ))); if (empty($links)) { if (filesize($this->datastore) > 100) { throw new NotWritableDataStoreException($this->datastore); } throw new EmptyDataStoreException(); } return $links; } /** * Saves the database from memory to disk * * @param Bookmark[] $links * * @throws NotWritableDataStoreException the datastore is not writable */ public function write($links) { if (is_file($this->datastore) && !is_writeable($this->datastore)) { // The datastore exists but is not writeable throw new NotWritableDataStoreException($this->datastore); } elseif (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { // The datastore does not exist and its parent directory is not writeable throw new NotWritableDataStoreException(dirname($this->datastore)); } $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix; $this->synchronized(function () use ($data) { file_put_contents( $this->datastore, $data ); }); } /** * Wrapper applying mutex to provided function. * If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex. * * @see https://github.com/shaarli/Shaarli/issues/1650 * * @param callable $function */ protected function synchronized(callable $function): void { try { $this->mutex->synchronized($function); } catch (LockAcquireException $exception) { $function(); } } }