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