Completed
Push — master ( 74ac5d...2a8e92 )
by Robin
24:33
created

Cache::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 4
nop 1
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Andreas Fischer <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Florin Peter <[email protected]>
8
 * @author Jens-Christian Fischer <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Michael Gapczynski <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author TheSFReader <[email protected]>
18
 * @author Thomas Müller <[email protected]>
19
 * @author Vincent Petry <[email protected]>
20
 *
21
 * @license AGPL-3.0
22
 *
23
 * This code is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License, version 3,
25
 * as published by the Free Software Foundation.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
 * GNU Affero General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU Affero General Public License, version 3,
33
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
34
 *
35
 */
36
37
namespace OC\Files\Cache;
38
39
use Doctrine\DBAL\Driver\Statement;
40
use OCP\Files\Cache\ICache;
41
use OCP\Files\Cache\ICacheEntry;
42
use \OCP\Files\IMimeTypeLoader;
43
use OCP\Files\Search\ISearchQuery;
44
use OCP\IDBConnection;
45
46
/**
47
 * Metadata cache for a storage
48
 *
49
 * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms:
50
 *
51
 * - Scanner: scans the storage and updates the cache where needed
52
 * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected
53
 * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed
54
 * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
55
 */
56
class Cache implements ICache {
57
	use MoveFromCacheTrait {
58
		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
59
	}
60
61
	/**
62
	 * @var array partial data for the cache
63
	 */
64
	protected $partial = array();
65
66
	/**
67
	 * @var string
68
	 */
69
	protected $storageId;
70
71
	/**
72
	 * @var Storage $storageCache
73
	 */
74
	protected $storageCache;
75
76
	/** @var IMimeTypeLoader */
77
	protected $mimetypeLoader;
78
79
	/**
80
	 * @var IDBConnection
81
	 */
82
	protected $connection;
83
84
	/** @var QuerySearchHelper */
85
	protected $querySearchHelper;
86
87
	/**
88
	 * @param \OC\Files\Storage\Storage|string $storage
89
	 */
90
	public function __construct($storage) {
91
		if ($storage instanceof \OC\Files\Storage\Storage) {
92
			$this->storageId = $storage->getId();
93
		} else {
94
			$this->storageId = $storage;
95
		}
96
		if (strlen($this->storageId) > 64) {
97
			$this->storageId = md5($this->storageId);
98
		}
99
100
		$this->storageCache = new Storage($storage);
101
		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
102
		$this->connection = \OC::$server->getDatabaseConnection();
103
		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
104
	}
105
106
	/**
107
	 * Get the numeric storage id for this cache's storage
108
	 *
109
	 * @return int
110
	 */
111
	public function getNumericStorageId() {
112
		return $this->storageCache->getNumericId();
113
	}
114
115
	/**
116
	 * get the stored metadata of a file or folder
117
	 *
118
	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
119
	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
120
	 */
121
	public function get($file) {
122
		if (is_string($file) or $file == '') {
123
			// normalize file
124
			$file = $this->normalize($file);
125
126
			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
127
			$params = array($this->getNumericStorageId(), md5($file));
128
		} else { //file id
129
			$where = 'WHERE `fileid` = ?';
130
			$params = array($file);
131
		}
132
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
133
					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
134
				FROM `*PREFIX*filecache` ' . $where;
135
		$result = $this->connection->executeQuery($sql, $params);
136
		$data = $result->fetch();
137
138
		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
139
		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
140
		if ($data === null) {
141
			$data = false;
142
		}
143
144
		//merge partial data
145
		if (!$data and is_string($file)) {
146
			if (isset($this->partial[$file])) {
147
				$data = $this->partial[$file];
148
			}
149
			return $data;
150
		} else {
151
			return self::cacheEntryFromData($data, $this->mimetypeLoader);
152
		}
153
	}
154
155
	/**
156
	 * Create a CacheEntry from database row
157
	 *
158
	 * @param array $data
159
	 * @param IMimeTypeLoader $mimetypeLoader
160
	 * @return CacheEntry
161
	 */
162
	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
163
		//fix types
164
		$data['fileid'] = (int)$data['fileid'];
165
		$data['parent'] = (int)$data['parent'];
166
		$data['size'] = 0 + $data['size'];
167
		$data['mtime'] = (int)$data['mtime'];
168
		$data['storage_mtime'] = (int)$data['storage_mtime'];
169
		$data['encryptedVersion'] = (int)$data['encrypted'];
170
		$data['encrypted'] = (bool)$data['encrypted'];
171
		$data['storage_id'] = $data['storage'];
172
		$data['storage'] = (int)$data['storage'];
173
		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
174
		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
175
		if ($data['storage_mtime'] == 0) {
176
			$data['storage_mtime'] = $data['mtime'];
177
		}
178
		$data['permissions'] = (int)$data['permissions'];
179
		return new CacheEntry($data);
180
	}
