]> git.mxchange.org Git - friendica.git/blob - src/Core/StorageManager.php
- Fixing SystemResource
[friendica.git] / src / Core / StorageManager.php
1 <?php
2
3 namespace Friendica\Core;
4
5 use Dice\Dice;
6 use Exception;
7 use Friendica\Core\Config\IConfiguration;
8 use Friendica\Database\Database;
9 use Friendica\Model\Storage;
10 use Psr\Log\LoggerInterface;
11
12
13 /**
14  * @brief Manage storage backends
15  *
16  * Core code uses this class to get and set current storage backend class.
17  * Addons use this class to register and unregister additional backends.
18  */
19 class StorageManager
20 {
21         // Default tables to look for data
22         const TABLES = ['photo', 'attach'];
23
24         // Default storage backends
25         const DEFAULT_BACKENDS = [
26                 Storage\Filesystem::NAME => Storage\Filesystem::class,
27                 Storage\Database::NAME   => Storage\Database::class,
28         ];
29
30         private $backends = [];
31
32         /** @var Database */
33         private $dba;
34         /** @var IConfiguration */
35         private $config;
36         /** @var LoggerInterface */
37         private $logger;
38         /** @var Dice */
39         private $dice;
40
41         /** @var Storage\IStorage */
42         private $currentBackend;
43
44         /**
45          * @param Database        $dba
46          * @param IConfiguration  $config
47          * @param LoggerInterface $logger
48          * @param Dice            $dice
49          */
50         public function __construct(Database $dba, IConfiguration $config, LoggerInterface $logger, Dice $dice)
51         {
52                 $this->dba      = $dba;
53                 $this->config   = $config;
54                 $this->logger   = $logger;
55                 $this->dice     = $dice;
56                 $this->backends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
57
58                 $currentName = $this->config->get('storage', 'name', '');
59
60                 if ($this->isValidBackend($currentName)) {
61                         $this->currentBackend = $this->dice->create($this->backends[$currentName]);
62                 } else {
63                         $this->currentBackend = null;
64                 }
65         }
66
67         /**
68          * @brief Return current storage backend class
69          *
70          * @return Storage\IStorage|null
71          */
72         public function getBackend()
73         {
74                 return $this->currentBackend;
75         }
76
77         /**
78          * @brief Return storage backend class by registered name
79          *
80          * @param string|null $name Backend name
81          *
82          * @return Storage\IStorage|null null if no backend registered at $name
83          */
84         public function getByName(string $name = null)
85         {
86                 if (!$this->isValidBackend($name) &&
87                     $name !== Storage\SystemResource::getName()) {
88                         return null;
89                 }
90
91                 /** @var Storage\IStorage $storage */
92                 $storage = null;
93
94                 // If the storage of the file is a system resource,
95                 // create it directly since it isn't listed in the registered backends
96                 if ($name === Storage\SystemResource::getName()) {
97                         $storage = $this->dice->create(Storage\SystemResource::class);
98                 } else {
99                         $storage = $this->dice->create($this->backends[$name]);
100                 }
101
102                 return $storage;
103         }
104
105         /**
106          * Checks, if the storage is a valid backend
107          *
108          * @param string|null $name The name or class of the backend
109          *
110          * @return boolean True, if the backend is a valid backend
111          */
112         public function isValidBackend(string $name = null)
113         {
114                 return array_key_exists($name, $this->backends);
115         }
116
117         /**
118          * @brief Set current storage backend class
119          *
120          * @param string $name Backend class name
121          *
122          * @return boolean True, if the set was successful
123          */
124         public function setBackend(string $name = null)
125         {
126                 if (!$this->isValidBackend($name)) {
127                         return false;
128                 }
129
130                 if ($this->config->set('storage', 'name', $name)) {
131                         $this->currentBackend = $this->dice->create($this->backends[$name]);
132                         return true;
133                 } else {
134                         return false;
135                 }
136         }
137
138         /**
139          * @brief Get registered backends
140          *
141          * @return array
142          */
143         public function listBackends()
144         {
145                 return $this->backends;
146         }
147
148         /**
149          * @brief Register a storage backend class
150          *
151          * @param string $class Backend class name
152          *
153          * @return boolean True, if the registration was successful
154          */
155         public function register(string $class)
156         {
157                 if (is_subclass_of($class, Storage\IStorage::class)) {
158                         /** @var Storage\IStorage $class */
159
160                         $backends        = $this->backends;
161                         $backends[$class::getName()] = $class;
162
163                         if ($this->config->set('storage', 'backends', $backends)) {
164                                 $this->backends = $backends;
165                                 return true;
166                         } else {
167                                 return false;
168                         }
169                 } else {
170                         return false;
171                 }
172         }
173
174         /**
175          * @brief Unregister a storage backend class
176          *
177          * @param string $class Backend class name
178          *
179          * @return boolean True, if unregistering was successful
180          */
181         public function unregister(string $class)
182         {
183                 if (is_subclass_of($class, Storage\IStorage::class)) {
184                         /** @var Storage\IStorage $class */
185
186                         unset($this->backends[$class::getName()]);
187
188                         if ($this->currentBackend instanceof $class) {
189                             $this->config->set('storage', 'name', null);
190                                 $this->currentBackend = null;
191                         }
192
193                         return $this->config->set('storage', 'backends', $this->backends);
194                 } else {
195                         return false;
196                 }
197         }
198
199         /**
200          * @brief Move up to 5000 resources to storage $dest
201          *
202          * Copy existing data to destination storage and delete from source.
203          * This method cannot move to legacy in-table `data` field.
204          *
205          * @param Storage\IStorage $destination Destination storage class name
206          * @param array            $tables      Tables to look in for resources. Optional, defaults to ['photo', 'attach']
207          * @param int              $limit       Limit of the process batch size, defaults to 5000
208          *
209          * @return int Number of moved resources
210          * @throws Storage\StorageException
211          * @throws Exception
212          */
213         public function move(Storage\IStorage $destination, array $tables = self::TABLES, int $limit = 5000)
214         {
215                 if ($destination === null) {
216                         throw new Storage\StorageException('Can\'t move to NULL storage backend');
217                 }
218
219                 $moved = 0;
220                 foreach ($tables as $table) {
221                         // Get the rows where backend class is not the destination backend class
222                         $resources = $this->dba->select(
223                                 $table,
224                                 ['id', 'data', 'backend-class', 'backend-ref'],
225                                 ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()],
226                                 ['limit' => $limit]
227                         );
228
229                         while ($resource = $this->dba->fetch($resources)) {
230                                 $id        = $resource['id'];
231                                 $data      = $resource['data'];
232                                 $source    = $this->getByName($resource['backend-class']);
233                                 $sourceRef = $resource['backend-ref'];
234
235                                 if (!empty($source)) {
236                                         $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
237                                         $data = $source->get($sourceRef);
238                                 }
239
240                                 $this->logger->info('Save data to new backend.', ['newBackend' => $destination]);
241                                 $destinationRef = $destination->put($data);
242                                 $this->logger->info('Saved data.', ['newReference' => $destinationRef]);
243
244                                 if ($destinationRef !== '') {
245                                         $this->logger->info('update row');
246                                         if ($this->dba->update($table, ['backend-class' => $destination, 'backend-ref' => $destinationRef, 'data' => ''], ['id' => $id])) {
247                                                 if (!empty($source)) {
248                                                         $this->logger->info('Delete data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
249                                                         $source->delete($sourceRef);
250                                                 }
251                                                 $moved++;
252                                         }
253                                 }
254                         }
255
256                         $this->dba->close($resources);
257                 }
258
259                 return $moved;
260         }
261 }