3 declare(strict_types
=1);
5 namespace Shaarli\Bookmark
;
7 use malkusch\lock\exception\LockAcquireException
;
8 use malkusch\lock\mutex\Mutex
;
9 use malkusch\lock\mutex\NoMutex
;
10 use Shaarli\Bookmark\Exception\DatastoreNotInitializedException
;
11 use Shaarli\Bookmark\Exception\EmptyDataStoreException
;
12 use Shaarli\Bookmark\Exception\NotWritableDataStoreException
;
13 use Shaarli\Config\ConfigManager
;
18 * This class performs read/write operation to the file data store.
19 * Used by BookmarkFileService.
21 * @package Shaarli\Bookmark
26 * @var string Datastore file path
31 * @var ConfigManager instance
40 * string Datastore PHP prefix
42 protected static $phpPrefix = '<?php /* ';
44 * string Datastore PHP suffix
46 protected static $phpSuffix = ' */ ?>';
49 * LinksIO constructor.
51 * @param ConfigManager $conf instance
53 public function __construct(ConfigManager
$conf, Mutex
$mutex = null)
55 if ($mutex === null) {
56 // This should only happen with legacy classes
57 $mutex = new NoMutex();
60 $this->datastore
= $conf->get('resource.datastore');
61 $this->mutex
= $mutex;
65 * Reads database from disk to memory
69 * @throws NotWritableDataStoreException Data couldn't be loaded
70 * @throws EmptyDataStoreException Datastore file exists but does not contain any bookmark
71 * @throws DatastoreNotInitializedException File does not exists
73 public function read()
75 if (! file_exists($this->datastore
)) {
76 throw new DatastoreNotInitializedException();
79 if (!is_writable($this->datastore
)) {
80 throw new NotWritableDataStoreException($this->datastore
);
84 $this->synchronized(function () use (&$content) {
85 $content = file_get_contents($this->datastore
);
88 // Note that gzinflate is faster than gzuncompress.
89 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
90 $links = unserialize(gzinflate(base64_decode(
91 substr($content, strlen(self
::$phpPrefix), -strlen(self
::$phpSuffix))
95 if (filesize($this->datastore
) > 100) {
96 throw new NotWritableDataStoreException($this->datastore
);
98 throw new EmptyDataStoreException();
105 * Saves the database from memory to disk
107 * @param Bookmark[] $links
109 * @throws NotWritableDataStoreException the datastore is not writable
111 public function write($links)
113 if (is_file($this->datastore
) && !is_writeable($this->datastore
)) {
114 // The datastore exists but is not writeable
115 throw new NotWritableDataStoreException($this->datastore
);
116 } elseif (!is_file($this->datastore
) && !is_writeable(dirname($this->datastore
))) {
117 // The datastore does not exist and its parent directory is not writeable
118 throw new NotWritableDataStoreException(dirname($this->datastore
));
121 $data = self
::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self
::$phpSuffix;
123 $this->synchronized(function () use ($data) {
132 * Wrapper applying mutex to provided function.
133 * If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex.
135 * @see https://github.com/shaarli/Shaarli/issues/1650
137 * @param callable $function
139 protected function synchronized(callable
$function): void
142 $this->mutex
->synchronized($function);
143 } catch (LockAcquireException
$exception) {