181
182
	/**
183
	 * get the metadata of all files stored in $folder
184
	 *
185
	 * @param string $folder
186
	 * @return ICacheEntry[]
187
	 */
188
	public function getFolderContents($folder) {
189
		$fileId = $this->getId($folder);
190
		return $this->getFolderContentsById($fileId);
191
	}
192
193
	/**
194
	 * get the metadata of all files stored in $folder
195
	 *
196
	 * @param int $fileId the file id of the folder
197
	 * @return ICacheEntry[]
198
	 */
199
	public function getFolderContentsById($fileId) {
200
		if ($fileId > -1) {
201
			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
202
						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
203
					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
204
			$result = $this->connection->executeQuery($sql, [$fileId]);
205
			$files = $result->fetchAll();
206
			return array_map(function (array $data) {
207
				return self::cacheEntryFromData($data, $this->mimetypeLoader);;
208
			}, $files);
209
		} else {
210
			return array();
211
		}
212
	}
213
214
	/**
215
	 * insert or update meta data for a file or folder
216
	 *
217
	 * @param string $file
218
	 * @param array $data
219
	 *
220
	 * @return int file id
221
	 * @throws \RuntimeException
222
	 */
223 View Code Duplication
	public function put($file, array $data) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
224
		if (($id = $this->getId($file)) > -1) {
225
			$this->update($id, $data);
226
			return $id;
227
		} else {
228
			return $this->insert($file, $data);
229
		}
230
	}
231
232
	/**
233
	 * insert meta data for a new file or folder
234
	 *
235
	 * @param string $file
236
	 * @param array $data
237
	 *
238
	 * @return int file id
239
	 * @throws \RuntimeException
240
	 */
241
	public function insert($file, array $data) {
242
		// normalize file
243
		$file = $this->normalize($file);
244
245
		if (isset($this->partial[$file])) { //add any saved partial data
246
			$data = array_merge($this->partial[$file], $data);
247
			unset($this->partial[$file]);
248
		}
249
250
		$requiredFields = array('size', 'mtime', 'mimetype');
251
		foreach ($requiredFields as $field) {
252
			if (!isset($data[$field])) { //data not complete save as partial and return
253
				$this->partial[$file] = $data;
254
				return -1;
255
			}
256
		}
257
258
		$data['path'] = $file;
259
		$data['parent'] = $this->getParentId($file);
260
		$data['name'] = \OC_Util::basename($file);
261
262
		list($queryParts, $params) = $this->buildParts($data);
263
		$queryParts[] = '`storage`';
264
		$params[] = $this->getNumericStorageId();
265
266
		$queryParts = array_map(function ($item) {
267
			return trim($item, "`");
268
		}, $queryParts);
269
		$values = array_combine($queryParts, $params);
270
		if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
271
			'storage',
272
			'path_hash',
273
		])
274
		) {
275
			return (int)$this->connection->lastInsertId('*PREFIX*filecache');
276
		}
277
278
		// The file was created in the mean time
279
		if (($id = $this->getId($file)) > -1) {
280
			$this->update($id, $data);
281
			return $id;
282
		} else {
283
			throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
284
		}
285
	}
286
287
	/**
288
	 * update the metadata of an existing file or folder in the cache
289
	 *
290
	 * @param int $id the fileid of the existing file or folder
291
	 * @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
292
	 */
