3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Core\Storage\Type;
25 use Friendica\Core\Storage\Exception\ReferenceStorageException;
26 use Friendica\Core\Storage\Exception\StorageException;
27 use Friendica\Core\Storage\Capability\ICanWriteToStorage;
28 use Friendica\Util\Strings;
31 * Filesystem based storage backend
33 * This class manage data on filesystem.
34 * Base folder for storage is set in storage.filesystem_path.
35 * Best would be for storage folder to be outside webserver folder, we are using a
36 * folder relative to code tree root as default to ease things for users in shared hostings.
37 * Each new resource gets a value as reference and is saved in a
38 * folder tree stucture created from that value.
40 class Filesystem implements ICanWriteToStorage
42 const NAME = 'Filesystem';
48 * Filesystem constructor.
50 * @param string $filesystemPath
52 * @throws StorageException in case the path doesn't exist or isn't writeable
54 public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
56 $path = $filesystemPath;
57 $this->basePath = rtrim($path, '/');
59 if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
60 throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
65 * Split data ref and return file path
67 * @param string $reference Data reference
71 private function pathForRef(string $reference): string
73 $fold1 = substr($reference, 0, 2);
74 $fold2 = substr($reference, 2, 2);
75 $file = substr($reference, 4);
77 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
82 * Create directory tree to store file, with .htaccess and index.html files
84 * @param string $file Path and filename
86 * @throws StorageException
88 private function createFoldersForFile(string $file)
90 $path = dirname($file);
93 if (!mkdir($path, 0770, true)) {
94 throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
98 while ($path !== $this->basePath) {
99 if (!is_file($path . '/index.html')) {
100 file_put_contents($path . '/index.html', '');
102 chmod($path . '/index.html', 0660);
104 $path = dirname($path);
106 if (!is_file($path . '/index.html')) {
107 file_put_contents($path . '/index.html', '');
108 chmod($path . '/index.html', 0660);
115 public function get(string $reference): string
117 $file = $this->pathForRef($reference);
118 if (!is_file($file)) {
119 throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
122 $result = file_get_contents($file);
124 if ($result === false) {
125 throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
134 public function put(string $data, string $reference = ''): string
136 if ($reference === '') {
138 $reference = Strings::getRandomHex();
139 } catch (Exception $exception) {
140 throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
143 $file = $this->pathForRef($reference);
145 $this->createFoldersForFile($file);
147 $result = file_put_contents($file, $data);
149 // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
150 if ($result === false) {
151 throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
161 public function delete(string $reference)
163 $file = $this->pathForRef($reference);
164 if (!is_file($file)) {
165 throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
168 if (!unlink($file)) {
169 throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
176 public static function getName(): string
181 public function __toString(): string
183 return self::getName();