Completed
Push — master ( 48eed4...615ba7 )
by
unknown
18:40 queued 02:26
created

Cache::getNumericStorageId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Andreas Fischer <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Florin Peter <[email protected]>
6
 * @author Jens-Christian Fischer <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Michael Gapczynski <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author TheSFReader <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @copyright Copyright (c) 2018, ownCloud GmbH
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OC\Files\Cache;
37
38
use Doctrine\DBAL\Platforms\OraclePlatform;
39
use OCP\Files\Cache\ICache;
40
use OCP\Files\Cache\ICacheEntry;
41
use \OCP\Files\IMimeTypeLoader;
42
use OCP\IDBConnection;
43
44
/**
45
 * Metadata cache for a storage
46
 *
47
 * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms:
48
 *
49
 * - Scanner: scans the storage and updates the cache where needed
50
 * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected
51
 * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed
52
 * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
53
 */
54
class Cache implements ICache {
55
	use MoveFromCacheTrait {
56
		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
57
	}
58
59
	/**
60
	 * @var array partial data for the cache
61
	 */
62
	protected $partial = [];
63
64
	/**
65
	 * @var string
66
	 */
67
	protected $storageId;
68
69
	/**
70
	 * @var Storage $storageCache
71
	 */
72
	protected $storageCache;
73
74
	/** @var IMimeTypeLoader */
75
	protected $mimetypeLoader;
76
77
	/**
78
	 * @var IDBConnection
79
	 */
80
	protected $connection;
81
82
	/**
83
	 * @param \OC\Files\Storage\Storage|string $storage
84
	 */
85
	public function __construct($storage) {
86
		if ($storage instanceof \OC\Files\Storage\Storage) {
87
			$this->storageId = $storage->getId();
88
		} else {
89
			$this->storageId = $storage;
90
		}
91
		if (\strlen($this->storageId) > 64) {
92
			$this->storageId = \md5($this->storageId);
93
		}
94
95
		$this->storageCache = new Storage($storage);
96
		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
97
		$this->connection = \OC::$server->getDatabaseConnection();
98
	}
99
100
	/**
101
	 * Get the numeric storage id for this cache's storage
102
	 *
103
	 * @return int
104
	 */
105
	public function getNumericStorageId() {
106
		return $this->storageCache->getNumericId();
107
	}
108
109
	/**
110
	 * get the stored metadata of a file or folder
111
	 *
112
	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
113
	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
114
	 */
115
	public function get($file) {
116
		if (\is_string($file) or $file == '') {
117
			// normalize file
118
			$file = $this->normalize($file);
119
120
			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
121
			$params = [$this->getNumericStorageId(), \md5($file)];
122
		} else { //file id
123
			$where = 'WHERE `fileid` = ?';
124
			$params = [$file];
125
		}
126
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
127
					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
128
				FROM `*PREFIX*filecache` ' . $where;
129
		$result = $this->connection->executeQuery($sql, $params);
130
		$data = $result->fetch();
131
132
		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
133
		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
134
		if ($data === null) {
135
			$data = false;
136
		}
137
138
		//merge partial data
139
		if ($data) {
140
			//fix types
141
			$data['fileid'] = (int)$data['fileid'];
142
			$data['parent'] = (int)$data['parent'];
143
			$data['size'] = 0 + $data['size'];
144
			$data['mtime'] = (int)$data['mtime'];
145
			$data['storage_mtime'] = (int)$data['storage_mtime'];
146
			$data['encryptedVersion'] = (int)$data['encrypted'];
147
			$data['encrypted'] = (bool)$data['encrypted'];
148
			$data['storage'] = $this->storageId;
149
			$data['mimetype'] = $this->mimetypeLoader->getMimetypeById($data['mimetype']);
150
			$data['mimepart'] = $this->mimetypeLoader->getMimetypeById($data['mimepart']);
151
			if ($data['storage_mtime'] == 0) {
152
				$data['storage_mtime'] = $data['mtime'];
153
			}
154
			$data['permissions'] = (int)$data['permissions'];
155
			// Oracle stores empty strings as null...
156
			if ($data['name'] === null) {
157
				$data['name'] = '';
158
			}
159
			if ($data['path'] === null) {
160
				$data['path'] = '';
161
			}
162
			return new CacheEntry($data);
163
		} elseif (!$data and \is_string($file)) {
164
			if (isset($this->partial[$file])) {
165
				$data = $this->partial[$file];
166
			}
167
			return $data;
168
		} else {
169
			return false;
170
		}
171
	}
172
173
	/**
174
	 * get the metadata of all files stored in $folder
175
	 *
176
	 * @param string $folder
177
	 * @return ICacheEntry[]
178
	 */
179
	public function getFolderContents($folder) {
180
		$fileId = $this->getId($folder);
181
		return $this->getFolderContentsById($fileId);
182
	}
183
184
	/**
185
	 * get the metadata of all files stored in $folder
186
	 *
187
	 * @param int $fileId the file id of the folder
188
	 * @return ICacheEntry[]
189
	 */
190
	public function getFolderContentsById($fileId) {
191
		if ($fileId > -1) {
192
			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
193
						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
194
					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
195
			$result = $this->connection->executeQuery($sql, [$fileId]);
196
			$files = $result->fetchAll();
197
			foreach ($files as &$file) {
198
				$file['mimetype'] = $this->mimetypeLoader->getMimetypeById($file['mimetype']);
199
				$file['mimepart'] = $this->mimetypeLoader->getMimetypeById($file['mimepart']);
200
				if ($file['storage_mtime'] == 0) {
201
					$file['storage_mtime'] = $file['mtime'];
202
				}
203
				$file['permissions'] = (int)$file['permissions'];
204
				$file['mtime'] = (int)$file['mtime'];
205
				$file['storage_mtime'] = (int)$file['storage_mtime'];
206
				$file['size'] = 0 + $file['size'];
207
			}
208
			return \array_map(function (array $data) {
209
				return new CacheEntry($data);
210
			}, $files);
211
		} else {
212
			return [];
213
		}
214
	}
215
216
	/**
217
	 * insert or update meta data for a file or folder
218
	 *
219
	 * @param string $file
220
	 * @param array $data
221
	 *
222
	 * @return int file id
223
	 * @throws \RuntimeException
224
	 */
225 View Code Duplication
	public function put($file, array $data) {
226
		if (($id = $this->getId($file)) > -1) {
227
			$this->update($id, $data);
228
			return $id;
229
		} else {
230
			return $this->insert($file, $data);
231
		}
232
	}
233
234
	/**
235
	 * insert meta data for a new file or folder
236
	 *
237
	 * @param string $file
238
	 * @param array $data
239
	 *
240
	 * @return int file id
241
	 * @throws \RuntimeException
242
	 */
243
	public function insert($file, array $data) {
244
		// normalize file
245
		$file = $this->normalize($file);
246
247
		if (isset($this->partial[$file])) { //add any saved partial data
248
			$data = \array_merge($this->partial[$file], $data);
249
			unset($this->partial[$file]);
250
		}
251
252
		$requiredFields = ['size', 'mtime', 'mimetype'];
253
		foreach ($requiredFields as $field) {
254
			if (!isset($data[$field])) { //data not complete save as partial and return
255
				$this->partial[$file] = $data;
256
				return -1;
257
			}
258
		}
259
260
		$data['path'] = $file;
261
		$data['parent'] = $this->getParentId($file);
262
		$data['name'] = \OC_Util::basename($file);
263
264
		list($queryParts, $params) = $this->buildParts($data);
265
		$queryParts[] = '`storage`';
266
		$params[] = $this->getNumericStorageId();
267
268
		$queryParts = \array_map(function ($item) {
269
			return \trim($item, "`");
270
		}, $queryParts);
271
		$values = \array_combine($queryParts, $params);
272
		// Update or insert this to the filecache
273
		\OC::$server->getDatabaseConnection()->upsert(
274
			'*PREFIX*filecache',
275
			$values,
276
			[
277
				'storage',
278
				'path_hash',
279
			]
280
		);
281
		// Now return the id for this row - crappy that we have to select here
282
		// GetID should already return a value if upsert returned a positive value
283
		return (int)$this->getId($file);
284
	}
285
286
	/**
287
	 * update the metadata of an existing file or folder in the cache
288
	 *
289
	 * @param int $id the fileid of the existing file or folder
290
	 * @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
291
	 */
292
	public function update($id, array $data) {
293
		if (isset($data['path'])) {
294
			// normalize path
295
			$data['path'] = $this->normalize($data['path']);
296
		}
297
298
		if (isset($data['name'])) {
299
			// normalize path
300
			$data['name'] = $this->normalize($data['name']);
301
		}
302
303
		list($queryParts, $params) = $this->buildParts($data);
304
		// duplicate $params because we need the parts twice in the SQL statement
305
		// once for the SET part, once in the WHERE clause
306
		$params = \array_merge($params, $params);
307
		$params[] = $id;
308
309
		// Oracle does not support empty string values so we convert them to nulls
310
		// https://github.com/owncloud/core/issues/31692
311
		if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
312
			foreach ($data as $param => $value) {
313
				if ($value === '') {
314
					$data[$param] = null;
315
				}
316
			}
317
		}
318
319
		// don't update if the data we try to set is the same as the one in the record
320
		// some databases (Postgres) don't like superfluous updates
321
		$sql = 'UPDATE `*PREFIX*filecache` SET ' . \implode(' = ?, ', $queryParts) . '=? ' .
322
			'WHERE (' .
323
			\implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
324
			\implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
325
			') AND `fileid` = ? ';
326
		$this->connection->executeQuery($sql, $params);
327
	}
