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