]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/bookmark/BookmarkIO.php
Fix: soft fail if the mutex is not working
[github/shaarli/Shaarli.git] / application / bookmark / BookmarkIO.php
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Shaarli\Bookmark;
6
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;
14
15 /**
16 * Class BookmarkIO
17 *
18 * This class performs read/write operation to the file data store.
19 * Used by BookmarkFileService.
20 *
21 * @package Shaarli\Bookmark
22 */
23 class BookmarkIO
24 {
25 /**
26 * @var string Datastore file path
27 */
28 protected $datastore;
29
30 /**
31 * @var ConfigManager instance
32 */
33 protected $conf;
34
35
36 /** @var Mutex */
37 protected $mutex;
38
39 /**
40 * string Datastore PHP prefix
41 */
42 protected static $phpPrefix = '<?php /* ';
43 /**
44 * string Datastore PHP suffix
45 */
46 protected static $phpSuffix = ' */ ?>';
47
48 /**
49 * LinksIO constructor.
50 *
51 * @param ConfigManager $conf instance
52 */
53 public function __construct(ConfigManager $conf, Mutex $mutex = null)
54 {
55 if ($mutex === null) {
56 // This should only happen with legacy classes
57 $mutex = new NoMutex();
58 }
59 $this->conf = $conf;
60 $this->datastore = $conf->get('resource.datastore');
61 $this->mutex = $mutex;
62 }
63
64 /**
65 * Reads database from disk to memory
66 *
67 * @return Bookmark[]
68 *
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
72 */
73 public function read()
74 {
75 if (! file_exists($this->datastore)) {
76 throw new DatastoreNotInitializedException();
77 }
78
79 if (!is_writable($this->datastore)) {
80 throw new NotWritableDataStoreException($this->datastore);
81 }
82
83 $content = null;
84 $this->synchronized(function () use (&$content) {
85 $content = file_get_contents($this->datastore);
86 });
87
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))
92 )));
93
94 if (empty($links)) {
95 if (filesize($this->datastore) > 100) {
96 throw new NotWritableDataStoreException($this->datastore);
97 }
98 throw new EmptyDataStoreException();
99 }
100
101 return $links;
102 }
103
104 /**
105 * Saves the database from memory to disk
106 *
107 * @param Bookmark[] $links
108 *
109 * @throws NotWritableDataStoreException the datastore is not writable
110 */
111 public function write($links)
112 {
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));
119 }
120
121 $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix;
122
123 $this->synchronized(function () use ($data) {
124 file_put_contents(
125 $this->datastore,
126 $data
127 );
128 });
129 }
130
131 /**
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.
134 *
135 * @see https://github.com/shaarli/Shaarli/issues/1650
136 *
137 * @param callable $function
138 */
139 protected function synchronized(callable $function): void
140 {
141 try {
142 $this->mutex->synchronized($function);
143 } catch (LockAcquireException $exception) {
144 $function();
145 }
146 }
147 }