328
329
	/**
330
	 * extract query parts and params array from data array
331
	 *
332
	 * @param array $data
333
	 * @return array [$queryParts, $params]
334
	 *        $queryParts: string[], the (escaped) column names to be set in the query
335
	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
336
	 */
337
	protected function buildParts(array $data) {
338
		$fields = [
339
			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
340
			'etag', 'permissions', 'checksum'];
341
342
		$doNotCopyStorageMTime = false;
343
		if (\array_key_exists('mtime', $data) && $data['mtime'] === null) {
344
			// this horrific magic tells it to not copy storage_mtime to mtime
345
			unset($data['mtime']);
346
			$doNotCopyStorageMTime = true;
347
		}
348
349
		$params = [];
350
		$queryParts = [];
351
		foreach ($data as $name => $value) {
352
			if (\array_search($name, $fields) !== false) {
353
				if ($name === 'path') {
354
					$params[] = \md5($value);
355
					$queryParts[] = '`path_hash`';
356
				} elseif ($name === 'mimetype') {
357
					$params[] = $this->mimetypeLoader->getId(\substr($value, 0, \strpos($value, '/')));
358
					$queryParts[] = '`mimepart`';
359
					$value = $this->mimetypeLoader->getId($value);
360
				} elseif ($name === 'storage_mtime') {
361
					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
362
						$params[] = $value;
363
						$queryParts[] = '`mtime`';
364
					}
365
				} elseif ($name === 'encrypted') {
366
					if (isset($data['encryptedVersion'])) {
367
						$value = $data['encryptedVersion'];
368
					} else {
369
						// Boolean to integer conversion
370
						$value = $value ? 1 : 0;
371
					}
372
				}
373
				$params[] = $value;
374
				$queryParts[] = '`' . $name . '`';
375
			}
376
		}