293
	public function update($id, array $data) {
294
295
		if (isset($data['path'])) {
296
			// normalize path
297
			$data['path'] = $this->normalize($data['path']);
298
		}
299
300
		if (isset($data['name'])) {
301
			// normalize path
302
			$data['name'] = $this->normalize($data['name']);
303
		}
304
305
		list($queryParts, $params) = $this->buildParts($data);
306
		// duplicate $params because we need the parts twice in the SQL statement
307
		// once for the SET part, once in the WHERE clause
308
		$params = array_merge($params, $params);
309
		$params[] = $id;
310
311
		// don't update if the data we try to set is the same as the one in the record
312
		// some databases (Postgres) don't like superfluous updates
313
		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
314
			'WHERE (' .
315
			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
316
			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
317
			') AND `fileid` = ? ';
318
		$this->connection->executeQuery($sql, $params);
319
320
	}
321
322
	/**
323
	 * extract query parts and params array from data array
324
	 *
325
	 * @param array $data
326
	 * @return array [$queryParts, $params]
327
	 *        $queryParts: string[], the (escaped) column names to be set in the query
328
	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
329
	 */
330
	protected function buildParts(array $data) {
331
		$fields = array(
332
			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
333
			'etag', 'permissions', 'checksum');
334
335
		$doNotCopyStorageMTime = false;
336
		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
337
			// this horrific magic tells it to not copy storage_mtime to mtime
338
			unset($data['mtime']);
339
			$doNotCopyStorageMTime = true;
340
		}
341
342
		$params = array();
343
		$queryParts = array();
344
		foreach ($data as $name => $value) {
345
			if (array_search($name, $fields) !== false) {
346
				if ($name === 'path') {
347
					$params[] = md5($value);
348
					$queryParts[] = '`path_hash`';
349
				} elseif ($name === 'mimetype') {
350
					$params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
351
					$queryParts[] = '`mimepart`';
352
					$value = $this->mimetypeLoader->getId($value);
353
				} elseif ($name === 'storage_mtime') {
354
					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
355
						$params[] = $value;
356
						$queryParts[] = '`mtime`';
357
					}
358
				} elseif ($name === 'encrypted') {
359
					if (isset($data['encryptedVersion'])) {
360
						$value = $data['encryptedVersion'];
361
					} else {
362
						// Boolean to integer conversion
363
						$value = $value ? 1 : 0;
364
					}
365
				}
366
				$params[] = $value;
367
				$queryParts[] = '`' . $name . '`';
368
			}
369
		}
370
		return array($queryParts, $params);
371
	}
372
373
	/**
374
	 * get the file id for a file
375
	 *
376
	 * 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
377
	 *
378
	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
379
	 *
380
	 * @param string $file
381
	 * @return int
382
	 */
383
	public function getId($file) {
384
		// normalize file
385
		$file = $this->normalize($file);
386
387
		$pathHash = md5($file);
388
389
		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
390
		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
391
		if ($row = $result->fetch()) {
392
			return $row['fileid'];
393
		} else {
394
			return -1;
395
		}
396
	}
397
398
	/**
399
	 * get the id of the parent folder of a file
400
	 *
401
	 * @param string $file
402
	 * @return int
403
	 */
404
	public function getParentId($file) {
405
		if ($file === '') {
406
			return -1;
407
		} else {
408
			$parent = $this->getParentPath($file);
409
			return (int)$this->getId($parent);
410
		}
411
	}
412
413
	private function getParentPath($path) {
414
		$parent = dirname($path);
415
		if ($parent === '.') {
416
			$parent = '';
417
		}
418
		return $parent;
419
	}
420
421
	/**
422
	 * check if a file is available in the cache
423
	 *
424
	 * @param string $file
425
	 * @return bool
426
	 */
427
	public function inCache($file) {
428
		return $this->getId($file) != -1;
429
	}
430
431
	/**
432
	 * remove a file or folder from the cache
433
	 *
434
	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
435
	 *
436
	 * @param string $file
437
	 */
438 View Code Duplication
	public function remove($file) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
439
		$entry = $this->get($file);
440
		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
441
		$this->connection->executeQuery($sql, array($entry['fileid']));
