ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
AbstractFileSystemStorageHandler.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
40
46{
47 protected const DATA = 'data';
48 public const FLAVOUR_PATH_PREFIX = 'fl';
51 protected bool $links_possible = false;
52
53 public function __construct(
54 protected Filesystem $fs,
55 protected int $location = Location::STORAGE,
56 bool $determine_linking_possible = false
57 ) {
58 $this->id = new UniqueIDIdentificationGenerator();
59 if ($determine_linking_possible) {
60 $this->links_possible = $this->determineLinksPossible();
61 }
62 }
63
64 private function determineLinksPossible(): bool
65 {
66 $random_filename = "test_" . random_int(10000, 99999);
67
68 $original_filename = $this->getStorageLocationBasePath() . "/" . $random_filename . '_orig';
69 $linked_filename = $this->getStorageLocationBasePath() . "/" . $random_filename . '_link';
70 $cleaner = function () use ($original_filename, $linked_filename): void {
71 try {
72 $this->fs->delete($original_filename);
73 } catch (\Throwable) {
74 }
75 try {
76 $this->fs->delete($linked_filename);
77 } catch (\Throwable) {
78 }
79 };
80
81 try {
82 // remove existing files
83 $cleaner();
84
85 // create file
86 $this->fs->write($original_filename, 'data');
87 $stream = $this->fs->readStream($original_filename);
88
89 // determine absolute pathes
90 $original_absolute_path = $stream->getMetadata('uri');
91 $linked_absolute_path = dirname((string) $original_absolute_path) . "/" . $random_filename . '_link';
92
93 // try linking and unlinking
95 $linking = @link($original_absolute_path, $linked_absolute_path);
97 $unlinking = @unlink($original_absolute_path);
98 $stream->close();
99 if ($linking && $unlinking && $this->fs->has($linked_filename)) {
100 $cleaner();
101
102 return true;
103 }
104 $cleaner();
105 } catch (\Throwable) {
106 return false;
107 }
108 return false;
109 }
110
115 {
116 return $this->id;
117 }
118
119 public function has(ResourceIdentification $identification): bool
120 {
121 return $this->fs->has($this->getFullContainerPath($identification));
122 }
123
127 public function getStream(Revision $revision): FileStream
128 {
129 return $this->fs->readStream($this->getRevisionPath($revision) . '/' . self::DATA);
130 }
131
132
133 public function storeUpload(UploadedFileRevision $revision): bool
134 {
135 global $DIC;
136
137 $DIC->upload()->moveOneFileTo(
138 $revision->getUpload(),
139 $this->getRevisionPath($revision),
140 $this->location,
142 );
143
144 return true;
145 }
146
150 public function storeStream(FileStreamRevision $revision): bool
151 {
152 try {
153 if ($revision->keepOriginal()) {
154 $stream = $revision->getStream();
155 $this->fs->writeStream($this->getRevisionPath($revision) . '/' . self::DATA, $stream);
156 $stream->close();
157 } else {
158 $original_path = $revision->getStream()->getMetadata('uri');
159 if ($this->links_possible) {
160 $this->fs->createDir($this->getRevisionPath($revision));
161 link($original_path, $this->getAbsoluteRevisionPath($revision) . '/' . self::DATA);
162 unlink($original_path);
163 } else {
164 $source_fs = LegacyPathHelper::deriveLocationFrom($original_path);
165 if ($source_fs !== Location::STORAGE) {
166 $stream = $revision->getStream();
167 $this->fs->writeStream($this->getRevisionPath($revision) . '/' . self::DATA, $stream);
168 $stream->close();
169 unlink($original_path);
170 } else {
171 $this->fs->rename(
173 $this->getRevisionPath($revision) . '/' . self::DATA
174 );
175 }
176 }
177 $revision->getStream()->close();
178 }
179 } catch (\Throwable) {
180 return false;
181 }
182
183 return true;
184 }
185
186 public function cloneRevision(CloneRevision $revision): bool
187 {
188 $stream = $this->getStream($revision->getRevisionToClone());
189 try {
190 $this->fs->writeStream($this->getRevisionPath($revision) . '/' . self::DATA, $stream);
191 $stream->close();
192 } catch (\Throwable) {
193 return false;
194 }
195
196 return true;
197 }
198
199 public function streamReplacement(StreamReplacementRevision $revision): bool
200 {
201 $stream = $revision->getReplacementStream();
202 try {
203 $path = $this->getRevisionPath($revision) . '/' . self::DATA;
204 if ($this->fs->has($path)) {
205 $this->fs->delete($path);
206 }
207 $this->fs->writeStream($path, $stream);
208 $stream->close();
209 } catch (\Throwable) {
210 return false;
211 }
212
213 return true;
214 }
215
219 public function deleteRevision(Revision $revision): void
220 {
221 try {
222 $this->fs->deleteDir($this->getRevisionPath($revision));
223 } catch (\Throwable) {
224 }
225 }
226
227 public function hasFlavour(Revision $revision, Flavour $flavour): bool
228 {
229 return $this->fs->has($this->getFlavourPath($revision, $flavour));
230 }
231
232
233 public function storeFlavour(Revision $revision, StorableFlavourDecorator $storabel_flavour): bool
234 {
235 $flavour = $storabel_flavour->getFlavour();
236 $path = $this->getFlavourPath($revision, $flavour);
237 if ($this->fs->has($path)) {
238 $this->fs->deleteDir($path); // we remove old files of this flavour.
239 }
240
241 foreach ($storabel_flavour->getStreams() as $index => $stream) {
242 $index_path = $path . '/' . self::DATA . '_' . $index; // flavour streams are just written with an index name
243 $this->fs->writeStream($index_path, $stream);
244 }
245
246 return true;
247 }
248
249 public function deleteFlavour(Revision $revision, Flavour $flavour): bool
250 {
251 $path = $this->getFlavourPath($revision, $flavour);
252 if ($this->fs->has($path)) {
253 $this->fs->deleteDir($path);
254 return true;
255 }
256 return false;
257 }
258
259
260 public function getFlavourStreams(Revision $revision, Flavour $flavour): \Generator
261 {
262 $path = $this->getFlavourPath($revision, $flavour);
263 if (!$this->hasFlavour($revision, $flavour)) {
264 return;
265 }
266 foreach ($this->fs->finder()->in([$path])->files()->sortByName()->getIterator() as $item) {
267 yield $this->fs->readStream($item->getPath());
268 }
269 }
270
271
275 public function deleteResource(StorableResource $resource): void
276 {
277 try {
278 $this->fs->deleteDir($this->getFullContainerPath($resource->getIdentification()));
279 } catch (\Throwable) {
280 }
281 try {
282 $this->cleanUpContainer($resource);
283 } catch (\Throwable) {
284 }
285 }
286
287 public function cleanUpContainer(StorableResource $resource): void
288 {
289 $storage_path = $this->getStorageLocationBasePath();
290 $container_path = $this->getContainerPathWithoutBase($resource->getIdentification());
291 $first_level = strtok($container_path, "/");
292 if (!empty($first_level)) {
293 $full_first_level = $storage_path . '/' . $first_level;
294 $number_of_files = $this->fs->finder()->files()->in([$full_first_level])->count();
295 if ($number_of_files === 0) {
296 $this->fs->deleteDir($full_first_level);
297 }
298 }
299 }
300
301 public function getBasePath(ResourceIdentification $identification): string
302 {
303 return $this->getFullContainerPath($identification);
304 }
305
306 public function getFlavourPath(Revision $revision, Flavour $flavour): string
307 {
308 return $this->getRevisionPath($revision)
309 . '/' . self::FLAVOUR_PATH_PREFIX
310 . '/' . $flavour->getPersistingName();
311 }
312
313 public function clearFlavours(Revision $revision): void
314 {
315 $this->fs->deleteDir($this->getRevisionPath($revision) . '/' . self::FLAVOUR_PATH_PREFIX);
316 }
317
318 public function getRevisionPath(Revision $revision): string
319 {
320 return $this->getFullContainerPath($revision->getIdentification()) . '/' . $revision->getVersionNumber();
321 }
322
323 public function getFullContainerPath(ResourceIdentification $identification): string
324 {
325 return $this->getStorageLocationBasePath() . '/' . $this->getContainerPathWithoutBase($identification);
326 }
327
328 public function getContainerPathWithoutBase(ResourceIdentification $identification): string
329 {
330 return $this->path_generator->getPathFor($identification);
331 }
332
333 private function getAbsoluteRevisionPath(Revision $revision): string
334 {
335 return rtrim(CLIENT_DATA_DIR, "/") . "/" . ltrim($this->getRevisionPath($revision), "/");
336 }
337
338 public function movementImplementation(): string
339 {
340 return $this->links_possible ? 'link' : 'rename';
341 }
342
344 {
346 }
347}
$location
Definition: buildRTE.php:22
The legacy path helper provides convenient functions for the integration of the filesystem service wi...
static createRelativePath(string $absolute_path)
Creates a relative path from an absolute path which starts with a valid storage location.
static deriveLocationFrom(string $absolute_path)
getPersistingName()
Flavours are stored in the file system by the StroageHandler.
Definition: Flavour.php:44
getContainerPathWithoutBase(ResourceIdentification $identification)
This is only the path of a ResourceIdentification inside the StorageLocation base path.
getRevisionPath(Revision $revision)
This is the full path to a revision of a Resource, incl.
getFullContainerPath(ResourceIdentification $identification)
This is the full path to the container of a ResourceIdentification (incl.
__construct(protected Filesystem $fs, protected int $location=Location::STORAGE, bool $determine_linking_possible=false)
cleanUpContainer(StorableResource $resource)
This checks if there are empty directories in the filesystem which can be deleted.
const CLIENT_DATA_DIR
Definition: constants.php:46
Interface Location.
Definition: Location.php:33
const STORAGE
The filesystem outside of the ilias web root.
Definition: Location.php:43
The filesystem interface provides the public interface for the Filesystem service API consumer.
Definition: Filesystem.php:37
The base interface for all filesystem streams.
Definition: FileStream.php:32
getStorageLocationBasePath()
This is the place in the filesystem where the containers (nested) get created.
$path
Definition: ltiservices.php:30
link(string $caption, string $href, bool $new_viewport=false)
global $DIC
Definition: shib_login.php:26