377
		return [$queryParts, $params];
378
	}
379
380
	/**
381
	 * get the file id for a file
382
	 *
383
	 * 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
384
	 *
385
	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
386
	 *
387
	 * @param string $file
388
	 * @return int
389
	 */
390
	public function getId($file) {
391
		// normalize file
392
		$file = $this->normalize($file);
393
394
		$pathHash = \md5($file);
395
396
		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
397
		$result = $this->connection->executeQuery($sql, [$this->getNumericStorageId(), $pathHash]);
398
		if ($row = $result->fetch()) {
399
			return $row['fileid'];
400
		} else {
401
			return -1;
402
		}
403
	}
404
405
	/**
406
	 * get the id of the parent folder of a file
407
	 *
408
	 * @param string $file
409
	 * @return int
410
	 */
411
	public function getParentId($file) {
412
		if ($file === '') {
413
			return -1;
414
		} else {
415
			$parent = $this->getParentPath($file);
416
			return (int)$this->getId($parent);
417
		}
418
	}
419
420
	private function getParentPath($path) {
421
		$parent = \dirname($path);
422
		if ($parent === '.') {
423
			$parent = '';
424
		}
425
		return $parent;
426
	}
427
428
	/**
429
	 * check if a file is available in the cache
430
	 *
431
	 * @param string $file
432
	 * @return bool
433
	 */