442
		if ($entry['mimetype'] === 'httpd/unix-directory') {
443
			$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...
444
		}
445
	}
446
447
	/**
448
	 * Get all sub folders of a folder
449
	 *
450
	 * @param array $entry the cache entry of the folder to get the subfolders for
451
	 * @return array[] the cache entries for the subfolders
452
	 */
453
	private function getSubFolders($entry) {
454
		$children = $this->getFolderContentsById($entry['fileid']);
455
		return array_filter($children, function ($child) {
456
			return $child['mimetype'] === 'httpd/unix-directory';
457
		});
458
	}
459
460
	/**
461
	 * Recursively remove all children of a folder
462
	 *
463
	 * @param array $entry the cache entry of the folder to remove the children of
464
	 * @throws \OC\DatabaseException
465
	 */
466 View Code Duplication
	private function removeChildren($entry) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
467
		$subFolders = $this->getSubFolders($entry);
468
		foreach ($subFolders as $folder) {
469
			$this->removeChildren($folder);
470
		}
471
		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
472
		$this->connection->executeQuery($sql, array($entry['fileid']));
473
	}
474
475
	/**
476
	 * Move a file or folder in the cache
477
	 *
478
	 * @param string $source
479
	 * @param string $target
480
	 */
481
	public function move($source, $target) {
482
		$this->moveFromCache($this, $source, $target);
483
	}
484
485
	/**
486
	 * Get the storage id and path needed for a move
487
	 *
488
	 * @param string $path
489
	 * @return array [$storageId, $internalPath]
490
	 */
491
	protected function getMoveInfo($path) {
492
		return [$this->getNumericStorageId(), $path];
493
	}
494
495
	/**
496
	 * Move a file or folder in the cache
497
	 *
498
	 * @param \OCP\Files\Cache\ICache $sourceCache
499
	 * @param string $sourcePath
500
	 * @param string $targetPath
501
	 * @throws \OC\DatabaseException
502
	 */
503
	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
