ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
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';
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),
141  self::DATA
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(
172  LegacyPathHelper::createRelativePath($original_path),
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 
343  public function getPathGenerator(): PathGenerator
344  {
345  return $this->path_generator;
346  }
347 }
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:43
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$location
Definition: buildRTE.php:22
getRevisionPath(Revision $revision)
This is the full path to a revision of a Resource, incl.
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...
__construct(protected Filesystem $fs, protected int $location=Location::STORAGE, bool $determine_linking_possible=false)
$path
Definition: ltiservices.php:29
const CLIENT_DATA_DIR
Definition: constants.php:46
global $DIC
Definition: shib_login.php:26
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:43
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