434
	public function inCache($file) {
435
		return $this->getId($file) != -1;
436
	}
437
438
	/**
439
	 * remove a file or folder from the cache
440
	 *
441
	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
442
	 *
443
	 * @param string $file
444
	 */
445 View Code Duplication
	public function remove($file) {
446
		$entry = $this->get($file);
447
		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
448
		$this->connection->executeQuery($sql, [$entry['fileid']]);
449
		if ($entry['mimetype'] === 'httpd/unix-directory') {
450
			$this->removeChildren($entry);
0 ignored issues
show
Documentation introduced by
$entry is of type object<OCP\Files\Cache\ICacheEntry>|false, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
451
		}
452
	}
453
454
	/**
455
	 * Get all sub folders of a folder
456
	 *
457
	 * @param array $entry the cache entry of the folder to get the subfolders for
458
	 * @return array[] the cache entries for the subfolders
459
	 */
460
	private function getSubFolders($entry) {
461
		$children = $this->getFolderContentsById($entry['fileid']);
462
		return \array_filter($children, function ($child) {
463
			return $child['mimetype'] === 'httpd/unix-directory';
464
		});
465
	}
466
467
	/**
468
	 * Recursively remove all children of a folder
469
	 *
470
	 * @param array $entry the cache entry of the folder to remove the children of
471
	 * @throws \OC\DatabaseException
472
	 */
473 View Code Duplication
	private function removeChildren($entry) {
474
		$subFolders = $this->getSubFolders($entry);
475
		foreach ($subFolders as $folder) {
476
			$this->removeChildren($folder);
477
		}
478
		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
479
		$this->connection->executeQuery($sql, [$entry['fileid']]);
480
	}
481
482
	/**
483
	 * Move a file or folder in the cache
484
	 *
485
	 * @param string $source
486
	 * @param string $target
487
	 */
488
	public function move($source, $target) {
489
		$this->moveFromCache($this, $source, $target);
490
	}
491
492
	/**
493
	 * Get the storage id and path needed for a move
494
	 *
495
	 * @param string $path
496
	 * @return array [$storageId, $internalPath]
497
	 */
498
	protected function getMoveInfo($path) {
499
		return [$this->getNumericStorageId(), $path];
500
	}
501
502
	/**
503
	 * Move a file or folder in the cache
504
	 *
505
	 * @param \OCP\Files\Cache\ICache $sourceCache
506
	 * @param string $sourcePath
507
	 * @param string $targetPath
508
	 * @throws \OC\DatabaseException
509
	 * @throws \Exception if the given storages have an invalid id
510
	 */
511
	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