504
		if ($sourceCache instanceof Cache) {
505
			// normalize source and target
506
			$sourcePath = $this->normalize($sourcePath);
507
			$targetPath = $this->normalize($targetPath);
508
509
			$sourceData = $sourceCache->get($sourcePath);
510
			$sourceId = $sourceData['fileid'];
511
			$newParentId = $this->getParentId($targetPath);
512
513
			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
514
			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
515
516
			// sql for final update
517
			$moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` =  ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
518
519
			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
520
				//find all child entries
521
				$sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
522
				$result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
523
				$childEntries = $result->fetchAll();
524
				$sourceLength = strlen($sourcePath);
525
				$this->connection->beginTransaction();
526
				$query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
527
528
				foreach ($childEntries as $child) {
529
					$newTargetPath = $targetPath . substr($child['path'], $sourceLength);
530
					$query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
531
				}
532
				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
533
				$this->connection->commit();
534
			} else {
535
				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
536
			}
537
		} else {
538
			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
539
		}
540
	}
541
542
	/**
543
	 * remove all entries for files that are stored on the storage from the cache
544
	 */
545
	public function clear() {
546
		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
547
		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
548
549
		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
550
		$this->connection->executeQuery($sql, array($this->storageId));
551
	}
552
553
	/**
554
	 * Get the scan status of a file
555
	 *
556
	 * - Cache::NOT_FOUND: File is not in the cache
557
	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
558
	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
559
	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
560
	 *
561
	 * @param string $file
562
	 *
563
	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
564
	 */
565
	public function getStatus($file) {
566
		// normalize file
567
		$file = $this->normalize($file);
568
569
		$pathHash = md5($file);
570
		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
571
		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
572
		if ($row = $result->fetch()) {
573
			if ((int)$row['size'] === -1) {
574
				return self::SHALLOW;
575
			} else {
576
				return self::COMPLETE;
577
			}
578
		} else {
579
			if (isset($this->partial[$file])) {
580
				return self::PARTIAL;
581
			} else {
582
				return self::NOT_FOUND;
583
			}
584
		}
585
	}
586
587
	/**
588
	 * search for files matching $pattern
589
	 *
590
	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
591
	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
592
	 */
593
	public function search($pattern) {
594
		// normalize pattern
595
		$pattern = $this->normalize($pattern);
596
597
598
		$sql = '
599
			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
600
				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
601
				 `encrypted`, `etag`, `permissions`, `checksum`
602
			FROM `*PREFIX*filecache`
603
			WHERE `storage` = ? AND `name` ILIKE ?';
604
		$result = $this->connection->executeQuery($sql,
605
			[$this->getNumericStorageId(), $pattern]
606
		);
607
608
		return $this->searchResultToCacheEntries($result);
609
	}
610
611
	/**
612
	 * @param Statement $result
613
	 * @return CacheEntry[]
614
	 */
615
	private function searchResultToCacheEntries(Statement $result) {
616
		$files = $result->fetchAll();
617
618
		return array_map(function (array $data) {
619
			return self::cacheEntryFromData($data, $this->mimetypeLoader);
620
		}, $files);
621
	}
622
623
	/**
624
	 * search for files by mimetype
625
	 *
626
	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
627
	 *        where it will search for all mimetypes in the group ('image/*')
628
	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
629
	 */
630
	public function searchByMime($mimetype) {
631
		if (strpos($mimetype, '/')) {
632
			$where = '`mimetype` = ?';
633
		} else {
634
			$where = '`mimepart` = ?';
635
		}
636
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
637
				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
638
		$mimetype = $this->mimetypeLoader->getId($mimetype);
639
		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
640
641
		return $this->searchResultToCacheEntries($result);
642
	}
643
644
	public function searchQuery(ISearchQuery $searchQuery) {
645
		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
646
647
		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
648
			->from('filecache')
649
			->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())))
650
			->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
651
652
		if ($searchQuery->getLimit()) {
653
			$query->setMaxResults($searchQuery->getLimit());
654
		}
655
		if ($searchQuery->getOffset()) {
656
			$query->setFirstResult($searchQuery->getOffset());
657
		}
658
659
		$result = $query->execute();
660
		return $this->searchResultToCacheEntries($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $query->execute() on line 659 can also be of type integer; however, OC\Files\Cache\Cache::searchResultToCacheEntries() does only seem to accept object<Doctrine\DBAL\Driver\Statement>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
661
	}
662
663
		/**
664
	 * Search for files by tag of a given users.
665
	 *
666
	 * Note that every user can tag files differently.
667
	 *
668
	 * @param string|int $tag name or tag id
669
	 * @param string $userId owner of the tags
670
	 * @return ICacheEntry[] file data
671
	 */
672
	public function searchByTag($tag, $userId) {
673
		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
674
			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
675
			'`encrypted`, `etag`, `permissions`, `checksum` ' .
676
			'FROM `*PREFIX*filecache` `file`, ' .
677
			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
678
			'`*PREFIX*vcategory` `tag` ' .
679
			// JOIN filecache to vcategory_to_object
680
			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
681
			// JOIN vcategory_to_object to vcategory
682
			'AND `tagmap`.`type` = `tag`.`type` ' .
683
			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
684
			// conditions
685
			'AND `file`.`storage` = ? ' .
686
			'AND `tag`.`type` = \'files\' ' .
687
			'AND `tag`.`uid` = ? ';
688
		if (is_int($tag)) {
689
			$sql .= 'AND `tag`.`id` = ? ';
690
		} else {
691
			$sql .= 'AND `tag`.`category` = ? ';
692
		}
693
		$result = $this->connection->executeQuery(
694
			$sql,
695
			[
696
				$this->getNumericStorageId(),
697
				$userId,
698
				$tag
699
			]
700
		);
701
702
		$files = $result->fetchAll();
703
704
		return array_map(function (array $data) {
705
			return self::cacheEntryFromData($data, $this->mimetypeLoader);
706
		}, $files);
707
	}
708
709
	/**
710
	 * Re-calculate the folder size and the size of all parent folders
711
	 *
712
	 * @param string|boolean $path
713
	 * @param array $data (optional) meta data of the folder
714
	 */
715
	public function correctFolderSize($path, $data = null) {
716
		$this->calculateFolderSize($path, $data);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 715 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...
717
		if ($path !== '') {
718
			$parent = dirname($path);
719
			if ($parent === '.' or $parent === '/') {
720
				$parent = '';
721
			}
722
			$this->correctFolderSize($parent);
723
		}
724
	}
725
726
	/**
727
	 * calculate the size of a folder and set it in the cache
728
	 *
729
	 * @param string $path
730
	 * @param array $entry (optional) meta data of the folder
731
	 * @return int
732
	 */
733
	public function calculateFolderSize($path, $entry = null) {
734
		$totalSize = 0;
735
		if (is_null($entry) or !isset($entry['fileid'])) {
736
			$entry = $this->get($path);
737
		}
738
		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
739
			$id = $entry['fileid'];
740
			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
741
				'FROM `*PREFIX*filecache` ' .
742
				'WHERE `parent` = ? AND `storage` = ?';
743
			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
744
			if ($row = $result->fetch()) {
745
				$result->closeCursor();
746
				list($sum, $min) = array_values($row);
747
				$sum = 0 + $sum;
748
				$min = 0 + $min;
749
				if ($min === -1) {
750
					$totalSize = $min;
751
				} else {
752
					$totalSize = $sum;
753
				}
754
				$update = array();
755
				if ($entry['size'] !== $totalSize) {
756
					$update['size'] = $totalSize;
757
				}
758
				if (count($update) > 0) {
759
					$this->update($id, $update);
760
				}
761
			} else {
762
				$result->closeCursor();
763
			}
764
		}
765
		return $totalSize;
766
	}
767
768
	/**
769
	 * get all file ids on the files on the storage
770
	 *
771
	 * @return int[]
772
	 */
773
	public function getAll() {
774
		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
775
		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
776
		$ids = array();
777
		while ($row = $result->fetch()) {
778
			$ids[] = $row['fileid'];
779
		}
780
		return $ids;
781
	}
782
783
	/**
784
	 * find a folder in the cache which has not been fully scanned
785
	 *
786
	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
787
	 * use the one with the highest id gives the best result with the background scanner, since that is most
788
	 * likely the folder where we stopped scanning previously
789
	 *
790
	 * @return string|bool the path of the folder or false when no folder matched
791
	 */
792
	public function getIncomplete() {
793
		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
794
			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
795
		$query->execute([$this->getNumericStorageId()]);
796
		if ($row = $query->fetch()) {
797
			return $row['path'];
798
		} else {
799
			return false;
800
		}
801
	}
802
803
	/**
804
	 * get the path of a file on this storage by it's file id
805
	 *
806
	 * @param int $id the file id of the file or folder to search
807
	 * @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
808
	 */
809
	public function getPathById($id) {
810
		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
811
		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
812
		if ($row = $result->fetch()) {
813
			// Oracle stores empty strings as null...
814
			if ($row['path'] === null) {
815
				return '';
816
			}
817
			return $row['path'];
818
		} else {
819
			return null;
820
		}
821
	}
822
823
	/**
824
	 * get the storage id of the storage for a file and the internal path of the file
825
	 * unlike getPathById this does not limit the search to files on this storage and
826
	 * instead does a global search in the cache table
827
	 *
828
	 * @param int $id
829
	 * @deprecated use getPathById() instead
830
	 * @return array first element holding the storage id, second the path
831
	 */
832
	static public function getById($id) {
833
		$connection = \OC::$server->getDatabaseConnection();
834
		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
835
		$result = $connection->executeQuery($sql, array($id));
836
		if ($row = $result->fetch()) {
837
			$numericId = $row['storage'];
838
			$path = $row['path'];
839
		} else {
840
			return null;
841
		}
842
843
		if ($id = Storage::getStorageId($numericId)) {
844
			return array($id, $path);
845
		} else {
846
			return null;
847
		}
848
	}
849
850
	/**
851
	 * normalize the given path
852
	 *
853
	 * @param string $path
854
	 * @return string
855
	 */
856
	public function normalize($path) {
857
858
		return trim(\OC_Util::normalizeUnicode($path), '/');
859
	}
860
}
861