3 * @copyright Copyright (C) 2010-2021, 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\Model\Storage;
25 use Friendica\Util\Strings;
28 * Filesystem based storage backend
30 * This class manage data on filesystem.
31 * Base folder for storage is set in storage.filesystem_path.
32 * Best would be for storage folder to be outside webserver folder, we are using a
33 * folder relative to code tree root as default to ease things for users in shared hostings.
34 * Each new resource gets a value as reference and is saved in a
35 * folder tree stucture created from that value.
37 class Filesystem implements IWritableStorage
39 const NAME = 'Filesystem';
45 * Filesystem constructor.
47 * @param string $filesystemPath
49 * @throws StorageException in case the path doesn't exist or isn't writeable
51 public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
53 $path = $filesystemPath;
54 $this->basePath = rtrim($path, '/');
56 if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
57 throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
62 * Split data ref and return file path
64 * @param string $reference Data reference
68 private function pathForRef(string $reference): string
70 $fold1 = substr($reference, 0, 2);
71 $fold2 = substr($reference, 2, 2);
72 $file = substr($reference, 4);
74 return implode('/', [$this->basePath, $fold1, $fold2, $file]);
79 * Create directory tree to store file, with .htaccess and index.html files
81 * @param string $file Path and filename
83 * @throws StorageException
85 private function createFoldersForFile(string $file)
87 $path = dirname($file);
90 if (!mkdir($path, 0770, true)) {
91 throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
95 while ($path !== $this->basePath) {
96 if (!is_file($path . '/index.html')) {
97 file_put_contents($path . '/index.html', '');
99 chmod($path . '/index.html', 0660);
101 $path = dirname($path);
103 if (!is_file($path . '/index.html')) {
104 file_put_contents($path . '/index.html', '');
105 chmod($path . '/index.html', 0660);
112 public function get(string $reference): string
114 $file = $this->pathForRef($reference);
115 if (!is_file($file)) {
116 throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
119 $result = file_get_contents($file);
121 if ($result === false) {
122 throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
131 public function put(string $data, string $reference = ''): string
133 if ($reference === '') {
135 $reference = Strings::getRandomHex();
136 } catch (Exception $exception) {
137 throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
140 $file = $this->pathForRef($reference);
142 $this->createFoldersForFile($file);
144 $result = file_put_contents($file, $data);
146 // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
147 if ($result === false) {
148 throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
158 public function delete(string $reference)
160 $file = $this->pathForRef($reference);
161 if (!is_file($file)) {
162 throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
165 if (!unlink($file)) {
166 throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
173 public static function getName(): string
178 public function __toString()
180 return self::getName();