Passed
Push — master ( bbd3e2...970f39 )
by Blizzz
23:27 queued 20s
created
lib/private/Files/Cache/Cache.php 1 patch
Indentation   +1036 added lines, -1036 removed lines patch added patch discarded remove patch
@@ -72,1040 +72,1040 @@
 block discarded – undo
72 72
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
73 73
  */
74 74
 class Cache implements ICache {
75
-	use MoveFromCacheTrait {
76
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
77
-	}
78
-
79
-	/**
80
-	 * @var array partial data for the cache
81
-	 */
82
-	protected $partial = [];
83
-
84
-	/**
85
-	 * @var string
86
-	 */
87
-	protected $storageId;
88
-
89
-	private $storage;
90
-
91
-	/**
92
-	 * @var Storage $storageCache
93
-	 */
94
-	protected $storageCache;
95
-
96
-	/** @var IMimeTypeLoader */
97
-	protected $mimetypeLoader;
98
-
99
-	/**
100
-	 * @var IDBConnection
101
-	 */
102
-	protected $connection;
103
-
104
-	/**
105
-	 * @var IEventDispatcher
106
-	 */
107
-	protected $eventDispatcher;
108
-
109
-	/** @var QuerySearchHelper */
110
-	protected $querySearchHelper;
111
-
112
-	/**
113
-	 * @param IStorage $storage
114
-	 */
115
-	public function __construct(IStorage $storage) {
116
-		$this->storageId = $storage->getId();
117
-		$this->storage = $storage;
118
-		if (strlen($this->storageId) > 64) {
119
-			$this->storageId = md5($this->storageId);
120
-		}
121
-
122
-		$this->storageCache = new Storage($storage);
123
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
124
-		$this->connection = \OC::$server->getDatabaseConnection();
125
-		$this->eventDispatcher = \OC::$server->get(IEventDispatcher::class);
126
-		$this->querySearchHelper = \OC::$server->query(QuerySearchHelper::class);
127
-	}
128
-
129
-	protected function getQueryBuilder() {
130
-		return new CacheQueryBuilder(
131
-			$this->connection,
132
-			\OC::$server->getSystemConfig(),
133
-			\OC::$server->get(LoggerInterface::class)
134
-		);
135
-	}
136
-
137
-	/**
138
-	 * Get the numeric storage id for this cache's storage
139
-	 *
140
-	 * @return int
141
-	 */
142
-	public function getNumericStorageId() {
143
-		return $this->storageCache->getNumericId();
144
-	}
145
-
146
-	/**
147
-	 * get the stored metadata of a file or folder
148
-	 *
149
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
150
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
151
-	 */
152
-	public function get($file) {
153
-		$query = $this->getQueryBuilder();
154
-		$query->selectFileCache();
155
-
156
-		if (is_string($file) || $file == '') {
157
-			// normalize file
158
-			$file = $this->normalize($file);
159
-
160
-			$query->whereStorageId($this->getNumericStorageId())
161
-				->wherePath($file);
162
-		} else { //file id
163
-			$query->whereFileId($file);
164
-		}
165
-
166
-		$result = $query->execute();
167
-		$data = $result->fetch();
168
-		$result->closeCursor();
169
-
170
-		//merge partial data
171
-		if (!$data && is_string($file) && isset($this->partial[$file])) {
172
-			return $this->partial[$file];
173
-		} elseif (!$data) {
174
-			return $data;
175
-		} else {
176
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
177
-		}
178
-	}
179
-
180
-	/**
181
-	 * Create a CacheEntry from database row
182
-	 *
183
-	 * @param array $data
184
-	 * @param IMimeTypeLoader $mimetypeLoader
185
-	 * @return CacheEntry
186
-	 */
187
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
188
-		//fix types
189
-		$data['name'] = (string)$data['name'];
190
-		$data['path'] = (string)$data['path'];
191
-		$data['fileid'] = (int)$data['fileid'];
192
-		$data['parent'] = (int)$data['parent'];
193
-		$data['size'] = 0 + $data['size'];
194
-		$data['unencrypted_size'] = 0 + ($data['unencrypted_size'] ?? 0);
195
-		$data['mtime'] = (int)$data['mtime'];
196
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
197
-		$data['encryptedVersion'] = (int)$data['encrypted'];
198
-		$data['encrypted'] = (bool)$data['encrypted'];
199
-		$data['storage_id'] = $data['storage'];
200
-		$data['storage'] = (int)$data['storage'];
201
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
202
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
203
-		if ($data['storage_mtime'] == 0) {
204
-			$data['storage_mtime'] = $data['mtime'];
205
-		}
206
-		$data['permissions'] = (int)$data['permissions'];
207
-		if (isset($data['creation_time'])) {
208
-			$data['creation_time'] = (int)$data['creation_time'];
209
-		}
210
-		if (isset($data['upload_time'])) {
211
-			$data['upload_time'] = (int)$data['upload_time'];
212
-		}
213
-		return new CacheEntry($data);
214
-	}
215
-
216
-	/**
217
-	 * get the metadata of all files stored in $folder
218
-	 *
219
-	 * @param string $folder
220
-	 * @return ICacheEntry[]
221
-	 */
222
-	public function getFolderContents($folder) {
223
-		$fileId = $this->getId($folder);
224
-		return $this->getFolderContentsById($fileId);
225
-	}
226
-
227
-	/**
228
-	 * get the metadata of all files stored in $folder
229
-	 *
230
-	 * @param int $fileId the file id of the folder
231
-	 * @return ICacheEntry[]
232
-	 */
233
-	public function getFolderContentsById($fileId) {
234
-		if ($fileId > -1) {
235
-			$query = $this->getQueryBuilder();
236
-			$query->selectFileCache()
237
-				->whereParent($fileId)
238
-				->orderBy('name', 'ASC');
239
-
240
-			$result = $query->execute();
241
-			$files = $result->fetchAll();
242
-			$result->closeCursor();
243
-
244
-			return array_map(function (array $data) {
245
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);
246
-			}, $files);
247
-		}
248
-		return [];
249
-	}
250
-
251
-	/**
252
-	 * insert or update meta data for a file or folder
253
-	 *
254
-	 * @param string $file
255
-	 * @param array $data
256
-	 *
257
-	 * @return int file id
258
-	 * @throws \RuntimeException
259
-	 */
260
-	public function put($file, array $data) {
261
-		if (($id = $this->getId($file)) > -1) {
262
-			$this->update($id, $data);
263
-			return $id;
264
-		} else {
265
-			return $this->insert($file, $data);
266
-		}
267
-	}
268
-
269
-	/**
270
-	 * insert meta data for a new file or folder
271
-	 *
272
-	 * @param string $file
273
-	 * @param array $data
274
-	 *
275
-	 * @return int file id
276
-	 * @throws \RuntimeException
277
-	 */
278
-	public function insert($file, array $data) {
279
-		// normalize file
280
-		$file = $this->normalize($file);
281
-
282
-		if (isset($this->partial[$file])) { //add any saved partial data
283
-			$data = array_merge($this->partial[$file], $data);
284
-			unset($this->partial[$file]);
285
-		}
286
-
287
-		$requiredFields = ['size', 'mtime', 'mimetype'];
288
-		foreach ($requiredFields as $field) {
289
-			if (!isset($data[$field])) { //data not complete save as partial and return
290
-				$this->partial[$file] = $data;
291
-				return -1;
292
-			}
293
-		}
294
-
295
-		$data['path'] = $file;
296
-		if (!isset($data['parent'])) {
297
-			$data['parent'] = $this->getParentId($file);
298
-		}
299
-		$data['name'] = basename($file);
300
-
301
-		[$values, $extensionValues] = $this->normalizeData($data);
302
-		$storageId = $this->getNumericStorageId();
303
-		$values['storage'] = $storageId;
304
-
305
-		try {
306
-			$builder = $this->connection->getQueryBuilder();
307
-			$builder->insert('filecache');
308
-
309
-			foreach ($values as $column => $value) {
310
-				$builder->setValue($column, $builder->createNamedParameter($value));
311
-			}
312
-
313
-			if ($builder->execute()) {
314
-				$fileId = $builder->getLastInsertId();
315
-
316
-				if (count($extensionValues)) {
317
-					$query = $this->getQueryBuilder();
318
-					$query->insert('filecache_extended');
319
-
320
-					$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
321
-					foreach ($extensionValues as $column => $value) {
322
-						$query->setValue($column, $query->createNamedParameter($value));
323
-					}
324
-					$query->execute();
325
-				}
326
-
327
-				$event = new CacheEntryInsertedEvent($this->storage, $file, $fileId, $storageId);
328
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, $event);
329
-				$this->eventDispatcher->dispatchTyped($event);
330
-				return $fileId;
331
-			}
332
-		} catch (UniqueConstraintViolationException $e) {
333
-			// entry exists already
334
-			if ($this->connection->inTransaction()) {
335
-				$this->connection->commit();
336
-				$this->connection->beginTransaction();
337
-			}
338
-		}
339
-
340
-		// The file was created in the mean time
341
-		if (($id = $this->getId($file)) > -1) {
342
-			$this->update($id, $data);
343
-			return $id;
344
-		} else {
345
-			throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
346
-		}
347
-	}
348
-
349
-	/**
350
-	 * update the metadata of an existing file or folder in the cache
351
-	 *
352
-	 * @param int $id the fileid of the existing file or folder
353
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
354
-	 */
355
-	public function update($id, array $data) {
356
-		if (isset($data['path'])) {
357
-			// normalize path
358
-			$data['path'] = $this->normalize($data['path']);
359
-		}
360
-
361
-		if (isset($data['name'])) {
362
-			// normalize path
363
-			$data['name'] = $this->normalize($data['name']);
364
-		}
365
-
366
-		[$values, $extensionValues] = $this->normalizeData($data);
367
-
368
-		if (count($values)) {
369
-			$query = $this->getQueryBuilder();
370
-
371
-			$query->update('filecache')
372
-				->whereFileId($id)
373
-				->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
374
-					return $query->expr()->orX(
375
-						$query->expr()->neq($key, $query->createNamedParameter($value)),
376
-						$query->expr()->isNull($key)
377
-					);
378
-				}, array_keys($values), array_values($values))));
379
-
380
-			foreach ($values as $key => $value) {
381
-				$query->set($key, $query->createNamedParameter($value));
382
-			}
383
-
384
-			$query->execute();
385
-		}
386
-
387
-		if (count($extensionValues)) {
388
-			try {
389
-				$query = $this->getQueryBuilder();
390
-				$query->insert('filecache_extended');
391
-
392
-				$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
393
-				foreach ($extensionValues as $column => $value) {
394
-					$query->setValue($column, $query->createNamedParameter($value));
395
-				}
396
-
397
-				$query->execute();
398
-			} catch (UniqueConstraintViolationException $e) {
399
-				$query = $this->getQueryBuilder();
400
-				$query->update('filecache_extended')
401
-					->whereFileId($id)
402
-					->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
403
-						return $query->expr()->orX(
404
-							$query->expr()->neq($key, $query->createNamedParameter($value)),
405
-							$query->expr()->isNull($key)
406
-						);
407
-					}, array_keys($extensionValues), array_values($extensionValues))));
408
-
409
-				foreach ($extensionValues as $key => $value) {
410
-					$query->set($key, $query->createNamedParameter($value));
411
-				}
412
-
413
-				$query->execute();
414
-			}
415
-		}
416
-
417
-		$path = $this->getPathById($id);
418
-		// path can still be null if the file doesn't exist
419
-		if ($path !== null) {
420
-			$event = new CacheEntryUpdatedEvent($this->storage, $path, $id, $this->getNumericStorageId());
421
-			$this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event);
422
-			$this->eventDispatcher->dispatchTyped($event);
423
-		}
424
-	}
425
-
426
-	/**
427
-	 * extract query parts and params array from data array
428
-	 *
429
-	 * @param array $data
430
-	 * @return array
431
-	 */
432
-	protected function normalizeData(array $data): array {
433
-		$fields = [
434
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
435
-			'etag', 'permissions', 'checksum', 'storage', 'unencrypted_size'];
436
-		$extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
437
-
438
-		$doNotCopyStorageMTime = false;
439
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
440
-			// this horrific magic tells it to not copy storage_mtime to mtime
441
-			unset($data['mtime']);
442
-			$doNotCopyStorageMTime = true;
443
-		}
444
-
445
-		$params = [];
446
-		$extensionParams = [];
447
-		foreach ($data as $name => $value) {
448
-			if (array_search($name, $fields) !== false) {
449
-				if ($name === 'path') {
450
-					$params['path_hash'] = md5($value);
451
-				} elseif ($name === 'mimetype') {
452
-					$params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
453
-					$value = $this->mimetypeLoader->getId($value);
454
-				} elseif ($name === 'storage_mtime') {
455
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
456
-						$params['mtime'] = $value;
457
-					}
458
-				} elseif ($name === 'encrypted') {
459
-					if (isset($data['encryptedVersion'])) {
460
-						$value = $data['encryptedVersion'];
461
-					} else {
462
-						// Boolean to integer conversion
463
-						$value = $value ? 1 : 0;
464
-					}
465
-				}
466
-				$params[$name] = $value;
467
-			}
468
-			if (array_search($name, $extensionFields) !== false) {
469
-				$extensionParams[$name] = $value;
470
-			}
471
-		}
472
-		return [$params, array_filter($extensionParams)];
473
-	}
474
-
475
-	/**
476
-	 * get the file id for a file
477
-	 *
478
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
479
-	 *
480
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
481
-	 *
482
-	 * @param string $file
483
-	 * @return int
484
-	 */
485
-	public function getId($file) {
486
-		// normalize file
487
-		$file = $this->normalize($file);
488
-
489
-		$query = $this->getQueryBuilder();
490
-		$query->select('fileid')
491
-			->from('filecache')
492
-			->whereStorageId($this->getNumericStorageId())
493
-			->wherePath($file);
494
-
495
-		$result = $query->execute();
496
-		$id = $result->fetchOne();
497
-		$result->closeCursor();
498
-
499
-		return $id === false ? -1 : (int)$id;
500
-	}
501
-
502
-	/**
503
-	 * get the id of the parent folder of a file
504
-	 *
505
-	 * @param string $file
506
-	 * @return int
507
-	 */
508
-	public function getParentId($file) {
509
-		if ($file === '') {
510
-			return -1;
511
-		} else {
512
-			$parent = $this->getParentPath($file);
513
-			return (int)$this->getId($parent);
514
-		}
515
-	}
516
-
517
-	private function getParentPath($path) {
518
-		$parent = dirname($path);
519
-		if ($parent === '.') {
520
-			$parent = '';
521
-		}
522
-		return $parent;
523
-	}
524
-
525
-	/**
526
-	 * check if a file is available in the cache
527
-	 *
528
-	 * @param string $file
529
-	 * @return bool
530
-	 */
531
-	public function inCache($file) {
532
-		return $this->getId($file) != -1;
533
-	}
534
-
535
-	/**
536
-	 * remove a file or folder from the cache
537
-	 *
538
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
539
-	 *
540
-	 * @param string $file
541
-	 */
542
-	public function remove($file) {
543
-		$entry = $this->get($file);
544
-
545
-		if ($entry instanceof ICacheEntry) {
546
-			$query = $this->getQueryBuilder();
547
-			$query->delete('filecache')
548
-				->whereFileId($entry->getId());
549
-			$query->execute();
550
-
551
-			$query = $this->getQueryBuilder();
552
-			$query->delete('filecache_extended')
553
-				->whereFileId($entry->getId());
554
-			$query->execute();
555
-
556
-			if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
557
-				$this->removeChildren($entry);
558
-			}
559
-
560
-			$this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $entry->getPath(), $entry->getId(), $this->getNumericStorageId()));
561
-		}
562
-	}
563
-
564
-	/**
565
-	 * Get all sub folders of a folder
566
-	 *
567
-	 * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
568
-	 * @return ICacheEntry[] the cache entries for the subfolders
569
-	 */
570
-	private function getSubFolders(ICacheEntry $entry) {
571
-		$children = $this->getFolderContentsById($entry->getId());
572
-		return array_filter($children, function ($child) {
573
-			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
574
-		});
575
-	}
576
-
577
-	/**
578
-	 * Recursively remove all children of a folder
579
-	 *
580
-	 * @param ICacheEntry $entry the cache entry of the folder to remove the children of
581
-	 * @throws \OC\DatabaseException
582
-	 */
583
-	private function removeChildren(ICacheEntry $entry) {
584
-		$parentIds = [$entry->getId()];
585
-		$queue = [$entry->getId()];
586
-
587
-		// we walk depth first through the file tree, removing all filecache_extended attributes while we walk
588
-		// and collecting all folder ids to later use to delete the filecache entries
589
-		while ($entryId = array_pop($queue)) {
590
-			$children = $this->getFolderContentsById($entryId);
591
-			$childIds = array_map(function (ICacheEntry $cacheEntry) {
592
-				return $cacheEntry->getId();
593
-			}, $children);
594
-
595
-			$query = $this->getQueryBuilder();
596
-			$query->delete('filecache_extended')
597
-				->where($query->expr()->in('fileid', $query->createParameter('childIds')));
598
-
599
-			foreach (array_chunk($childIds, 1000) as $childIdChunk) {
600
-				$query->setParameter('childIds', $childIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
601
-				$query->execute();
602
-			}
603
-
604
-			/** @var ICacheEntry[] $childFolders */
605
-			$childFolders = array_filter($children, function ($child) {
606
-				return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
607
-			});
608
-			foreach ($childFolders as $folder) {
609
-				$parentIds[] = $folder->getId();
610
-				$queue[] = $folder->getId();
611
-			}
612
-		}
613
-
614
-		$query = $this->getQueryBuilder();
615
-		$query->delete('filecache')
616
-			->whereParentInParameter('parentIds');
617
-
618
-		foreach (array_chunk($parentIds, 1000) as $parentIdChunk) {
619
-			$query->setParameter('parentIds', $parentIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
620
-			$query->execute();
621
-		}
622
-	}
623
-
624
-	/**
625
-	 * Move a file or folder in the cache
626
-	 *
627
-	 * @param string $source
628
-	 * @param string $target
629
-	 */
630
-	public function move($source, $target) {
631
-		$this->moveFromCache($this, $source, $target);
632
-	}
633
-
634
-	/**
635
-	 * Get the storage id and path needed for a move
636
-	 *
637
-	 * @param string $path
638
-	 * @return array [$storageId, $internalPath]
639
-	 */
640
-	protected function getMoveInfo($path) {
641
-		return [$this->getNumericStorageId(), $path];
642
-	}
643
-
644
-	/**
645
-	 * Move a file or folder in the cache
646
-	 *
647
-	 * @param ICache $sourceCache
648
-	 * @param string $sourcePath
649
-	 * @param string $targetPath
650
-	 * @throws \OC\DatabaseException
651
-	 * @throws \Exception if the given storages have an invalid id
652
-	 */
653
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
654
-		if ($sourceCache instanceof Cache) {
655
-			// normalize source and target
656
-			$sourcePath = $this->normalize($sourcePath);
657
-			$targetPath = $this->normalize($targetPath);
658
-
659
-			$sourceData = $sourceCache->get($sourcePath);
660
-			if ($sourceData === false) {
661
-				throw new \Exception('Invalid source storage path: ' . $sourcePath);
662
-			}
663
-
664
-			$sourceId = $sourceData['fileid'];
665
-			$newParentId = $this->getParentId($targetPath);
666
-
667
-			[$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
668
-			[$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
669
-
670
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
671
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
672
-			}
673
-			if (is_null($targetStorageId) || $targetStorageId === false) {
674
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
675
-			}
676
-
677
-			$this->connection->beginTransaction();
678
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
679
-				//update all child entries
680
-				$sourceLength = mb_strlen($sourcePath);
681
-				$query = $this->connection->getQueryBuilder();
682
-
683
-				$fun = $query->func();
684
-				$newPathFunction = $fun->concat(
685
-					$query->createNamedParameter($targetPath),
686
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
687
-				);
688
-				$query->update('filecache')
689
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
690
-					->set('path_hash', $fun->md5($newPathFunction))
691
-					->set('path', $newPathFunction)
692
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
693
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
694
-
695
-				try {
696
-					$query->execute();
697
-				} catch (\OC\DatabaseException $e) {
698
-					$this->connection->rollBack();
699
-					throw $e;
700
-				}
701
-			}
702
-
703
-			$query = $this->getQueryBuilder();
704
-			$query->update('filecache')
705
-				->set('storage', $query->createNamedParameter($targetStorageId))
706
-				->set('path', $query->createNamedParameter($targetPath))
707
-				->set('path_hash', $query->createNamedParameter(md5($targetPath)))
708
-				->set('name', $query->createNamedParameter(basename($targetPath)))
709
-				->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
710
-				->whereFileId($sourceId);
711
-			$query->execute();
712
-
713
-			$this->connection->commit();
714
-
715
-			if ($sourceCache->getNumericStorageId() !== $this->getNumericStorageId()) {
716
-				$this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $sourcePath, $sourceId, $sourceCache->getNumericStorageId()));
717
-				$event = new CacheEntryInsertedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId());
718
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, $event);
719
-				$this->eventDispatcher->dispatchTyped($event);
720
-			} else {
721
-				$event = new CacheEntryUpdatedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId());
722
-				$this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event);
723
-				$this->eventDispatcher->dispatchTyped($event);
724
-			}
725
-		} else {
726
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
727
-		}
728
-	}
729
-
730
-	/**
731
-	 * remove all entries for files that are stored on the storage from the cache
732
-	 */
733
-	public function clear() {
734
-		$query = $this->getQueryBuilder();
735
-		$query->delete('filecache')
736
-			->whereStorageId($this->getNumericStorageId());
737
-		$query->execute();
738
-
739
-		$query = $this->connection->getQueryBuilder();
740
-		$query->delete('storages')
741
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
742
-		$query->execute();
743
-	}
744
-
745
-	/**
746
-	 * Get the scan status of a file
747
-	 *
748
-	 * - Cache::NOT_FOUND: File is not in the cache
749
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
750
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
751
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
752
-	 *
753
-	 * @param string $file
754
-	 *
755
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
756
-	 */
757
-	public function getStatus($file) {
758
-		// normalize file
759
-		$file = $this->normalize($file);
760
-
761
-		$query = $this->getQueryBuilder();
762
-		$query->select('size')
763
-			->from('filecache')
764
-			->whereStorageId($this->getNumericStorageId())
765
-			->wherePath($file);
766
-
767
-		$result = $query->execute();
768
-		$size = $result->fetchOne();
769
-		$result->closeCursor();
770
-
771
-		if ($size !== false) {
772
-			if ((int)$size === -1) {
773
-				return self::SHALLOW;
774
-			} else {
775
-				return self::COMPLETE;
776
-			}
777
-		} else {
778
-			if (isset($this->partial[$file])) {
779
-				return self::PARTIAL;
780
-			} else {
781
-				return self::NOT_FOUND;
782
-			}
783
-		}
784
-	}
785
-
786
-	/**
787
-	 * search for files matching $pattern
788
-	 *
789
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
790
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
791
-	 */
792
-	public function search($pattern) {
793
-		$operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', $pattern);
794
-		return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
795
-	}
796
-
797
-	/**
798
-	 * search for files by mimetype
799
-	 *
800
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
801
-	 *        where it will search for all mimetypes in the group ('image/*')
802
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
803
-	 */
804
-	public function searchByMime($mimetype) {
805
-		if (strpos($mimetype, '/') === false) {
806
-			$operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%');
807
-		} else {
808
-			$operator = new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype);
809
-		}
810
-		return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
811
-	}
812
-
813
-	public function searchQuery(ISearchQuery $searchQuery) {
814
-		return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
815
-	}
816
-
817
-	/**
818
-	 * Re-calculate the folder size and the size of all parent folders
819
-	 *
820
-	 * @param string|boolean $path
821
-	 * @param array $data (optional) meta data of the folder
822
-	 */
823
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
824
-		$this->calculateFolderSize($path, $data);
825
-		if ($path !== '') {
826
-			$parent = dirname($path);
827
-			if ($parent === '.' || $parent === '/') {
828
-				$parent = '';
829
-			}
830
-			if ($isBackgroundScan) {
831
-				$parentData = $this->get($parent);
832
-				if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
833
-					$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
834
-				}
835
-			} else {
836
-				$this->correctFolderSize($parent);
837
-			}
838
-		}
839
-	}
840
-
841
-	/**
842
-	 * get the incomplete count that shares parent $folder
843
-	 *
844
-	 * @param int $fileId the file id of the folder
845
-	 * @return int
846
-	 */
847
-	public function getIncompleteChildrenCount($fileId) {
848
-		if ($fileId > -1) {
849
-			$query = $this->getQueryBuilder();
850
-			$query->select($query->func()->count())
851
-				->from('filecache')
852
-				->whereParent($fileId)
853
-				->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
854
-
855
-			$result = $query->execute();
856
-			$size = (int)$result->fetchOne();
857
-			$result->closeCursor();
858
-
859
-			return $size;
860
-		}
861
-		return -1;
862
-	}
863
-
864
-	/**
865
-	 * calculate the size of a folder and set it in the cache
866
-	 *
867
-	 * @param string $path
868
-	 * @param array $entry (optional) meta data of the folder
869
-	 * @return int
870
-	 */
871
-	public function calculateFolderSize($path, $entry = null) {
872
-		$totalSize = 0;
873
-		if (is_null($entry) || !isset($entry['fileid'])) {
874
-			$entry = $this->get($path);
875
-		}
876
-		if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
877
-			$id = $entry['fileid'];
878
-
879
-			$query = $this->getQueryBuilder();
880
-			$query->select('size', 'unencrypted_size')
881
-				->from('filecache')
882
-				->whereParent($id);
883
-
884
-			$result = $query->execute();
885
-			$rows = $result->fetchAll();
886
-			$result->closeCursor();
887
-
888
-			if ($rows) {
889
-				$sizes = array_map(function (array $row) {
890
-					return (int)$row['size'];
891
-				}, $rows);
892
-				$unencryptedOnlySizes = array_map(function (array $row) {
893
-					return (int)$row['unencrypted_size'];
894
-				}, $rows);
895
-				$unencryptedSizes = array_map(function (array $row) {
896
-					return (int)(($row['unencrypted_size'] > 0) ? $row['unencrypted_size'] : $row['size']);
897
-				}, $rows);
898
-
899
-				$sum = array_sum($sizes);
900
-				$min = min($sizes);
901
-
902
-				$unencryptedSum = array_sum($unencryptedSizes);
903
-				$unencryptedMin = min($unencryptedSizes);
904
-				$unencryptedMax = max($unencryptedOnlySizes);
905
-
906
-				$sum = 0 + $sum;
907
-				$min = 0 + $min;
908
-				if ($min === -1) {
909
-					$totalSize = $min;
910
-				} else {
911
-					$totalSize = $sum;
912
-				}
913
-				if ($unencryptedMin === -1 || $min === -1) {
914
-					$unencryptedTotal = $unencryptedMin;
915
-				} else {
916
-					$unencryptedTotal = $unencryptedSum;
917
-				}
918
-			} else {
919
-				$totalSize = 0;
920
-				$unencryptedTotal = 0;
921
-				$unencryptedMax = 0;
922
-			}
923
-			if ($entry['size'] !== $totalSize) {
924
-				// only set unencrypted size for a folder if any child entries have it set, or the folder is empty
925
-				if ($unencryptedMax > 0 || $totalSize === 0) {
926
-					$this->update($id, [
927
-						'size' => $totalSize,
928
-						'unencrypted_size' => $unencryptedTotal,
929
-					]);
930
-				} else {
931
-					$this->update($id, [
932
-						'size' => $totalSize,
933
-					]);
934
-				}
935
-			}
936
-		}
937
-		return $totalSize;
938
-	}
939
-
940
-	/**
941
-	 * get all file ids on the files on the storage
942
-	 *
943
-	 * @return int[]
944
-	 */
945
-	public function getAll() {
946
-		$query = $this->getQueryBuilder();
947
-		$query->select('fileid')
948
-			->from('filecache')
949
-			->whereStorageId($this->getNumericStorageId());
950
-
951
-		$result = $query->execute();
952
-		$files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
-		$result->closeCursor();
954
-
955
-		return array_map(function ($id) {
956
-			return (int)$id;
957
-		}, $files);
958
-	}
959
-
960
-	/**
961
-	 * find a folder in the cache which has not been fully scanned
962
-	 *
963
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
965
-	 * likely the folder where we stopped scanning previously
966
-	 *
967
-	 * @return string|false the path of the folder or false when no folder matched
968
-	 */
969
-	public function getIncomplete() {
970
-		$query = $this->getQueryBuilder();
971
-		$query->select('path')
972
-			->from('filecache')
973
-			->whereStorageId($this->getNumericStorageId())
974
-			->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
-			->orderBy('fileid', 'DESC')
976
-			->setMaxResults(1);
977
-
978
-		$result = $query->execute();
979
-		$path = $result->fetchOne();
980
-		$result->closeCursor();
981
-
982
-		if ($path === false) {
983
-			return false;
984
-		}
985
-
986
-		// Make sure Oracle does not continue with null for empty strings
987
-		return (string)$path;
988
-	}
989
-
990
-	/**
991
-	 * get the path of a file on this storage by it's file id
992
-	 *
993
-	 * @param int $id the file id of the file or folder to search
994
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
995
-	 */
996
-	public function getPathById($id) {
997
-		$query = $this->getQueryBuilder();
998
-		$query->select('path')
999
-			->from('filecache')
1000
-			->whereStorageId($this->getNumericStorageId())
1001
-			->whereFileId($id);
1002
-
1003
-		$result = $query->execute();
1004
-		$path = $result->fetchOne();
1005
-		$result->closeCursor();
1006
-
1007
-		if ($path === false) {
1008
-			return null;
1009
-		}
1010
-
1011
-		return (string)$path;
1012
-	}
1013
-
1014
-	/**
1015
-	 * get the storage id of the storage for a file and the internal path of the file
1016
-	 * unlike getPathById this does not limit the search to files on this storage and
1017
-	 * instead does a global search in the cache table
1018
-	 *
1019
-	 * @param int $id
1020
-	 * @return array first element holding the storage id, second the path
1021
-	 * @deprecated use getPathById() instead
1022
-	 */
1023
-	public static function getById($id) {
1024
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1025
-		$query->select('path', 'storage')
1026
-			->from('filecache')
1027
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1028
-
1029
-		$result = $query->execute();
1030
-		$row = $result->fetch();
1031
-		$result->closeCursor();
1032
-
1033
-		if ($row) {
1034
-			$numericId = $row['storage'];
1035
-			$path = $row['path'];
1036
-		} else {
1037
-			return null;
1038
-		}
1039
-
1040
-		if ($id = Storage::getStorageId($numericId)) {
1041
-			return [$id, $path];
1042
-		} else {
1043
-			return null;
1044
-		}
1045
-	}
1046
-
1047
-	/**
1048
-	 * normalize the given path
1049
-	 *
1050
-	 * @param string $path
1051
-	 * @return string
1052
-	 */
1053
-	public function normalize($path) {
1054
-		return trim(\OC_Util::normalizeUnicode($path), '/');
1055
-	}
1056
-
1057
-	/**
1058
-	 * Copy a file or folder in the cache
1059
-	 *
1060
-	 * @param ICache $sourceCache
1061
-	 * @param ICacheEntry $sourceEntry
1062
-	 * @param string $targetPath
1063
-	 * @return int fileId of copied entry
1064
-	 */
1065
-	public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
1066
-		if ($sourceEntry->getId() < 0) {
1067
-			throw new \RuntimeException("Invalid source cache entry on copyFromCache");
1068
-		}
1069
-		$data = $this->cacheEntryToArray($sourceEntry);
1070
-		$fileId = $this->put($targetPath, $data);
1071
-		if ($fileId <= 0) {
1072
-			throw new \RuntimeException("Failed to copy to " . $targetPath . " from cache with source data " . json_encode($data) . " ");
1073
-		}
1074
-		if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
1075
-			$folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
1076
-			foreach ($folderContent as $subEntry) {
1077
-				$subTargetPath = $targetPath . '/' . $subEntry->getName();
1078
-				$this->copyFromCache($sourceCache, $subEntry, $subTargetPath);
1079
-			}
1080
-		}
1081
-		return $fileId;
1082
-	}
1083
-
1084
-	private function cacheEntryToArray(ICacheEntry $entry): array {
1085
-		return [
1086
-			'size' => $entry->getSize(),
1087
-			'mtime' => $entry->getMTime(),
1088
-			'storage_mtime' => $entry->getStorageMTime(),
1089
-			'mimetype' => $entry->getMimeType(),
1090
-			'mimepart' => $entry->getMimePart(),
1091
-			'etag' => $entry->getEtag(),
1092
-			'permissions' => $entry->getPermissions(),
1093
-			'encrypted' => $entry->isEncrypted(),
1094
-			'creation_time' => $entry->getCreationTime(),
1095
-			'upload_time' => $entry->getUploadTime(),
1096
-			'metadata_etag' => $entry->getMetadataEtag(),
1097
-		];
1098
-	}
1099
-
1100
-	public function getQueryFilterForStorage(): ISearchOperator {
1101
-		return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'storage', $this->getNumericStorageId());
1102
-	}
1103
-
1104
-	public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
1105
-		if ($rawEntry->getStorageId() === $this->getNumericStorageId()) {
1106
-			return $rawEntry;
1107
-		} else {
1108
-			return null;
1109
-		}
1110
-	}
75
+    use MoveFromCacheTrait {
76
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
77
+    }
78
+
79
+    /**
80
+     * @var array partial data for the cache
81
+     */
82
+    protected $partial = [];
83
+
84
+    /**
85
+     * @var string
86
+     */
87
+    protected $storageId;
88
+
89
+    private $storage;
90
+
91
+    /**
92
+     * @var Storage $storageCache
93
+     */
94
+    protected $storageCache;
95
+
96
+    /** @var IMimeTypeLoader */
97
+    protected $mimetypeLoader;
98
+
99
+    /**
100
+     * @var IDBConnection
101
+     */
102
+    protected $connection;
103
+
104
+    /**
105
+     * @var IEventDispatcher
106
+     */
107
+    protected $eventDispatcher;
108
+
109
+    /** @var QuerySearchHelper */
110
+    protected $querySearchHelper;
111
+
112
+    /**
113
+     * @param IStorage $storage
114
+     */
115
+    public function __construct(IStorage $storage) {
116
+        $this->storageId = $storage->getId();
117
+        $this->storage = $storage;
118
+        if (strlen($this->storageId) > 64) {
119
+            $this->storageId = md5($this->storageId);
120
+        }
121
+
122
+        $this->storageCache = new Storage($storage);
123
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
124
+        $this->connection = \OC::$server->getDatabaseConnection();
125
+        $this->eventDispatcher = \OC::$server->get(IEventDispatcher::class);
126
+        $this->querySearchHelper = \OC::$server->query(QuerySearchHelper::class);
127
+    }
128
+
129
+    protected function getQueryBuilder() {
130
+        return new CacheQueryBuilder(
131
+            $this->connection,
132
+            \OC::$server->getSystemConfig(),
133
+            \OC::$server->get(LoggerInterface::class)
134
+        );
135
+    }
136
+
137
+    /**
138
+     * Get the numeric storage id for this cache's storage
139
+     *
140
+     * @return int
141
+     */
142
+    public function getNumericStorageId() {
143
+        return $this->storageCache->getNumericId();
144
+    }
145
+
146
+    /**
147
+     * get the stored metadata of a file or folder
148
+     *
149
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
150
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
151
+     */
152
+    public function get($file) {
153
+        $query = $this->getQueryBuilder();
154
+        $query->selectFileCache();
155
+
156
+        if (is_string($file) || $file == '') {
157
+            // normalize file
158
+            $file = $this->normalize($file);
159
+
160
+            $query->whereStorageId($this->getNumericStorageId())
161
+                ->wherePath($file);
162
+        } else { //file id
163
+            $query->whereFileId($file);
164
+        }
165
+
166
+        $result = $query->execute();
167
+        $data = $result->fetch();
168
+        $result->closeCursor();
169
+
170
+        //merge partial data
171
+        if (!$data && is_string($file) && isset($this->partial[$file])) {
172
+            return $this->partial[$file];
173
+        } elseif (!$data) {
174
+            return $data;
175
+        } else {
176
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
177
+        }
178
+    }
179
+
180
+    /**
181
+     * Create a CacheEntry from database row
182
+     *
183
+     * @param array $data
184
+     * @param IMimeTypeLoader $mimetypeLoader
185
+     * @return CacheEntry
186
+     */
187
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
188
+        //fix types
189
+        $data['name'] = (string)$data['name'];
190
+        $data['path'] = (string)$data['path'];
191
+        $data['fileid'] = (int)$data['fileid'];
192
+        $data['parent'] = (int)$data['parent'];
193
+        $data['size'] = 0 + $data['size'];
194
+        $data['unencrypted_size'] = 0 + ($data['unencrypted_size'] ?? 0);
195
+        $data['mtime'] = (int)$data['mtime'];
196
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
197
+        $data['encryptedVersion'] = (int)$data['encrypted'];
198
+        $data['encrypted'] = (bool)$data['encrypted'];
199
+        $data['storage_id'] = $data['storage'];
200
+        $data['storage'] = (int)$data['storage'];
201
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
202
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
203
+        if ($data['storage_mtime'] == 0) {
204
+            $data['storage_mtime'] = $data['mtime'];
205
+        }
206
+        $data['permissions'] = (int)$data['permissions'];
207
+        if (isset($data['creation_time'])) {
208
+            $data['creation_time'] = (int)$data['creation_time'];
209
+        }
210
+        if (isset($data['upload_time'])) {
211
+            $data['upload_time'] = (int)$data['upload_time'];
212
+        }
213
+        return new CacheEntry($data);
214
+    }
215
+
216
+    /**
217
+     * get the metadata of all files stored in $folder
218
+     *
219
+     * @param string $folder
220
+     * @return ICacheEntry[]
221
+     */
222
+    public function getFolderContents($folder) {
223
+        $fileId = $this->getId($folder);
224
+        return $this->getFolderContentsById($fileId);
225
+    }
226
+
227
+    /**
228
+     * get the metadata of all files stored in $folder
229
+     *
230
+     * @param int $fileId the file id of the folder
231
+     * @return ICacheEntry[]
232
+     */
233
+    public function getFolderContentsById($fileId) {
234
+        if ($fileId > -1) {
235
+            $query = $this->getQueryBuilder();
236
+            $query->selectFileCache()
237
+                ->whereParent($fileId)
238
+                ->orderBy('name', 'ASC');
239
+
240
+            $result = $query->execute();
241
+            $files = $result->fetchAll();
242
+            $result->closeCursor();
243
+
244
+            return array_map(function (array $data) {
245
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);
246
+            }, $files);
247
+        }
248
+        return [];
249
+    }
250
+
251
+    /**
252
+     * insert or update meta data for a file or folder
253
+     *
254
+     * @param string $file
255
+     * @param array $data
256
+     *
257
+     * @return int file id
258
+     * @throws \RuntimeException
259
+     */
260
+    public function put($file, array $data) {
261
+        if (($id = $this->getId($file)) > -1) {
262
+            $this->update($id, $data);
263
+            return $id;
264
+        } else {
265
+            return $this->insert($file, $data);
266
+        }
267
+    }
268
+
269
+    /**
270
+     * insert meta data for a new file or folder
271
+     *
272
+     * @param string $file
273
+     * @param array $data
274
+     *
275
+     * @return int file id
276
+     * @throws \RuntimeException
277
+     */
278
+    public function insert($file, array $data) {
279
+        // normalize file
280
+        $file = $this->normalize($file);
281
+
282
+        if (isset($this->partial[$file])) { //add any saved partial data
283
+            $data = array_merge($this->partial[$file], $data);
284
+            unset($this->partial[$file]);
285
+        }
286
+
287
+        $requiredFields = ['size', 'mtime', 'mimetype'];
288
+        foreach ($requiredFields as $field) {
289
+            if (!isset($data[$field])) { //data not complete save as partial and return
290
+                $this->partial[$file] = $data;
291
+                return -1;
292
+            }
293
+        }
294
+
295
+        $data['path'] = $file;
296
+        if (!isset($data['parent'])) {
297
+            $data['parent'] = $this->getParentId($file);
298
+        }
299
+        $data['name'] = basename($file);
300
+
301
+        [$values, $extensionValues] = $this->normalizeData($data);
302
+        $storageId = $this->getNumericStorageId();
303
+        $values['storage'] = $storageId;
304
+
305
+        try {
306
+            $builder = $this->connection->getQueryBuilder();
307
+            $builder->insert('filecache');
308
+
309
+            foreach ($values as $column => $value) {
310
+                $builder->setValue($column, $builder->createNamedParameter($value));
311
+            }
312
+
313
+            if ($builder->execute()) {
314
+                $fileId = $builder->getLastInsertId();
315
+
316
+                if (count($extensionValues)) {
317
+                    $query = $this->getQueryBuilder();
318
+                    $query->insert('filecache_extended');
319
+
320
+                    $query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
321
+                    foreach ($extensionValues as $column => $value) {
322
+                        $query->setValue($column, $query->createNamedParameter($value));
323
+                    }
324
+                    $query->execute();
325
+                }
326
+
327
+                $event = new CacheEntryInsertedEvent($this->storage, $file, $fileId, $storageId);
328
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, $event);
329
+                $this->eventDispatcher->dispatchTyped($event);
330
+                return $fileId;
331
+            }
332
+        } catch (UniqueConstraintViolationException $e) {
333
+            // entry exists already
334
+            if ($this->connection->inTransaction()) {
335
+                $this->connection->commit();
336
+                $this->connection->beginTransaction();
337
+            }
338
+        }
339
+
340
+        // The file was created in the mean time
341
+        if (($id = $this->getId($file)) > -1) {
342
+            $this->update($id, $data);
343
+            return $id;
344
+        } else {
345
+            throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
346
+        }
347
+    }
348
+
349
+    /**
350
+     * update the metadata of an existing file or folder in the cache
351
+     *
352
+     * @param int $id the fileid of the existing file or folder
353
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
354
+     */
355
+    public function update($id, array $data) {
356
+        if (isset($data['path'])) {
357
+            // normalize path
358
+            $data['path'] = $this->normalize($data['path']);
359
+        }
360
+
361
+        if (isset($data['name'])) {
362
+            // normalize path
363
+            $data['name'] = $this->normalize($data['name']);
364
+        }
365
+
366
+        [$values, $extensionValues] = $this->normalizeData($data);
367
+
368
+        if (count($values)) {
369
+            $query = $this->getQueryBuilder();
370
+
371
+            $query->update('filecache')
372
+                ->whereFileId($id)
373
+                ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
374
+                    return $query->expr()->orX(
375
+                        $query->expr()->neq($key, $query->createNamedParameter($value)),
376
+                        $query->expr()->isNull($key)
377
+                    );
378
+                }, array_keys($values), array_values($values))));
379
+
380
+            foreach ($values as $key => $value) {
381
+                $query->set($key, $query->createNamedParameter($value));
382
+            }
383
+
384
+            $query->execute();
385
+        }
386
+
387
+        if (count($extensionValues)) {
388
+            try {
389
+                $query = $this->getQueryBuilder();
390
+                $query->insert('filecache_extended');
391
+
392
+                $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
393
+                foreach ($extensionValues as $column => $value) {
394
+                    $query->setValue($column, $query->createNamedParameter($value));
395
+                }
396
+
397
+                $query->execute();
398
+            } catch (UniqueConstraintViolationException $e) {
399
+                $query = $this->getQueryBuilder();
400
+                $query->update('filecache_extended')
401
+                    ->whereFileId($id)
402
+                    ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
403
+                        return $query->expr()->orX(
404
+                            $query->expr()->neq($key, $query->createNamedParameter($value)),
405
+                            $query->expr()->isNull($key)
406
+                        );
407
+                    }, array_keys($extensionValues), array_values($extensionValues))));
408
+
409
+                foreach ($extensionValues as $key => $value) {
410
+                    $query->set($key, $query->createNamedParameter($value));
411
+                }
412
+
413
+                $query->execute();
414
+            }
415
+        }
416
+
417
+        $path = $this->getPathById($id);
418
+        // path can still be null if the file doesn't exist
419
+        if ($path !== null) {
420
+            $event = new CacheEntryUpdatedEvent($this->storage, $path, $id, $this->getNumericStorageId());
421
+            $this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event);
422
+            $this->eventDispatcher->dispatchTyped($event);
423
+        }
424
+    }
425
+
426
+    /**
427
+     * extract query parts and params array from data array
428
+     *
429
+     * @param array $data
430
+     * @return array
431
+     */
432
+    protected function normalizeData(array $data): array {
433
+        $fields = [
434
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
435
+            'etag', 'permissions', 'checksum', 'storage', 'unencrypted_size'];
436
+        $extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
437
+
438
+        $doNotCopyStorageMTime = false;
439
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
440
+            // this horrific magic tells it to not copy storage_mtime to mtime
441
+            unset($data['mtime']);
442
+            $doNotCopyStorageMTime = true;
443
+        }
444
+
445
+        $params = [];
446
+        $extensionParams = [];
447
+        foreach ($data as $name => $value) {
448
+            if (array_search($name, $fields) !== false) {
449
+                if ($name === 'path') {
450
+                    $params['path_hash'] = md5($value);
451
+                } elseif ($name === 'mimetype') {
452
+                    $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
453
+                    $value = $this->mimetypeLoader->getId($value);
454
+                } elseif ($name === 'storage_mtime') {
455
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
456
+                        $params['mtime'] = $value;
457
+                    }
458
+                } elseif ($name === 'encrypted') {
459
+                    if (isset($data['encryptedVersion'])) {
460
+                        $value = $data['encryptedVersion'];
461
+                    } else {
462
+                        // Boolean to integer conversion
463
+                        $value = $value ? 1 : 0;
464
+                    }
465
+                }
466
+                $params[$name] = $value;
467
+            }
468
+            if (array_search($name, $extensionFields) !== false) {
469
+                $extensionParams[$name] = $value;
470
+            }
471
+        }
472
+        return [$params, array_filter($extensionParams)];
473
+    }
474
+
475
+    /**
476
+     * get the file id for a file
477
+     *
478
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
479
+     *
480
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
481
+     *
482
+     * @param string $file
483
+     * @return int
484
+     */
485
+    public function getId($file) {
486
+        // normalize file
487
+        $file = $this->normalize($file);
488
+
489
+        $query = $this->getQueryBuilder();
490
+        $query->select('fileid')
491
+            ->from('filecache')
492
+            ->whereStorageId($this->getNumericStorageId())
493
+            ->wherePath($file);
494
+
495
+        $result = $query->execute();
496
+        $id = $result->fetchOne();
497
+        $result->closeCursor();
498
+
499
+        return $id === false ? -1 : (int)$id;
500
+    }
501
+
502
+    /**
503
+     * get the id of the parent folder of a file
504
+     *
505
+     * @param string $file
506
+     * @return int
507
+     */
508
+    public function getParentId($file) {
509
+        if ($file === '') {
510
+            return -1;
511
+        } else {
512
+            $parent = $this->getParentPath($file);
513
+            return (int)$this->getId($parent);
514
+        }
515
+    }
516
+
517
+    private function getParentPath($path) {
518
+        $parent = dirname($path);
519
+        if ($parent === '.') {
520
+            $parent = '';
521
+        }
522
+        return $parent;
523
+    }
524
+
525
+    /**
526
+     * check if a file is available in the cache
527
+     *
528
+     * @param string $file
529
+     * @return bool
530
+     */
531
+    public function inCache($file) {
532
+        return $this->getId($file) != -1;
533
+    }
534
+
535
+    /**
536
+     * remove a file or folder from the cache
537
+     *
538
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
539
+     *
540
+     * @param string $file
541
+     */
542
+    public function remove($file) {
543
+        $entry = $this->get($file);
544
+
545
+        if ($entry instanceof ICacheEntry) {
546
+            $query = $this->getQueryBuilder();
547
+            $query->delete('filecache')
548
+                ->whereFileId($entry->getId());
549
+            $query->execute();
550
+
551
+            $query = $this->getQueryBuilder();
552
+            $query->delete('filecache_extended')
553
+                ->whereFileId($entry->getId());
554
+            $query->execute();
555
+
556
+            if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
557
+                $this->removeChildren($entry);
558
+            }
559
+
560
+            $this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $entry->getPath(), $entry->getId(), $this->getNumericStorageId()));
561
+        }
562
+    }
563
+
564
+    /**
565
+     * Get all sub folders of a folder
566
+     *
567
+     * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
568
+     * @return ICacheEntry[] the cache entries for the subfolders
569
+     */
570
+    private function getSubFolders(ICacheEntry $entry) {
571
+        $children = $this->getFolderContentsById($entry->getId());
572
+        return array_filter($children, function ($child) {
573
+            return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
574
+        });
575
+    }
576
+
577
+    /**
578
+     * Recursively remove all children of a folder
579
+     *
580
+     * @param ICacheEntry $entry the cache entry of the folder to remove the children of
581
+     * @throws \OC\DatabaseException
582
+     */
583
+    private function removeChildren(ICacheEntry $entry) {
584
+        $parentIds = [$entry->getId()];
585
+        $queue = [$entry->getId()];
586
+
587
+        // we walk depth first through the file tree, removing all filecache_extended attributes while we walk
588
+        // and collecting all folder ids to later use to delete the filecache entries
589
+        while ($entryId = array_pop($queue)) {
590
+            $children = $this->getFolderContentsById($entryId);
591
+            $childIds = array_map(function (ICacheEntry $cacheEntry) {
592
+                return $cacheEntry->getId();
593
+            }, $children);
594
+
595
+            $query = $this->getQueryBuilder();
596
+            $query->delete('filecache_extended')
597
+                ->where($query->expr()->in('fileid', $query->createParameter('childIds')));
598
+
599
+            foreach (array_chunk($childIds, 1000) as $childIdChunk) {
600
+                $query->setParameter('childIds', $childIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
601
+                $query->execute();
602
+            }
603
+
604
+            /** @var ICacheEntry[] $childFolders */
605
+            $childFolders = array_filter($children, function ($child) {
606
+                return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
607
+            });
608
+            foreach ($childFolders as $folder) {
609
+                $parentIds[] = $folder->getId();
610
+                $queue[] = $folder->getId();
611
+            }
612
+        }
613
+
614
+        $query = $this->getQueryBuilder();
615
+        $query->delete('filecache')
616
+            ->whereParentInParameter('parentIds');
617
+
618
+        foreach (array_chunk($parentIds, 1000) as $parentIdChunk) {
619
+            $query->setParameter('parentIds', $parentIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
620
+            $query->execute();
621
+        }
622
+    }
623
+
624
+    /**
625
+     * Move a file or folder in the cache
626
+     *
627
+     * @param string $source
628
+     * @param string $target
629
+     */
630
+    public function move($source, $target) {
631
+        $this->moveFromCache($this, $source, $target);
632
+    }
633
+
634
+    /**
635
+     * Get the storage id and path needed for a move
636
+     *
637
+     * @param string $path
638
+     * @return array [$storageId, $internalPath]
639
+     */
640
+    protected function getMoveInfo($path) {
641
+        return [$this->getNumericStorageId(), $path];
642
+    }
643
+
644
+    /**
645
+     * Move a file or folder in the cache
646
+     *
647
+     * @param ICache $sourceCache
648
+     * @param string $sourcePath
649
+     * @param string $targetPath
650
+     * @throws \OC\DatabaseException
651
+     * @throws \Exception if the given storages have an invalid id
652
+     */
653
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
654
+        if ($sourceCache instanceof Cache) {
655
+            // normalize source and target
656
+            $sourcePath = $this->normalize($sourcePath);
657
+            $targetPath = $this->normalize($targetPath);
658
+
659
+            $sourceData = $sourceCache->get($sourcePath);
660
+            if ($sourceData === false) {
661
+                throw new \Exception('Invalid source storage path: ' . $sourcePath);
662
+            }
663
+
664
+            $sourceId = $sourceData['fileid'];
665
+            $newParentId = $this->getParentId($targetPath);
666
+
667
+            [$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
668
+            [$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
669
+
670
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
671
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
672
+            }
673
+            if (is_null($targetStorageId) || $targetStorageId === false) {
674
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
675
+            }
676
+
677
+            $this->connection->beginTransaction();
678
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
679
+                //update all child entries
680
+                $sourceLength = mb_strlen($sourcePath);
681
+                $query = $this->connection->getQueryBuilder();
682
+
683
+                $fun = $query->func();
684
+                $newPathFunction = $fun->concat(
685
+                    $query->createNamedParameter($targetPath),
686
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
687
+                );
688
+                $query->update('filecache')
689
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
690
+                    ->set('path_hash', $fun->md5($newPathFunction))
691
+                    ->set('path', $newPathFunction)
692
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
693
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
694
+
695
+                try {
696
+                    $query->execute();
697
+                } catch (\OC\DatabaseException $e) {
698
+                    $this->connection->rollBack();
699
+                    throw $e;
700
+                }
701
+            }
702
+
703
+            $query = $this->getQueryBuilder();
704
+            $query->update('filecache')
705
+                ->set('storage', $query->createNamedParameter($targetStorageId))
706
+                ->set('path', $query->createNamedParameter($targetPath))
707
+                ->set('path_hash', $query->createNamedParameter(md5($targetPath)))
708
+                ->set('name', $query->createNamedParameter(basename($targetPath)))
709
+                ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
710
+                ->whereFileId($sourceId);
711
+            $query->execute();
712
+
713
+            $this->connection->commit();
714
+
715
+            if ($sourceCache->getNumericStorageId() !== $this->getNumericStorageId()) {
716
+                $this->eventDispatcher->dispatchTyped(new CacheEntryRemovedEvent($this->storage, $sourcePath, $sourceId, $sourceCache->getNumericStorageId()));
717
+                $event = new CacheEntryInsertedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId());
718
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, $event);
719
+                $this->eventDispatcher->dispatchTyped($event);
720
+            } else {
721
+                $event = new CacheEntryUpdatedEvent($this->storage, $targetPath, $sourceId, $this->getNumericStorageId());
722
+                $this->eventDispatcher->dispatch(CacheUpdateEvent::class, $event);
723
+                $this->eventDispatcher->dispatchTyped($event);
724
+            }
725
+        } else {
726
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
727
+        }
728
+    }
729
+
730
+    /**
731
+     * remove all entries for files that are stored on the storage from the cache
732
+     */
733
+    public function clear() {
734
+        $query = $this->getQueryBuilder();
735
+        $query->delete('filecache')
736
+            ->whereStorageId($this->getNumericStorageId());
737
+        $query->execute();
738
+
739
+        $query = $this->connection->getQueryBuilder();
740
+        $query->delete('storages')
741
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
742
+        $query->execute();
743
+    }
744
+
745
+    /**
746
+     * Get the scan status of a file
747
+     *
748
+     * - Cache::NOT_FOUND: File is not in the cache
749
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
750
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
751
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
752
+     *
753
+     * @param string $file
754
+     *
755
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
756
+     */
757
+    public function getStatus($file) {
758
+        // normalize file
759
+        $file = $this->normalize($file);
760
+
761
+        $query = $this->getQueryBuilder();
762
+        $query->select('size')
763
+            ->from('filecache')
764
+            ->whereStorageId($this->getNumericStorageId())
765
+            ->wherePath($file);
766
+
767
+        $result = $query->execute();
768
+        $size = $result->fetchOne();
769
+        $result->closeCursor();
770
+
771
+        if ($size !== false) {
772
+            if ((int)$size === -1) {
773
+                return self::SHALLOW;
774
+            } else {
775
+                return self::COMPLETE;
776
+            }
777
+        } else {
778
+            if (isset($this->partial[$file])) {
779
+                return self::PARTIAL;
780
+            } else {
781
+                return self::NOT_FOUND;
782
+            }
783
+        }
784
+    }
785
+
786
+    /**
787
+     * search for files matching $pattern
788
+     *
789
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
790
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
791
+     */
792
+    public function search($pattern) {
793
+        $operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', $pattern);
794
+        return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
795
+    }
796
+
797
+    /**
798
+     * search for files by mimetype
799
+     *
800
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
801
+     *        where it will search for all mimetypes in the group ('image/*')
802
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
803
+     */
804
+    public function searchByMime($mimetype) {
805
+        if (strpos($mimetype, '/') === false) {
806
+            $operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%');
807
+        } else {
808
+            $operator = new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype);
809
+        }
810
+        return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
811
+    }
812
+
813
+    public function searchQuery(ISearchQuery $searchQuery) {
814
+        return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
815
+    }
816
+
817
+    /**
818
+     * Re-calculate the folder size and the size of all parent folders
819
+     *
820
+     * @param string|boolean $path
821
+     * @param array $data (optional) meta data of the folder
822
+     */
823
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
824
+        $this->calculateFolderSize($path, $data);
825
+        if ($path !== '') {
826
+            $parent = dirname($path);
827
+            if ($parent === '.' || $parent === '/') {
828
+                $parent = '';
829
+            }
830
+            if ($isBackgroundScan) {
831
+                $parentData = $this->get($parent);
832
+                if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
833
+                    $this->correctFolderSize($parent, $parentData, $isBackgroundScan);
834
+                }
835
+            } else {
836
+                $this->correctFolderSize($parent);
837
+            }
838
+        }
839
+    }
840
+
841
+    /**
842
+     * get the incomplete count that shares parent $folder
843
+     *
844
+     * @param int $fileId the file id of the folder
845
+     * @return int
846
+     */
847
+    public function getIncompleteChildrenCount($fileId) {
848
+        if ($fileId > -1) {
849
+            $query = $this->getQueryBuilder();
850
+            $query->select($query->func()->count())
851
+                ->from('filecache')
852
+                ->whereParent($fileId)
853
+                ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
854
+
855
+            $result = $query->execute();
856
+            $size = (int)$result->fetchOne();
857
+            $result->closeCursor();
858
+
859
+            return $size;
860
+        }
861
+        return -1;
862
+    }
863
+
864
+    /**
865
+     * calculate the size of a folder and set it in the cache
866
+     *
867
+     * @param string $path
868
+     * @param array $entry (optional) meta data of the folder
869
+     * @return int
870
+     */
871
+    public function calculateFolderSize($path, $entry = null) {
872
+        $totalSize = 0;
873
+        if (is_null($entry) || !isset($entry['fileid'])) {
874
+            $entry = $this->get($path);
875
+        }
876
+        if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
877
+            $id = $entry['fileid'];
878
+
879
+            $query = $this->getQueryBuilder();
880
+            $query->select('size', 'unencrypted_size')
881
+                ->from('filecache')
882
+                ->whereParent($id);
883
+
884
+            $result = $query->execute();
885
+            $rows = $result->fetchAll();
886
+            $result->closeCursor();
887
+
888
+            if ($rows) {
889
+                $sizes = array_map(function (array $row) {
890
+                    return (int)$row['size'];
891
+                }, $rows);
892
+                $unencryptedOnlySizes = array_map(function (array $row) {
893
+                    return (int)$row['unencrypted_size'];
894
+                }, $rows);
895
+                $unencryptedSizes = array_map(function (array $row) {
896
+                    return (int)(($row['unencrypted_size'] > 0) ? $row['unencrypted_size'] : $row['size']);
897
+                }, $rows);
898
+
899
+                $sum = array_sum($sizes);
900
+                $min = min($sizes);
901
+
902
+                $unencryptedSum = array_sum($unencryptedSizes);
903
+                $unencryptedMin = min($unencryptedSizes);
904
+                $unencryptedMax = max($unencryptedOnlySizes);
905
+
906
+                $sum = 0 + $sum;
907
+                $min = 0 + $min;
908
+                if ($min === -1) {
909
+                    $totalSize = $min;
910
+                } else {
911
+                    $totalSize = $sum;
912
+                }
913
+                if ($unencryptedMin === -1 || $min === -1) {
914
+                    $unencryptedTotal = $unencryptedMin;
915
+                } else {
916
+                    $unencryptedTotal = $unencryptedSum;
917
+                }
918
+            } else {
919
+                $totalSize = 0;
920
+                $unencryptedTotal = 0;
921
+                $unencryptedMax = 0;
922
+            }
923
+            if ($entry['size'] !== $totalSize) {
924
+                // only set unencrypted size for a folder if any child entries have it set, or the folder is empty
925
+                if ($unencryptedMax > 0 || $totalSize === 0) {
926
+                    $this->update($id, [
927
+                        'size' => $totalSize,
928
+                        'unencrypted_size' => $unencryptedTotal,
929
+                    ]);
930
+                } else {
931
+                    $this->update($id, [
932
+                        'size' => $totalSize,
933
+                    ]);
934
+                }
935
+            }
936
+        }
937
+        return $totalSize;
938
+    }
939
+
940
+    /**
941
+     * get all file ids on the files on the storage
942
+     *
943
+     * @return int[]
944
+     */
945
+    public function getAll() {
946
+        $query = $this->getQueryBuilder();
947
+        $query->select('fileid')
948
+            ->from('filecache')
949
+            ->whereStorageId($this->getNumericStorageId());
950
+
951
+        $result = $query->execute();
952
+        $files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
+        $result->closeCursor();
954
+
955
+        return array_map(function ($id) {
956
+            return (int)$id;
957
+        }, $files);
958
+    }
959
+
960
+    /**
961
+     * find a folder in the cache which has not been fully scanned
962
+     *
963
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
+     * use the one with the highest id gives the best result with the background scanner, since that is most
965
+     * likely the folder where we stopped scanning previously
966
+     *
967
+     * @return string|false the path of the folder or false when no folder matched
968
+     */
969
+    public function getIncomplete() {
970
+        $query = $this->getQueryBuilder();
971
+        $query->select('path')
972
+            ->from('filecache')
973
+            ->whereStorageId($this->getNumericStorageId())
974
+            ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
+            ->orderBy('fileid', 'DESC')
976
+            ->setMaxResults(1);
977
+
978
+        $result = $query->execute();
979
+        $path = $result->fetchOne();
980
+        $result->closeCursor();
981
+
982
+        if ($path === false) {
983
+            return false;
984
+        }
985
+
986
+        // Make sure Oracle does not continue with null for empty strings
987
+        return (string)$path;
988
+    }
989
+
990
+    /**
991
+     * get the path of a file on this storage by it's file id
992
+     *
993
+     * @param int $id the file id of the file or folder to search
994
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
995
+     */
996
+    public function getPathById($id) {
997
+        $query = $this->getQueryBuilder();
998
+        $query->select('path')
999
+            ->from('filecache')
1000
+            ->whereStorageId($this->getNumericStorageId())
1001
+            ->whereFileId($id);
1002
+
1003
+        $result = $query->execute();
1004
+        $path = $result->fetchOne();
1005
+        $result->closeCursor();
1006
+
1007
+        if ($path === false) {
1008
+            return null;
1009
+        }
1010
+
1011
+        return (string)$path;
1012
+    }
1013
+
1014
+    /**
1015
+     * get the storage id of the storage for a file and the internal path of the file
1016
+     * unlike getPathById this does not limit the search to files on this storage and
1017
+     * instead does a global search in the cache table
1018
+     *
1019
+     * @param int $id
1020
+     * @return array first element holding the storage id, second the path
1021
+     * @deprecated use getPathById() instead
1022
+     */
1023
+    public static function getById($id) {
1024
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1025
+        $query->select('path', 'storage')
1026
+            ->from('filecache')
1027
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1028
+
1029
+        $result = $query->execute();
1030
+        $row = $result->fetch();
1031
+        $result->closeCursor();
1032
+
1033
+        if ($row) {
1034
+            $numericId = $row['storage'];
1035
+            $path = $row['path'];
1036
+        } else {
1037
+            return null;
1038
+        }
1039
+
1040
+        if ($id = Storage::getStorageId($numericId)) {
1041
+            return [$id, $path];
1042
+        } else {
1043
+            return null;
1044
+        }
1045
+    }
1046
+
1047
+    /**
1048
+     * normalize the given path
1049
+     *
1050
+     * @param string $path
1051
+     * @return string
1052
+     */
1053
+    public function normalize($path) {
1054
+        return trim(\OC_Util::normalizeUnicode($path), '/');
1055
+    }
1056
+
1057
+    /**
1058
+     * Copy a file or folder in the cache
1059
+     *
1060
+     * @param ICache $sourceCache
1061
+     * @param ICacheEntry $sourceEntry
1062
+     * @param string $targetPath
1063
+     * @return int fileId of copied entry
1064
+     */
1065
+    public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
1066
+        if ($sourceEntry->getId() < 0) {
1067
+            throw new \RuntimeException("Invalid source cache entry on copyFromCache");
1068
+        }
1069
+        $data = $this->cacheEntryToArray($sourceEntry);
1070
+        $fileId = $this->put($targetPath, $data);
1071
+        if ($fileId <= 0) {
1072
+            throw new \RuntimeException("Failed to copy to " . $targetPath . " from cache with source data " . json_encode($data) . " ");
1073
+        }
1074
+        if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
1075
+            $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
1076
+            foreach ($folderContent as $subEntry) {
1077
+                $subTargetPath = $targetPath . '/' . $subEntry->getName();
1078
+                $this->copyFromCache($sourceCache, $subEntry, $subTargetPath);
1079
+            }
1080
+        }
1081
+        return $fileId;
1082
+    }
1083
+
1084
+    private function cacheEntryToArray(ICacheEntry $entry): array {
1085
+        return [
1086
+            'size' => $entry->getSize(),
1087
+            'mtime' => $entry->getMTime(),
1088
+            'storage_mtime' => $entry->getStorageMTime(),
1089
+            'mimetype' => $entry->getMimeType(),
1090
+            'mimepart' => $entry->getMimePart(),
1091
+            'etag' => $entry->getEtag(),
1092
+            'permissions' => $entry->getPermissions(),
1093
+            'encrypted' => $entry->isEncrypted(),
1094
+            'creation_time' => $entry->getCreationTime(),
1095
+            'upload_time' => $entry->getUploadTime(),
1096
+            'metadata_etag' => $entry->getMetadataEtag(),
1097
+        ];
1098
+    }
1099
+
1100
+    public function getQueryFilterForStorage(): ISearchOperator {
1101
+        return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'storage', $this->getNumericStorageId());
1102
+    }
1103
+
1104
+    public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry {
1105
+        if ($rawEntry->getStorageId() === $this->getNumericStorageId()) {
1106
+            return $rawEntry;
1107
+        } else {
1108
+            return null;
1109
+        }
1110
+    }
1111 1111
 }