512
		if ($sourceCache instanceof Cache) {
513
			// normalize source and target
514
			$sourcePath = $this->normalize($sourcePath);
515
			$targetPath = $this->normalize($targetPath);
516
517
			$sourceData = $sourceCache->get($sourcePath);
518
			$sourceId = $sourceData['fileid'];
519
			$newParentId = $this->getParentId($targetPath);
520
521
			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
522
			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
523
524
			if ($sourceStorageId === null || $sourceStorageId === false) {
525
				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
526
			}
527
			if ($targetStorageId === null || $targetStorageId === false) {
528
				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
529
			}
530
531
			// sql for final update
532
			$moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` =  ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
533
534
			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
535
				//find all child entries
536
				$sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
537
				$result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
538
				$childEntries = $result->fetchAll();
539
				$sourceLength = \strlen($sourcePath);
540
				$this->connection->beginTransaction();
541
				$query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
542
543
				foreach ($childEntries as $child) {
544
					$newTargetPath = $targetPath . \substr($child['path'], $sourceLength);
545
					$query->execute([$targetStorageId, $newTargetPath, \md5($newTargetPath), $child['fileid']]);
546
				}
547
				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, \md5($targetPath), \basename($targetPath), $newParentId, $sourceId]);
548
				$this->connection->commit();
549
			} else {
550
				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, \md5($targetPath), \basename($targetPath), $newParentId, $sourceId]);
551
			}
552
		} else {
553
			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
554
		}
555
	}
556
557
	/**
558
	 * remove all entries for files that are stored on the storage from the cache
559
	 */
560
	public function clear() {
561
		Storage::remove($this->storageId);
562
	}
563
564
	/**
565
	 * Get the scan status of a file
566
	 *
567
	 * - Cache::NOT_FOUND: File is not in the cache
568
	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
569
	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
570
	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
571
	 *
572
	 * @param string $file
573
	 *
574
	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
575
	 */
576
	public function getStatus($file) {
577
		// normalize file
578
		$file = $this->normalize($file);
579
580
		$pathHash = \md5($file);
581
		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
582
		$result = $this->connection->executeQuery($sql, [$this->getNumericStorageId(), $pathHash]);
583
		if ($row = $result->fetch()) {
584
			if ((int)$row['size'] === -1) {
585
				return self::SHALLOW;
586
			} else {
587
				return self::COMPLETE;
588
			}
589
		} else {
590
			if (isset($this->partial[$file])) {
591
				return self::PARTIAL;
592
			} else {
593
				return self::NOT_FOUND;
594
			}
595
		}
596
	}
597
598
	/**
599
	 * search for files matching $pattern
600
	 *
601
	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
602
	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
603
	 */
604
	public function search($pattern) {
605
		// normalize pattern
606
		$pattern = $this->normalize($pattern);
607
608
		$sql = '
609
			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
610
				`mimetype`, `mimepart`, `size`, `mtime`, `encrypted`,
611
				`etag`, `permissions`, `checksum`
612
			FROM `*PREFIX*filecache`
613
			WHERE `storage` = ? AND `name` ILIKE ?';
614
		$result = $this->connection->executeQuery($sql,
615
			[$this->getNumericStorageId(), $pattern]
616
		);
617
618
		$files = [];
619 View Code Duplication
		while ($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
620
			$row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']);
621
			$row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']);
622
			$files[] = $row;
623
		}
624
		return \array_map(function (array $data) {
625
			return new CacheEntry($data);
626
		}, $files);
627
	}
628
629
	/**
630
	 * search for files by mimetype
631
	 *
632
	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
633
	 *        where it will search for all mimetypes in the group ('image/*')
634
	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
635
	 */
636
	public function searchByMime($mimetype) {
637
		if (\strpos($mimetype, '/')) {
638
			$where = '`mimetype` = ?';
639
		} else {
640
			$where = '`mimepart` = ?';
641
		}
642
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
643
				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
644
		$mimetype = $this->mimetypeLoader->getId($mimetype);
645
		$result = $this->connection->executeQuery($sql, [$mimetype, $this->getNumericStorageId()]);
646
		$files = [];
647 View Code Duplication
		while ($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
648
			$row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']);
649
			$row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']);
650
			$files[] = $row;
651
		}
652
		return \array_map(function (array $data) {
653
			return new CacheEntry($data);
654
		}, $files);
655
	}
656
657
	/**
658
	 * Search for files by tag of a given users.
659
	 *
660
	 * Note that every user can tag files differently.
661
	 *
662
	 * @param string|int $tag name or tag id
663
	 * @param string $userId owner of the tags
664
	 * @return ICacheEntry[] file data
665
	 */
666
	public function searchByTag($tag, $userId) {
667
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
668
			'`mimetype`, `mimepart`, `size`, `mtime`, ' .
669
			'`encrypted`, `etag`, `permissions`, `checksum` ' .
670
			'FROM `*PREFIX*filecache` `file`, ' .
671
			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
672
			'`*PREFIX*vcategory` `tag` ' .
673
			// JOIN filecache to vcategory_to_object
674
			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
675
			// JOIN vcategory_to_object to vcategory
676
			'AND `tagmap`.`type` = `tag`.`type` ' .
677
			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
678
			// conditions
679
			'AND `file`.`storage` = ? ' .
680
			'AND `tag`.`type` = \'files\' ' .
681
			'AND `tag`.`uid` = ? ';
682
		if (\is_int($tag)) {
683
			$sql .= 'AND `tag`.`id` = ? ';
684
		} else {
685
			$sql .= 'AND `tag`.`category` = ? ';
686
		}
687
		$result = $this->connection->executeQuery(
688
			$sql,
689
			[
690
				$this->getNumericStorageId(),
691
				$userId,
692
				$tag
693
			]
694
		);
695
		$files = [];
696
		while ($row = $result->fetch()) {
697
			$files[] = $row;
698
		}
699
		return \array_map(function (array $data) {
700
			return new CacheEntry($data);
701
		}, $files);
702
	}
703
704
	/**
705
	 * Re-calculate the folder size and the size of all parent folders
706
	 *
707
	 * @param string|boolean $path
708
	 * @param array $data (optional) meta data of the folder
709
	 */
710
	public function correctFolderSize($path, $data = null) {
711
		$this->calculateFolderSize($path, $data);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 710 can also be of type boolean; however, OC\Files\Cache\Cache::calculateFolderSize() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
712
		if ($path !== '') {
713
			$parent = \dirname($path);
714
			if ($parent === '.' or $parent === '/') {
715
				$parent = '';
716
			}
717
			$this->correctFolderSize($parent);
718
		}
719
	}
720
721
	/**
722
	 * calculate the size of a folder and set it in the cache
723
	 *
724
	 * @param string $path
725
	 * @param array $entry (optional) meta data of the folder
726
	 * @return int
727
	 */
728
	public function calculateFolderSize($path, $entry = null) {
729
		$totalSize = 0;
730
		if ($entry === null or !isset($entry['fileid'])) {
731
			$entry = $this->get($path);
732
		}
733
		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
734
			$id = $entry['fileid'];
735
			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
736
				'FROM `*PREFIX*filecache` ' .
737
				'WHERE `parent` = ? AND `storage` = ?';
738
			$result = $this->connection->executeQuery($sql, [$id, $this->getNumericStorageId()]);
739
			if ($row = $result->fetch()) {
740
				$result->closeCursor();
741
				list($sum, $min) = \array_values($row);
742
				$sum = 0 + $sum;
743
				$min = 0 + $min;
744
				if ($min === -1) {
745
					$totalSize = $min;
746
				} else {
747
					$totalSize = $sum;
748
				}
749
				$update = [];
750
				if ($entry['size'] !== $totalSize) {
751
					$update['size'] = $totalSize;
752
				}
753
				if (\count($update) > 0) {
754
					$this->update($id, $update);
755
				}
756
			} else {
757
				$result->closeCursor();
758
			}
759
		}
760
		return $totalSize;
761
	}
762
763
	/**
764
	 * find a folder in the cache which has not been fully scanned
765
	 *
766
	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
767
	 * use the one with the highest id gives the best result with the background scanner, since that is most
768
	 * likely the folder where we stopped scanning previously
769
	 *
770
	 * @return string|bool the path of the folder or false when no folder matched
771
	 */
772
	public function getIncomplete() {
773
		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
774
			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
775
		$query->execute([$this->getNumericStorageId()]);
776
		if ($row = $query->fetch()) {
777
			return $row['path'];
778
		} else {
779
			return false;
780
		}
781
	}
782
783
	/**
784
	 * get the path of a file on this storage by it's file id
785
	 *
786
	 * @param int $id the file id of the file or folder to search
787
	 * @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
788
	 */
789
	public function getPathById($id) {
790
		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
791
		$result = $this->connection->executeQuery($sql, [$id, $this->getNumericStorageId()]);
792
		if ($row = $result->fetch()) {
793
			// Oracle stores empty strings as null...
794
			if ($row['path'] === null) {
795
				return '';
796
			}
797
			return $row['path'];
798
		} else {
799
			return null;
800
		}
801
	}
802
803
	/**
804
	 * normalize the given path
805
	 *
806
	 * @param string $path
807
	 * @return string
808
	 */
809
	public function normalize($path) {
810
		return \trim(\OC_Util::normalizeUnicode($path), '/');
811
	}
812
}
813