Please login to merge, or discard this patch.
lib/versioncheck.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -27,16 +27,16 @@
 block discarded – undo
27 27
  */
28 28
 // Show warning if a PHP version below 8.0 is used,
29 29
 if (PHP_VERSION_ID < 80000) {
30
-	http_response_code(500);
31
-	echo 'This version of Nextcloud requires at least PHP 8.0<br/>';
32
-	echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.';
33
-	exit(1);
30
+    http_response_code(500);
31
+    echo 'This version of Nextcloud requires at least PHP 8.0<br/>';
32
+    echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.';
33
+    exit(1);
34 34
 }
35 35
 
36 36
 // Show warning if >= PHP 8.3 is used as Nextcloud is not compatible with >= PHP 8.3 for now
37 37
 if (PHP_VERSION_ID >= 80300) {
38
-	http_response_code(500);
39
-	echo 'This version of Nextcloud is not compatible with PHP>=8.3.<br/>';
40
-	echo 'You are currently running ' . PHP_VERSION . '.';
41
-	exit(1);
38
+    http_response_code(500);
39
+    echo 'This version of Nextcloud is not compatible with PHP>=8.3.<br/>';
40
+    echo 'You are currently running ' . PHP_VERSION . '.';
41
+    exit(1);
42 42
 }
Please login to merge, or discard this patch.