Passed
Push — master ( 70aa85...772303 )
by Morris
25:14 queued 14:09
created
lib/private/Files/Cache/Wrapper/CacheWrapper.php 1 patch
Indentation   +298 added lines, -298 removed lines patch added patch discarded remove patch
@@ -33,302 +33,302 @@
 block discarded – undo
33 33
 use OCP\Files\Search\ISearchQuery;
34 34
 
35 35
 class CacheWrapper extends Cache {
36
-	/**
37
-	 * @var \OCP\Files\Cache\ICache
38
-	 */
39
-	protected $cache;
40
-
41
-	/**
42
-	 * @param \OCP\Files\Cache\ICache $cache
43
-	 */
44
-	public function __construct($cache) {
45
-		$this->cache = $cache;
46
-	}
47
-
48
-	protected function getCache() {
49
-		return $this->cache;
50
-	}
51
-
52
-	/**
53
-	 * Make it easy for wrappers to modify every returned cache entry
54
-	 *
55
-	 * @param ICacheEntry $entry
56
-	 * @return ICacheEntry
57
-	 */
58
-	protected function formatCacheEntry($entry) {
59
-		return $entry;
60
-	}
61
-
62
-	/**
63
-	 * get the stored metadata of a file or folder
64
-	 *
65
-	 * @param string|int $file
66
-	 * @return ICacheEntry|false
67
-	 */
68
-	public function get($file) {
69
-		$result = $this->getCache()->get($file);
70
-		if ($result) {
71
-			$result = $this->formatCacheEntry($result);
72
-		}
73
-		return $result;
74
-	}
75
-
76
-	/**
77
-	 * get the metadata of all files stored in $folder
78
-	 *
79
-	 * @param string $folder
80
-	 * @return ICacheEntry[]
81
-	 */
82
-	public function getFolderContents($folder) {
83
-		// can't do a simple $this->getCache()->.... call here since getFolderContentsById needs to be called on this
84
-		// and not the wrapped cache
85
-		$fileId = $this->getId($folder);
86
-		return $this->getFolderContentsById($fileId);
87
-	}
88
-
89
-	/**
90
-	 * get the metadata of all files stored in $folder
91
-	 *
92
-	 * @param int $fileId the file id of the folder
93
-	 * @return array
94
-	 */
95
-	public function getFolderContentsById($fileId) {
96
-		$results = $this->getCache()->getFolderContentsById($fileId);
97
-		return array_map(array($this, 'formatCacheEntry'), $results);
98
-	}
99
-
100
-	/**
101
-	 * insert or update meta data for a file or folder
102
-	 *
103
-	 * @param string $file
104
-	 * @param array $data
105
-	 *
106
-	 * @return int file id
107
-	 * @throws \RuntimeException
108
-	 */
109
-	public function put($file, array $data) {
110
-		if (($id = $this->getId($file)) > -1) {
111
-			$this->update($id, $data);
112
-			return $id;
113
-		} else {
114
-			return $this->insert($file, $data);
115
-		}
116
-	}
117
-
118
-	/**
119
-	 * insert meta data for a new file or folder
120
-	 *
121
-	 * @param string $file
122
-	 * @param array $data
123
-	 *
124
-	 * @return int file id
125
-	 * @throws \RuntimeException
126
-	 */
127
-	public function insert($file, array $data) {
128
-		return $this->getCache()->insert($file, $data);
129
-	}
130
-
131
-	/**
132
-	 * update the metadata in the cache
133
-	 *
134
-	 * @param int $id
135
-	 * @param array $data
136
-	 */
137
-	public function update($id, array $data) {
138
-		$this->getCache()->update($id, $data);
139
-	}
140
-
141
-	/**
142
-	 * get the file id for a file
143
-	 *
144
-	 * @param string $file
145
-	 * @return int
146
-	 */
147
-	public function getId($file) {
148
-		return $this->getCache()->getId($file);
149
-	}
150
-
151
-	/**
152
-	 * get the id of the parent folder of a file
153
-	 *
154
-	 * @param string $file
155
-	 * @return int
156
-	 */
157
-	public function getParentId($file) {
158
-		return $this->getCache()->getParentId($file);
159
-	}
160
-
161
-	/**
162
-	 * check if a file is available in the cache
163
-	 *
164
-	 * @param string $file
165
-	 * @return bool
166
-	 */
167
-	public function inCache($file) {
168
-		return $this->getCache()->inCache($file);
169
-	}
170
-
171
-	/**
172
-	 * remove a file or folder from the cache
173
-	 *
174
-	 * @param string $file
175
-	 */
176
-	public function remove($file) {
177
-		$this->getCache()->remove($file);
178
-	}
179
-
180
-	/**
181
-	 * Move a file or folder in the cache
182
-	 *
183
-	 * @param string $source
184
-	 * @param string $target
185
-	 */
186
-	public function move($source, $target) {
187
-		$this->getCache()->move($source, $target);
188
-	}
189
-
190
-	protected function getMoveInfo($path) {
191
-		/** @var Cache $cache */
192
-		$cache = $this->getCache();
193
-		return $cache->getMoveInfo($path);
194
-	}
195
-
196
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
197
-		$this->getCache()->moveFromCache($sourceCache, $sourcePath, $targetPath);
198
-	}
199
-
200
-	/**
201
-	 * remove all entries for files that are stored on the storage from the cache
202
-	 */
203
-	public function clear() {
204
-		$this->getCache()->clear();
205
-	}
206
-
207
-	/**
208
-	 * @param string $file
209
-	 *
210
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
211
-	 */
212
-	public function getStatus($file) {
213
-		return $this->getCache()->getStatus($file);
214
-	}
215
-
216
-	/**
217
-	 * search for files matching $pattern
218
-	 *
219
-	 * @param string $pattern
220
-	 * @return ICacheEntry[] an array of file data
221
-	 */
222
-	public function search($pattern) {
223
-		$results = $this->getCache()->search($pattern);
224
-		return array_map(array($this, 'formatCacheEntry'), $results);
225
-	}
226
-
227
-	/**
228
-	 * search for files by mimetype
229
-	 *
230
-	 * @param string $mimetype
231
-	 * @return ICacheEntry[]
232
-	 */
233
-	public function searchByMime($mimetype) {
234
-		$results = $this->getCache()->searchByMime($mimetype);
235
-		return array_map(array($this, 'formatCacheEntry'), $results);
236
-	}
237
-
238
-	public function searchQuery(ISearchQuery $query) {
239
-		$results = $this->getCache()->searchQuery($query);
240
-		return array_map(array($this, 'formatCacheEntry'), $results);
241
-	}
242
-
243
-	/**
244
-	 * search for files by tag
245
-	 *
246
-	 * @param string|int $tag name or tag id
247
-	 * @param string $userId owner of the tags
248
-	 * @return ICacheEntry[] file data
249
-	 */
250
-	public function searchByTag($tag, $userId) {
251
-		$results = $this->getCache()->searchByTag($tag, $userId);
252
-		return array_map(array($this, 'formatCacheEntry'), $results);
253
-	}
254
-
255
-	/**
256
-	 * update the folder size and the size of all parent folders
257
-	 *
258
-	 * @param string|boolean $path
259
-	 * @param array $data (optional) meta data of the folder
260
-	 */
261
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
262
-		if ($this->getCache() instanceof Cache) {
263
-			$this->getCache()->correctFolderSize($path, $data, $isBackgroundScan);
264
-		}
265
-	}
266
-
267
-	/**
268
-	 * get the size of a folder and set it in the cache
269
-	 *
270
-	 * @param string $path
271
-	 * @param array $entry (optional) meta data of the folder
272
-	 * @return int
273
-	 */
274
-	public function calculateFolderSize($path, $entry = null) {
275
-		if ($this->getCache() instanceof Cache) {
276
-			return $this->getCache()->calculateFolderSize($path, $entry);
277
-		} else {
278
-			return 0;
279
-		}
280
-	}
281
-
282
-	/**
283
-	 * get all file ids on the files on the storage
284
-	 *
285
-	 * @return int[]
286
-	 */
287
-	public function getAll() {
288
-		return $this->getCache()->getAll();
289
-	}
290
-
291
-	/**
292
-	 * find a folder in the cache which has not been fully scanned
293
-	 *
294
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
295
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
296
-	 * likely the folder where we stopped scanning previously
297
-	 *
298
-	 * @return string|bool the path of the folder or false when no folder matched
299
-	 */
300
-	public function getIncomplete() {
301
-		return $this->getCache()->getIncomplete();
302
-	}
303
-
304
-	/**
305
-	 * get the path of a file on this storage by it's id
306
-	 *
307
-	 * @param int $id
308
-	 * @return string|null
309
-	 */
310
-	public function getPathById($id) {
311
-		return $this->getCache()->getPathById($id);
312
-	}
313
-
314
-	/**
315
-	 * Returns the numeric storage id
316
-	 *
317
-	 * @return int
318
-	 */
319
-	public function getNumericStorageId() {
320
-		return $this->getCache()->getNumericStorageId();
321
-	}
322
-
323
-	/**
324
-	 * get the storage id of the storage for a file and the internal path of the file
325
-	 * unlike getPathById this does not limit the search to files on this storage and
326
-	 * instead does a global search in the cache table
327
-	 *
328
-	 * @param int $id
329
-	 * @return array first element holding the storage id, second the path
330
-	 */
331
-	static public function getById($id) {
332
-		return parent::getById($id);
333
-	}
36
+    /**
37
+     * @var \OCP\Files\Cache\ICache
38
+     */
39
+    protected $cache;
40
+
41
+    /**
42
+     * @param \OCP\Files\Cache\ICache $cache
43
+     */
44
+    public function __construct($cache) {
45
+        $this->cache = $cache;
46
+    }
47
+
48
+    protected function getCache() {
49
+        return $this->cache;
50
+    }
51
+
52
+    /**
53
+     * Make it easy for wrappers to modify every returned cache entry
54
+     *
55
+     * @param ICacheEntry $entry
56
+     * @return ICacheEntry
57
+     */
58
+    protected function formatCacheEntry($entry) {
59
+        return $entry;
60
+    }
61
+
62
+    /**
63
+     * get the stored metadata of a file or folder
64
+     *
65
+     * @param string|int $file
66
+     * @return ICacheEntry|false
67
+     */
68
+    public function get($file) {
69
+        $result = $this->getCache()->get($file);
70
+        if ($result) {
71
+            $result = $this->formatCacheEntry($result);
72
+        }
73
+        return $result;
74
+    }
75
+
76
+    /**
77
+     * get the metadata of all files stored in $folder
78
+     *
79
+     * @param string $folder
80
+     * @return ICacheEntry[]
81
+     */
82
+    public function getFolderContents($folder) {
83
+        // can't do a simple $this->getCache()->.... call here since getFolderContentsById needs to be called on this
84
+        // and not the wrapped cache
85
+        $fileId = $this->getId($folder);
86
+        return $this->getFolderContentsById($fileId);
87
+    }
88
+
89
+    /**
90
+     * get the metadata of all files stored in $folder
91
+     *
92
+     * @param int $fileId the file id of the folder
93
+     * @return array
94
+     */
95
+    public function getFolderContentsById($fileId) {
96
+        $results = $this->getCache()->getFolderContentsById($fileId);
97
+        return array_map(array($this, 'formatCacheEntry'), $results);
98
+    }
99
+
100
+    /**
101
+     * insert or update meta data for a file or folder
102
+     *
103
+     * @param string $file
104
+     * @param array $data
105
+     *
106
+     * @return int file id
107
+     * @throws \RuntimeException
108
+     */
109
+    public function put($file, array $data) {
110
+        if (($id = $this->getId($file)) > -1) {
111
+            $this->update($id, $data);
112
+            return $id;
113
+        } else {
114
+            return $this->insert($file, $data);
115
+        }
116
+    }
117
+
118
+    /**
119
+     * insert meta data for a new file or folder
120
+     *
121
+     * @param string $file
122
+     * @param array $data
123
+     *
124
+     * @return int file id
125
+     * @throws \RuntimeException
126
+     */
127
+    public function insert($file, array $data) {
128
+        return $this->getCache()->insert($file, $data);
129
+    }
130
+
131
+    /**
132
+     * update the metadata in the cache
133
+     *
134
+     * @param int $id
135
+     * @param array $data
136
+     */
137
+    public function update($id, array $data) {
138
+        $this->getCache()->update($id, $data);
139
+    }
140
+
141
+    /**
142
+     * get the file id for a file
143
+     *
144
+     * @param string $file
145
+     * @return int
146
+     */
147
+    public function getId($file) {
148
+        return $this->getCache()->getId($file);
149
+    }
150
+
151
+    /**
152
+     * get the id of the parent folder of a file
153
+     *
154
+     * @param string $file
155
+     * @return int
156
+     */
157
+    public function getParentId($file) {
158
+        return $this->getCache()->getParentId($file);
159
+    }
160
+
161
+    /**
162
+     * check if a file is available in the cache
163
+     *
164
+     * @param string $file
165
+     * @return bool
166
+     */
167
+    public function inCache($file) {
168
+        return $this->getCache()->inCache($file);
169
+    }
170
+
171
+    /**
172
+     * remove a file or folder from the cache
173
+     *
174
+     * @param string $file
175
+     */
176
+    public function remove($file) {
177
+        $this->getCache()->remove($file);
178
+    }
179
+
180
+    /**
181
+     * Move a file or folder in the cache
182
+     *
183
+     * @param string $source
184
+     * @param string $target
185
+     */
186
+    public function move($source, $target) {
187
+        $this->getCache()->move($source, $target);
188
+    }
189
+
190
+    protected function getMoveInfo($path) {
191
+        /** @var Cache $cache */
192
+        $cache = $this->getCache();
193
+        return $cache->getMoveInfo($path);
194
+    }
195
+
196
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
197
+        $this->getCache()->moveFromCache($sourceCache, $sourcePath, $targetPath);
198
+    }
199
+
200
+    /**
201
+     * remove all entries for files that are stored on the storage from the cache
202
+     */
203
+    public function clear() {
204
+        $this->getCache()->clear();
205
+    }
206
+
207
+    /**
208
+     * @param string $file
209
+     *
210
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
211
+     */
212
+    public function getStatus($file) {
213
+        return $this->getCache()->getStatus($file);
214
+    }
215
+
216
+    /**
217
+     * search for files matching $pattern
218
+     *
219
+     * @param string $pattern
220
+     * @return ICacheEntry[] an array of file data
221
+     */
222
+    public function search($pattern) {
223
+        $results = $this->getCache()->search($pattern);
224
+        return array_map(array($this, 'formatCacheEntry'), $results);
225
+    }
226
+
227
+    /**
228
+     * search for files by mimetype
229
+     *
230
+     * @param string $mimetype
231
+     * @return ICacheEntry[]
232
+     */
233
+    public function searchByMime($mimetype) {
234
+        $results = $this->getCache()->searchByMime($mimetype);
235
+        return array_map(array($this, 'formatCacheEntry'), $results);
236
+    }
237
+
238
+    public function searchQuery(ISearchQuery $query) {
239
+        $results = $this->getCache()->searchQuery($query);
240
+        return array_map(array($this, 'formatCacheEntry'), $results);
241
+    }
242
+
243
+    /**
244
+     * search for files by tag
245
+     *
246
+     * @param string|int $tag name or tag id
247
+     * @param string $userId owner of the tags
248
+     * @return ICacheEntry[] file data
249
+     */
250
+    public function searchByTag($tag, $userId) {
251
+        $results = $this->getCache()->searchByTag($tag, $userId);
252
+        return array_map(array($this, 'formatCacheEntry'), $results);
253
+    }
254
+
255
+    /**
256
+     * update the folder size and the size of all parent folders
257
+     *
258
+     * @param string|boolean $path
259
+     * @param array $data (optional) meta data of the folder
260
+     */
261
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
262
+        if ($this->getCache() instanceof Cache) {
263
+            $this->getCache()->correctFolderSize($path, $data, $isBackgroundScan);
264
+        }
265
+    }
266
+
267
+    /**
268
+     * get the size of a folder and set it in the cache
269
+     *
270
+     * @param string $path
271
+     * @param array $entry (optional) meta data of the folder
272
+     * @return int
273
+     */
274
+    public function calculateFolderSize($path, $entry = null) {
275
+        if ($this->getCache() instanceof Cache) {
276
+            return $this->getCache()->calculateFolderSize($path, $entry);
277
+        } else {
278
+            return 0;
279
+        }
280
+    }
281
+
282
+    /**
283
+     * get all file ids on the files on the storage
284
+     *
285
+     * @return int[]
286
+     */
287
+    public function getAll() {
288
+        return $this->getCache()->getAll();
289
+    }
290
+
291
+    /**
292
+     * find a folder in the cache which has not been fully scanned
293
+     *
294
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
295
+     * use the one with the highest id gives the best result with the background scanner, since that is most
296
+     * likely the folder where we stopped scanning previously
297
+     *
298
+     * @return string|bool the path of the folder or false when no folder matched
299
+     */
300
+    public function getIncomplete() {
301
+        return $this->getCache()->getIncomplete();
302
+    }
303
+
304
+    /**
305
+     * get the path of a file on this storage by it's id
306
+     *
307
+     * @param int $id
308
+     * @return string|null
309
+     */
310
+    public function getPathById($id) {
311
+        return $this->getCache()->getPathById($id);
312
+    }
313
+
314
+    /**
315
+     * Returns the numeric storage id
316
+     *
317
+     * @return int
318
+     */
319
+    public function getNumericStorageId() {
320
+        return $this->getCache()->getNumericStorageId();
321
+    }
322
+
323
+    /**
324
+     * get the storage id of the storage for a file and the internal path of the file
325
+     * unlike getPathById this does not limit the search to files on this storage and
326
+     * instead does a global search in the cache table
327
+     *
328
+     * @param int $id
329
+     * @return array first element holding the storage id, second the path
330
+     */
331
+    static public function getById($id) {
332
+        return parent::getById($id);
333
+    }
334 334
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Wrapper/CacheJail.php 1 patch
Indentation   +304 added lines, -304 removed lines patch added patch discarded remove patch
@@ -37,308 +37,308 @@
 block discarded – undo
37 37
  * Jail to a subdirectory of the wrapped cache
38 38
  */
39 39
 class CacheJail extends CacheWrapper {
40
-	/**
41
-	 * @var string
42
-	 */
43
-	protected $root;
44
-
45
-	/**
46
-	 * @param \OCP\Files\Cache\ICache $cache
47
-	 * @param string $root
48
-	 */
49
-	public function __construct($cache, $root) {
50
-		parent::__construct($cache);
51
-		$this->root = $root;
52
-	}
53
-
54
-	protected function getRoot() {
55
-		return $this->root;
56
-	}
57
-
58
-	protected function getSourcePath($path) {
59
-		if ($path === '') {
60
-			return $this->getRoot();
61
-		} else {
62
-			return $this->getRoot() . '/' . ltrim($path, '/');
63
-		}
64
-	}
65
-
66
-	/**
67
-	 * @param string $path
68
-	 * @return null|string the jailed path or null if the path is outside the jail
69
-	 */
70
-	protected function getJailedPath($path) {
71
-		if ($this->getRoot() === '') {
72
-			return $path;
73
-		}
74
-		$rootLength = strlen($this->getRoot()) + 1;
75
-		if ($path === $this->getRoot()) {
76
-			return '';
77
-		} else if (substr($path, 0, $rootLength) === $this->getRoot() . '/') {
78
-			return substr($path, $rootLength);
79
-		} else {
80
-			return null;
81
-		}
82
-	}
83
-
84
-	/**
85
-	 * @param ICacheEntry|array $entry
86
-	 * @return array
87
-	 */
88
-	protected function formatCacheEntry($entry) {
89
-		if (isset($entry['path'])) {
90
-			$entry['path'] = $this->getJailedPath($entry['path']);
91
-		}
92
-		return $entry;
93
-	}
94
-
95
-	protected function filterCacheEntry($entry) {
96
-		$rootLength = strlen($this->getRoot()) + 1;
97
-		return $rootLength === 1 || ($entry['path'] === $this->getRoot()) || (substr($entry['path'], 0, $rootLength) === $this->getRoot() . '/');
98
-	}
99
-
100
-	/**
101
-	 * get the stored metadata of a file or folder
102
-	 *
103
-	 * @param string /int $file
104
-	 * @return ICacheEntry|false
105
-	 */
106
-	public function get($file) {
107
-		if (is_string($file) or $file == '') {
108
-			$file = $this->getSourcePath($file);
109
-		}
110
-		return parent::get($file);
111
-	}
112
-
113
-	/**
114
-	 * insert meta data for a new file or folder
115
-	 *
116
-	 * @param string $file
117
-	 * @param array $data
118
-	 *
119
-	 * @return int file id
120
-	 * @throws \RuntimeException
121
-	 */
122
-	public function insert($file, array $data) {
123
-		return $this->getCache()->insert($this->getSourcePath($file), $data);
124
-	}
125
-
126
-	/**
127
-	 * update the metadata in the cache
128
-	 *
129
-	 * @param int $id
130
-	 * @param array $data
131
-	 */
132
-	public function update($id, array $data) {
133
-		$this->getCache()->update($id, $data);
134
-	}
135
-
136
-	/**
137
-	 * get the file id for a file
138
-	 *
139
-	 * @param string $file
140
-	 * @return int
141
-	 */
142
-	public function getId($file) {
143
-		return $this->getCache()->getId($this->getSourcePath($file));
144
-	}
145
-
146
-	/**
147
-	 * get the id of the parent folder of a file
148
-	 *
149
-	 * @param string $file
150
-	 * @return int
151
-	 */
152
-	public function getParentId($file) {
153
-		return $this->getCache()->getParentId($this->getSourcePath($file));
154
-	}
155
-
156
-	/**
157
-	 * check if a file is available in the cache
158
-	 *
159
-	 * @param string $file
160
-	 * @return bool
161
-	 */
162
-	public function inCache($file) {
163
-		return $this->getCache()->inCache($this->getSourcePath($file));
164
-	}
165
-
166
-	/**
167
-	 * remove a file or folder from the cache
168
-	 *
169
-	 * @param string $file
170
-	 */
171
-	public function remove($file) {
172
-		$this->getCache()->remove($this->getSourcePath($file));
173
-	}
174
-
175
-	/**
176
-	 * Move a file or folder in the cache
177
-	 *
178
-	 * @param string $source
179
-	 * @param string $target
180
-	 */
181
-	public function move($source, $target) {
182
-		$this->getCache()->move($this->getSourcePath($source), $this->getSourcePath($target));
183
-	}
184
-
185
-	/**
186
-	 * Get the storage id and path needed for a move
187
-	 *
188
-	 * @param string $path
189
-	 * @return array [$storageId, $internalPath]
190
-	 */
191
-	protected function getMoveInfo($path) {
192
-		return [$this->getNumericStorageId(), $this->getSourcePath($path)];
193
-	}
194
-
195
-	/**
196
-	 * remove all entries for files that are stored on the storage from the cache
197
-	 */
198
-	public function clear() {
199
-		$this->getCache()->remove($this->getRoot());
200
-	}
201
-
202
-	/**
203
-	 * @param string $file
204
-	 *
205
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
206
-	 */
207
-	public function getStatus($file) {
208
-		return $this->getCache()->getStatus($this->getSourcePath($file));
209
-	}
210
-
211
-	private function formatSearchResults($results) {
212
-		$results = array_filter($results, array($this, 'filterCacheEntry'));
213
-		$results = array_values($results);
214
-		return array_map(array($this, 'formatCacheEntry'), $results);
215
-	}
216
-
217
-	/**
218
-	 * search for files matching $pattern
219
-	 *
220
-	 * @param string $pattern
221
-	 * @return array an array of file data
222
-	 */
223
-	public function search($pattern) {
224
-		$results = $this->getCache()->search($pattern);
225
-		return $this->formatSearchResults($results);
226
-	}
227
-
228
-	/**
229
-	 * search for files by mimetype
230
-	 *
231
-	 * @param string $mimetype
232
-	 * @return array
233
-	 */
234
-	public function searchByMime($mimetype) {
235
-		$results = $this->getCache()->searchByMime($mimetype);
236
-		return $this->formatSearchResults($results);
237
-	}
238
-
239
-	public function searchQuery(ISearchQuery $query) {
240
-		$simpleQuery = new SearchQuery($query->getSearchOperation(), 0, 0, $query->getOrder(), $query->getUser());
241
-		$results = $this->getCache()->searchQuery($simpleQuery);
242
-		$results = $this->formatSearchResults($results);
243
-
244
-		$limit = $query->getLimit() === 0 ? NULL : $query->getLimit();
245
-		$results = array_slice($results, $query->getOffset(), $limit);
246
-
247
-		return $results;
248
-	}
249
-
250
-	/**
251
-	 * search for files by mimetype
252
-	 *
253
-	 * @param string|int $tag name or tag id
254
-	 * @param string $userId owner of the tags
255
-	 * @return array
256
-	 */
257
-	public function searchByTag($tag, $userId) {
258
-		$results = $this->getCache()->searchByTag($tag, $userId);
259
-		return $this->formatSearchResults($results);
260
-	}
261
-
262
-	/**
263
-	 * update the folder size and the size of all parent folders
264
-	 *
265
-	 * @param string|boolean $path
266
-	 * @param array $data (optional) meta data of the folder
267
-	 */
268
-	public function correctFolderSize($path, $data = null, $isBackgroundSize = false) {
269
-		if ($this->getCache() instanceof Cache) {
270
-			$this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundSize);
271
-		}
272
-	}
273
-
274
-	/**
275
-	 * get the size of a folder and set it in the cache
276
-	 *
277
-	 * @param string $path
278
-	 * @param array $entry (optional) meta data of the folder
279
-	 * @return int
280
-	 */
281
-	public function calculateFolderSize($path, $entry = null) {
282
-		if ($this->getCache() instanceof Cache) {
283
-			return $this->getCache()->calculateFolderSize($this->getSourcePath($path), $entry);
284
-		} else {
285
-			return 0;
286
-		}
287
-
288
-	}
289
-
290
-	/**
291
-	 * get all file ids on the files on the storage
292
-	 *
293
-	 * @return int[]
294
-	 */
295
-	public function getAll() {
296
-		// not supported
297
-		return array();
298
-	}
299
-
300
-	/**
301
-	 * find a folder in the cache which has not been fully scanned
302
-	 *
303
-	 * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
304
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
305
-	 * likely the folder where we stopped scanning previously
306
-	 *
307
-	 * @return string|bool the path of the folder or false when no folder matched
308
-	 */
309
-	public function getIncomplete() {
310
-		// not supported
311
-		return false;
312
-	}
313
-
314
-	/**
315
-	 * get the path of a file on this storage by it's id
316
-	 *
317
-	 * @param int $id
318
-	 * @return string|null
319
-	 */
320
-	public function getPathById($id) {
321
-		$path = $this->getCache()->getPathById($id);
322
-		if ($path === null) {
323
-			return null;
324
-		}
325
-
326
-		return $this->getJailedPath($path);
327
-	}
328
-
329
-	/**
330
-	 * Move a file or folder in the cache
331
-	 *
332
-	 * Note that this should make sure the entries are removed from the source cache
333
-	 *
334
-	 * @param \OCP\Files\Cache\ICache $sourceCache
335
-	 * @param string $sourcePath
336
-	 * @param string $targetPath
337
-	 */
338
-	public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) {
339
-		if ($sourceCache === $this) {
340
-			return $this->move($sourcePath, $targetPath);
341
-		}
342
-		return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath));
343
-	}
40
+    /**
41
+     * @var string
42
+     */
43
+    protected $root;
44
+
45
+    /**
46
+     * @param \OCP\Files\Cache\ICache $cache
47
+     * @param string $root
48
+     */
49
+    public function __construct($cache, $root) {
50
+        parent::__construct($cache);
51
+        $this->root = $root;
52
+    }
53
+
54
+    protected function getRoot() {
55
+        return $this->root;
56
+    }
57
+
58
+    protected function getSourcePath($path) {
59
+        if ($path === '') {
60
+            return $this->getRoot();
61
+        } else {
62
+            return $this->getRoot() . '/' . ltrim($path, '/');
63
+        }
64
+    }
65
+
66
+    /**
67
+     * @param string $path
68
+     * @return null|string the jailed path or null if the path is outside the jail
69
+     */
70
+    protected function getJailedPath($path) {
71
+        if ($this->getRoot() === '') {
72
+            return $path;
73
+        }
74
+        $rootLength = strlen($this->getRoot()) + 1;
75
+        if ($path === $this->getRoot()) {
76
+            return '';
77
+        } else if (substr($path, 0, $rootLength) === $this->getRoot() . '/') {
78
+            return substr($path, $rootLength);
79
+        } else {
80
+            return null;
81
+        }
82
+    }
83
+
84
+    /**
85
+     * @param ICacheEntry|array $entry
86
+     * @return array
87
+     */
88
+    protected function formatCacheEntry($entry) {
89
+        if (isset($entry['path'])) {
90
+            $entry['path'] = $this->getJailedPath($entry['path']);
91
+        }
92
+        return $entry;
93
+    }
94
+
95
+    protected function filterCacheEntry($entry) {
96
+        $rootLength = strlen($this->getRoot()) + 1;
97
+        return $rootLength === 1 || ($entry['path'] === $this->getRoot()) || (substr($entry['path'], 0, $rootLength) === $this->getRoot() . '/');
98
+    }
99
+
100
+    /**
101
+     * get the stored metadata of a file or folder
102
+     *
103
+     * @param string /int $file
104
+     * @return ICacheEntry|false
105
+     */
106
+    public function get($file) {
107
+        if (is_string($file) or $file == '') {
108
+            $file = $this->getSourcePath($file);
109
+        }
110
+        return parent::get($file);
111
+    }
112
+
113
+    /**
114
+     * insert meta data for a new file or folder
115
+     *
116
+     * @param string $file
117
+     * @param array $data
118
+     *
119
+     * @return int file id
120
+     * @throws \RuntimeException
121
+     */
122
+    public function insert($file, array $data) {
123
+        return $this->getCache()->insert($this->getSourcePath($file), $data);
124
+    }
125
+
126
+    /**
127
+     * update the metadata in the cache
128
+     *
129
+     * @param int $id
130
+     * @param array $data
131
+     */
132
+    public function update($id, array $data) {
133
+        $this->getCache()->update($id, $data);
134
+    }
135
+
136
+    /**
137
+     * get the file id for a file
138
+     *
139
+     * @param string $file
140
+     * @return int
141
+     */
142
+    public function getId($file) {
143
+        return $this->getCache()->getId($this->getSourcePath($file));
144
+    }
145
+
146
+    /**
147
+     * get the id of the parent folder of a file
148
+     *
149
+     * @param string $file
150
+     * @return int
151
+     */
152
+    public function getParentId($file) {
153
+        return $this->getCache()->getParentId($this->getSourcePath($file));
154
+    }
155
+
156
+    /**
157
+     * check if a file is available in the cache
158
+     *
159
+     * @param string $file
160
+     * @return bool
161
+     */
162
+    public function inCache($file) {
163
+        return $this->getCache()->inCache($this->getSourcePath($file));
164
+    }
165
+
166
+    /**
167
+     * remove a file or folder from the cache
168
+     *
169
+     * @param string $file
170
+     */
171
+    public function remove($file) {
172
+        $this->getCache()->remove($this->getSourcePath($file));
173
+    }
174
+
175
+    /**
176
+     * Move a file or folder in the cache
177
+     *
178
+     * @param string $source
179
+     * @param string $target
180
+     */
181
+    public function move($source, $target) {
182
+        $this->getCache()->move($this->getSourcePath($source), $this->getSourcePath($target));
183
+    }
184
+
185
+    /**
186
+     * Get the storage id and path needed for a move
187
+     *
188
+     * @param string $path
189
+     * @return array [$storageId, $internalPath]
190
+     */
191
+    protected function getMoveInfo($path) {
192
+        return [$this->getNumericStorageId(), $this->getSourcePath($path)];
193
+    }
194
+
195
+    /**
196
+     * remove all entries for files that are stored on the storage from the cache
197
+     */
198
+    public function clear() {
199
+        $this->getCache()->remove($this->getRoot());
200
+    }
201
+
202
+    /**
203
+     * @param string $file
204
+     *
205
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
206
+     */
207
+    public function getStatus($file) {
208
+        return $this->getCache()->getStatus($this->getSourcePath($file));
209
+    }
210
+
211
+    private function formatSearchResults($results) {
212
+        $results = array_filter($results, array($this, 'filterCacheEntry'));
213
+        $results = array_values($results);
214
+        return array_map(array($this, 'formatCacheEntry'), $results);
215
+    }
216
+
217
+    /**
218
+     * search for files matching $pattern
219
+     *
220
+     * @param string $pattern
221
+     * @return array an array of file data
222
+     */
223
+    public function search($pattern) {
224
+        $results = $this->getCache()->search($pattern);
225
+        return $this->formatSearchResults($results);
226
+    }
227
+
228
+    /**
229
+     * search for files by mimetype
230
+     *
231
+     * @param string $mimetype
232
+     * @return array
233
+     */
234
+    public function searchByMime($mimetype) {
235
+        $results = $this->getCache()->searchByMime($mimetype);
236
+        return $this->formatSearchResults($results);
237
+    }
238
+
239
+    public function searchQuery(ISearchQuery $query) {
240
+        $simpleQuery = new SearchQuery($query->getSearchOperation(), 0, 0, $query->getOrder(), $query->getUser());
241
+        $results = $this->getCache()->searchQuery($simpleQuery);
242
+        $results = $this->formatSearchResults($results);
243
+
244
+        $limit = $query->getLimit() === 0 ? NULL : $query->getLimit();
245
+        $results = array_slice($results, $query->getOffset(), $limit);
246
+
247
+        return $results;
248
+    }
249
+
250
+    /**
251
+     * search for files by mimetype
252
+     *
253
+     * @param string|int $tag name or tag id
254
+     * @param string $userId owner of the tags
255
+     * @return array
256
+     */
257
+    public function searchByTag($tag, $userId) {
258
+        $results = $this->getCache()->searchByTag($tag, $userId);
259
+        return $this->formatSearchResults($results);
260
+    }
261
+
262
+    /**
263
+     * update the folder size and the size of all parent folders
264
+     *
265
+     * @param string|boolean $path
266
+     * @param array $data (optional) meta data of the folder
267
+     */
268
+    public function correctFolderSize($path, $data = null, $isBackgroundSize = false) {
269
+        if ($this->getCache() instanceof Cache) {
270
+            $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundSize);
271
+        }
272
+    }
273
+
274
+    /**
275
+     * get the size of a folder and set it in the cache
276
+     *
277
+     * @param string $path
278
+     * @param array $entry (optional) meta data of the folder
279
+     * @return int
280
+     */
281
+    public function calculateFolderSize($path, $entry = null) {
282
+        if ($this->getCache() instanceof Cache) {
283
+            return $this->getCache()->calculateFolderSize($this->getSourcePath($path), $entry);
284
+        } else {
285
+            return 0;
286
+        }
287
+
288
+    }
289
+
290
+    /**
291
+     * get all file ids on the files on the storage
292
+     *
293
+     * @return int[]
294
+     */
295
+    public function getAll() {
296
+        // not supported
297
+        return array();
298
+    }
299
+
300
+    /**
301
+     * find a folder in the cache which has not been fully scanned
302
+     *
303
+     * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
304
+     * use the one with the highest id gives the best result with the background scanner, since that is most
305
+     * likely the folder where we stopped scanning previously
306
+     *
307
+     * @return string|bool the path of the folder or false when no folder matched
308
+     */
309
+    public function getIncomplete() {
310
+        // not supported
311
+        return false;
312
+    }
313
+
314
+    /**
315
+     * get the path of a file on this storage by it's id
316
+     *
317
+     * @param int $id
318
+     * @return string|null
319
+     */
320
+    public function getPathById($id) {
321
+        $path = $this->getCache()->getPathById($id);
322
+        if ($path === null) {
323
+            return null;
324
+        }
325
+
326
+        return $this->getJailedPath($path);
327
+    }
328
+
329
+    /**
330
+     * Move a file or folder in the cache
331
+     *
332
+     * Note that this should make sure the entries are removed from the source cache
333
+     *
334
+     * @param \OCP\Files\Cache\ICache $sourceCache
335
+     * @param string $sourcePath
336
+     * @param string $targetPath
337
+     */
338
+    public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) {
339
+        if ($sourceCache === $this) {
340
+            return $this->move($sourcePath, $targetPath);
341
+        }
342
+        return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath));
343
+    }
344 344
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Scanner.php 1 patch
Indentation   +500 added lines, -500 removed lines patch added patch discarded remove patch
@@ -53,504 +53,504 @@
 block discarded – undo
53 53
  * @package OC\Files\Cache
54 54
  */
55 55
 class Scanner extends BasicEmitter implements IScanner {
56
-	/**
57
-	 * @var \OC\Files\Storage\Storage $storage
58
-	 */
59
-	protected $storage;
60
-
61
-	/**
62
-	 * @var string $storageId
63
-	 */
64
-	protected $storageId;
65
-
66
-	/**
67
-	 * @var \OC\Files\Cache\Cache $cache
68
-	 */
69
-	protected $cache;
70
-
71
-	/**
72
-	 * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
73
-	 */
74
-	protected $cacheActive;
75
-
76
-	/**
77
-	 * @var bool $useTransactions whether to use transactions
78
-	 */
79
-	protected $useTransactions = true;
80
-
81
-	/**
82
-	 * @var \OCP\Lock\ILockingProvider
83
-	 */
84
-	protected $lockingProvider;
85
-
86
-	public function __construct(\OC\Files\Storage\Storage $storage) {
87
-		$this->storage = $storage;
88
-		$this->storageId = $this->storage->getId();
89
-		$this->cache = $storage->getCache();
90
-		$this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
91
-		$this->lockingProvider = \OC::$server->getLockingProvider();
92
-	}
93
-
94
-	/**
95
-	 * Whether to wrap the scanning of a folder in a database transaction
96
-	 * On default transactions are used
97
-	 *
98
-	 * @param bool $useTransactions
99
-	 */
100
-	public function setUseTransactions($useTransactions) {
101
-		$this->useTransactions = $useTransactions;
102
-	}
103
-
104
-	/**
105
-	 * get all the metadata of a file or folder
106
-	 * *
107
-	 *
108
-	 * @param string $path
109
-	 * @return array an array of metadata of the file
110
-	 */
111
-	protected function getData($path) {
112
-		$data = $this->storage->getMetaData($path);
113
-		if (is_null($data)) {
114
-			\OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
115
-		}
116
-		return $data;
117
-	}
118
-
119
-	/**
120
-	 * scan a single file and store it in the cache
121
-	 *
122
-	 * @param string $file
123
-	 * @param int $reuseExisting
124
-	 * @param int $parentId
125
-	 * @param array | null $cacheData existing data in the cache for the file to be scanned
126
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
127
-	 * @return array an array of metadata of the scanned file
128
-	 * @throws \OC\ServerNotAvailableException
129
-	 * @throws \OCP\Lock\LockedException
130
-	 */
131
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) {
132
-		if ($file !== '') {
133
-			try {
134
-				$this->storage->verifyPath(dirname($file), basename($file));
135
-			} catch (\Exception $e) {
136
-				return null;
137
-			}
138
-		}
139
-		// only proceed if $file is not a partial file nor a blacklisted file
140
-		if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) {
141
-
142
-			//acquire a lock
143
-			if ($lock) {
144
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
145
-					$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
146
-				}
147
-			}
148
-
149
-			try {
150
-				$data = $this->getData($file);
151
-			} catch (ForbiddenException $e) {
152
-				if ($lock) {
153
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
154
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
155
-					}
156
-				}
157
-
158
-				return null;
159
-			}
160
-
161
-			try {
162
-				if ($data) {
163
-
164
-					// pre-emit only if it was a file. By that we avoid counting/treating folders as files
165
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
166
-						$this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId));
167
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId));
168
-					}
169
-
170
-					$parent = dirname($file);
171
-					if ($parent === '.' or $parent === '/') {
172
-						$parent = '';
173
-					}
174
-					if ($parentId === -1) {
175
-						$parentId = $this->cache->getParentId($file);
176
-					}
177
-
178
-					// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
179
-					if ($file and $parentId === -1) {
180
-						$parentData = $this->scanFile($parent);
181
-						if (!$parentData) {
182
-							return null;
183
-						}
184
-						$parentId = $parentData['fileid'];
185
-					}
186
-					if ($parent) {
187
-						$data['parent'] = $parentId;
188
-					}
189
-					if (is_null($cacheData)) {
190
-						/** @var CacheEntry $cacheData */
191
-						$cacheData = $this->cache->get($file);
192
-					}
193
-					if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
194
-						// prevent empty etag
195
-						if (empty($cacheData['etag'])) {
196
-							$etag = $data['etag'];
197
-						} else {
198
-							$etag = $cacheData['etag'];
199
-						}
200
-						$fileId = $cacheData['fileid'];
201
-						$data['fileid'] = $fileId;
202
-						// only reuse data if the file hasn't explicitly changed
203
-						if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
204
-							$data['mtime'] = $cacheData['mtime'];
205
-							if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
206
-								$data['size'] = $cacheData['size'];
207
-							}
208
-							if ($reuseExisting & self::REUSE_ETAG) {
209
-								$data['etag'] = $etag;
210
-							}
211
-						}
212
-						// Only update metadata that has changed
213
-						$newData = array_diff_assoc($data, $cacheData->getData());
214
-					} else {
215
-						$newData = $data;
216
-						$fileId = -1;
217
-					}
218
-					if (!empty($newData)) {
219
-						// Reset the checksum if the data has changed
220
-						$newData['checksum'] = '';
221
-						$data['fileid'] = $this->addToCache($file, $newData, $fileId);
222
-					}
223
-					if (isset($cacheData['size'])) {
224
-						$data['oldSize'] = $cacheData['size'];
225
-					} else {
226
-						$data['oldSize'] = 0;
227
-					}
228
-
229
-					if (isset($cacheData['encrypted'])) {
230
-						$data['encrypted'] = $cacheData['encrypted'];
231
-					}
232
-
233
-					// post-emit only if it was a file. By that we avoid counting/treating folders as files
234
-					if ($data['mimetype'] !== 'httpd/unix-directory') {
235
-						$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', array($file, $this->storageId));
236
-						\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', array('path' => $file, 'storage' => $this->storageId));
237
-					}
238
-
239
-				} else {
240
-					$this->removeFromCache($file);
241
-				}
242
-			} catch (\Exception $e) {
243
-				if ($lock) {
244
-					if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
245
-						$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
246
-					}
247
-				}
248
-				throw $e;
249
-			}
250
-
251
-			//release the acquired lock
252
-			if ($lock) {
253
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
254
-					$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
255
-				}
256
-			}
257
-
258
-			if ($data && !isset($data['encrypted'])) {
259
-				$data['encrypted'] = false;
260
-			}
261
-			return $data;
262
-		}
263
-
264
-		return null;
265
-	}
266
-
267
-	protected function removeFromCache($path) {
268
-		\OC_Hook::emit('Scanner', 'removeFromCache', array('file' => $path));
269
-		$this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', array($path));
270
-		if ($this->cacheActive) {
271
-			$this->cache->remove($path);
272
-		}
273
-	}
274
-
275
-	/**
276
-	 * @param string $path
277
-	 * @param array $data
278
-	 * @param int $fileId
279
-	 * @return int the id of the added file
280
-	 */
281
-	protected function addToCache($path, $data, $fileId = -1) {
282
-		if (isset($data['scan_permissions'])) {
283
-			$data['permissions'] = $data['scan_permissions'];
284
-		}
285
-		\OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
286
-		$this->emit('\OC\Files\Cache\Scanner', 'addToCache', array($path, $this->storageId, $data));
287
-		if ($this->cacheActive) {
288
-			if ($fileId !== -1) {
289
-				$this->cache->update($fileId, $data);
290
-				return $fileId;
291
-			} else {
292
-				return $this->cache->put($path, $data);
293
-			}
294
-		} else {
295
-			return -1;
296
-		}
297
-	}
298
-
299
-	/**
300
-	 * @param string $path
301
-	 * @param array $data
302
-	 * @param int $fileId
303
-	 */
304
-	protected function updateCache($path, $data, $fileId = -1) {
305
-		\OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
306
-		$this->emit('\OC\Files\Cache\Scanner', 'updateCache', array($path, $this->storageId, $data));
307
-		if ($this->cacheActive) {
308
-			if ($fileId !== -1) {
309
-				$this->cache->update($fileId, $data);
310
-			} else {
311
-				$this->cache->put($path, $data);
312
-			}
313
-		}
314
-	}
315
-
316
-	/**
317
-	 * scan a folder and all it's children
318
-	 *
319
-	 * @param string $path
320
-	 * @param bool $recursive
321
-	 * @param int $reuse
322
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
323
-	 * @return array an array of the meta data of the scanned file or folder
324
-	 */
325
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
326
-		if ($reuse === -1) {
327
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
328
-		}
329
-		if ($lock) {
330
-			if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
331
-				$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
332
-				$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
333
-			}
334
-		}
335
-		try {
336
-			$data = $this->scanFile($path, $reuse, -1, null, $lock);
337
-			if ($data and $data['mimetype'] === 'httpd/unix-directory') {
338
-				$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
339
-				$data['size'] = $size;
340
-			}
341
-		} finally {
342
-			if ($lock) {
343
-				if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
344
-					$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
345
-					$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
346
-				}
347
-			}
348
-		}
349
-		return $data;
350
-	}
351
-
352
-	/**
353
-	 * Get the children currently in the cache
354
-	 *
355
-	 * @param int $folderId
356
-	 * @return array[]
357
-	 */
358
-	protected function getExistingChildren($folderId) {
359
-		$existingChildren = array();
360
-		$children = $this->cache->getFolderContentsById($folderId);
361
-		foreach ($children as $child) {
362
-			$existingChildren[$child['name']] = $child;
363
-		}
364
-		return $existingChildren;
365
-	}
366
-
367
-	/**
368
-	 * Get the children from the storage
369
-	 *
370
-	 * @param string $folder
371
-	 * @return string[]
372
-	 */
373
-	protected function getNewChildren($folder) {
374
-		$children = array();
375
-		if ($dh = $this->storage->opendir($folder)) {
376
-			if (is_resource($dh)) {
377
-				while (($file = readdir($dh)) !== false) {
378
-					if (!Filesystem::isIgnoredDir($file)) {
379
-						$children[] = trim(\OC\Files\Filesystem::normalizePath($file), '/');
380
-					}
381
-				}
382
-			}
383
-		}
384
-		return $children;
385
-	}
386
-
387
-	/**
388
-	 * scan all the files and folders in a folder
389
-	 *
390
-	 * @param string $path
391
-	 * @param bool $recursive
392
-	 * @param int $reuse
393
-	 * @param int $folderId id for the folder to be scanned
394
-	 * @param bool $lock set to false to disable getting an additional read lock during scanning
395
-	 * @return int the size of the scanned folder or -1 if the size is unknown at this stage
396
-	 */
397
-	protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
398
-		if ($reuse === -1) {
399
-			$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
400
-		}
401
-		$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId));
402
-		$size = 0;
403
-		if (!is_null($folderId)) {
404
-			$folderId = $this->cache->getId($path);
405
-		}
406
-		$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
407
-
408
-		foreach ($childQueue as $child => $childId) {
409
-			$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
410
-			if ($childSize === -1) {
411
-				$size = -1;
412
-			} else if ($size !== -1) {
413
-				$size += $childSize;
414
-			}
415
-		}
416
-		if ($this->cacheActive) {
417
-			$this->cache->update($folderId, array('size' => $size));
418
-		}
419
-		$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId));
420
-		return $size;
421
-	}
422
-
423
-	private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
424
-		// we put this in it's own function so it cleans up the memory before we start recursing
425
-		$existingChildren = $this->getExistingChildren($folderId);
426
-		$newChildren = $this->getNewChildren($path);
427
-
428
-		if ($this->useTransactions) {
429
-			\OC::$server->getDatabaseConnection()->beginTransaction();
430
-		}
431
-
432
-		$exceptionOccurred = false;
433
-		$childQueue = [];
434
-		foreach ($newChildren as $file) {
435
-			$child = $path ? $path . '/' . $file : $file;
436
-			try {
437
-				$existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : null;
438
-				$data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock);
439
-				if ($data) {
440
-					if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
441
-						$childQueue[$child] = $data['fileid'];
442
-					} else if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
443
-						// only recurse into folders which aren't fully scanned
444
-						$childQueue[$child] = $data['fileid'];
445
-					} else if ($data['size'] === -1) {
446
-						$size = -1;
447
-					} else if ($size !== -1) {
448
-						$size += $data['size'];
449
-					}
450
-				}
451
-			} catch (\Doctrine\DBAL\DBALException $ex) {
452
-				// might happen if inserting duplicate while a scanning
453
-				// process is running in parallel
454
-				// log and ignore
455
-				if ($this->useTransactions) {
456
-					\OC::$server->getDatabaseConnection()->rollback();
457
-					\OC::$server->getDatabaseConnection()->beginTransaction();
458
-				}
459
-				\OC::$server->getLogger()->logException($ex, [
460
-					'message' => 'Exception while scanning file "' . $child . '"',
461
-					'level' => ILogger::DEBUG,
462
-					'app' => 'core',
463
-				]);
464
-				$exceptionOccurred = true;
465
-			} catch (\OCP\Lock\LockedException $e) {
466
-				if ($this->useTransactions) {
467
-					\OC::$server->getDatabaseConnection()->rollback();
468
-				}
469
-				throw $e;
470
-			}
471
-		}
472
-		$removedChildren = \array_diff(array_keys($existingChildren), $newChildren);
473
-		foreach ($removedChildren as $childName) {
474
-			$child = $path ? $path . '/' . $childName : $childName;
475
-			$this->removeFromCache($child);
476
-		}
477
-		if ($this->useTransactions) {
478
-			\OC::$server->getDatabaseConnection()->commit();
479
-		}
480
-		if ($exceptionOccurred) {
481
-			// It might happen that the parallel scan process has already
482
-			// inserted mimetypes but those weren't available yet inside the transaction
483
-			// To make sure to have the updated mime types in such cases,
484
-			// we reload them here
485
-			\OC::$server->getMimeTypeLoader()->reset();
486
-		}
487
-		return $childQueue;
488
-	}
489
-
490
-	/**
491
-	 * check if the file should be ignored when scanning
492
-	 * NOTE: files with a '.part' extension are ignored as well!
493
-	 *       prevents unfinished put requests to be scanned
494
-	 *
495
-	 * @param string $file
496
-	 * @return boolean
497
-	 */
498
-	public static function isPartialFile($file) {
499
-		if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
500
-			return true;
501
-		}
502
-		if (strpos($file, '.part/') !== false) {
503
-			return true;
504
-		}
505
-
506
-		return false;
507
-	}
508
-
509
-	/**
510
-	 * walk over any folders that are not fully scanned yet and scan them
511
-	 */
512
-	public function backgroundScan() {
513
-		if (!$this->cache->inCache('')) {
514
-			$this->runBackgroundScanJob(function () {
515
-				$this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
516
-			}, '');
517
-		} else {
518
-			$lastPath = null;
519
-			while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
520
-				$this->runBackgroundScanJob(function () use ($path) {
521
-					$this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
522
-				}, $path);
523
-				// FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
524
-				// to make this possible
525
-				$lastPath = $path;
526
-			}
527
-		}
528
-	}
529
-
530
-	private function runBackgroundScanJob(callable $callback, $path) {
531
-		try {
532
-			$callback();
533
-			\OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path));
534
-			if ($this->cacheActive && $this->cache instanceof Cache) {
535
-				$this->cache->correctFolderSize($path, null, true);
536
-			}
537
-		} catch (\OCP\Files\StorageInvalidException $e) {
538
-			// skip unavailable storages
539
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
540
-			// skip unavailable storages
541
-		} catch (\OCP\Files\ForbiddenException $e) {
542
-			// skip forbidden storages
543
-		} catch (\OCP\Lock\LockedException $e) {
544
-			// skip unavailable storages
545
-		}
546
-	}
547
-
548
-	/**
549
-	 * Set whether the cache is affected by scan operations
550
-	 *
551
-	 * @param boolean $active The active state of the cache
552
-	 */
553
-	public function setCacheActive($active) {
554
-		$this->cacheActive = $active;
555
-	}
56
+    /**
57
+     * @var \OC\Files\Storage\Storage $storage
58
+     */
59
+    protected $storage;
60
+
61
+    /**
62
+     * @var string $storageId
63
+     */
64
+    protected $storageId;
65
+
66
+    /**
67
+     * @var \OC\Files\Cache\Cache $cache
68
+     */
69
+    protected $cache;
70
+
71
+    /**
72
+     * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache
73
+     */
74
+    protected $cacheActive;
75
+
76
+    /**
77
+     * @var bool $useTransactions whether to use transactions
78
+     */
79
+    protected $useTransactions = true;
80
+
81
+    /**
82
+     * @var \OCP\Lock\ILockingProvider
83
+     */
84
+    protected $lockingProvider;
85
+
86
+    public function __construct(\OC\Files\Storage\Storage $storage) {
87
+        $this->storage = $storage;
88
+        $this->storageId = $this->storage->getId();
89
+        $this->cache = $storage->getCache();
90
+        $this->cacheActive = !\OC::$server->getConfig()->getSystemValue('filesystem_cache_readonly', false);
91
+        $this->lockingProvider = \OC::$server->getLockingProvider();
92
+    }
93
+
94
+    /**
95
+     * Whether to wrap the scanning of a folder in a database transaction
96
+     * On default transactions are used
97
+     *
98
+     * @param bool $useTransactions
99
+     */
100
+    public function setUseTransactions($useTransactions) {
101
+        $this->useTransactions = $useTransactions;
102
+    }
103
+
104
+    /**
105
+     * get all the metadata of a file or folder
106
+     * *
107
+     *
108
+     * @param string $path
109
+     * @return array an array of metadata of the file
110
+     */
111
+    protected function getData($path) {
112
+        $data = $this->storage->getMetaData($path);
113
+        if (is_null($data)) {
114
+            \OCP\Util::writeLog(Scanner::class, "!!! Path '$path' is not accessible or present !!!", ILogger::DEBUG);
115
+        }
116
+        return $data;
117
+    }
118
+
119
+    /**
120
+     * scan a single file and store it in the cache
121
+     *
122
+     * @param string $file
123
+     * @param int $reuseExisting
124
+     * @param int $parentId
125
+     * @param array | null $cacheData existing data in the cache for the file to be scanned
126
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
127
+     * @return array an array of metadata of the scanned file
128
+     * @throws \OC\ServerNotAvailableException
129
+     * @throws \OCP\Lock\LockedException
130
+     */
131
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) {
132
+        if ($file !== '') {
133
+            try {
134
+                $this->storage->verifyPath(dirname($file), basename($file));
135
+            } catch (\Exception $e) {
136
+                return null;
137
+            }
138
+        }
139
+        // only proceed if $file is not a partial file nor a blacklisted file
140
+        if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) {
141
+
142
+            //acquire a lock
143
+            if ($lock) {
144
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
145
+                    $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
146
+                }
147
+            }
148
+
149
+            try {
150
+                $data = $this->getData($file);
151
+            } catch (ForbiddenException $e) {
152
+                if ($lock) {
153
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
154
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
155
+                    }
156
+                }
157
+
158
+                return null;
159
+            }
160
+
161
+            try {
162
+                if ($data) {
163
+
164
+                    // pre-emit only if it was a file. By that we avoid counting/treating folders as files
165
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
166
+                        $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId));
167
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId));
168
+                    }
169
+
170
+                    $parent = dirname($file);
171
+                    if ($parent === '.' or $parent === '/') {
172
+                        $parent = '';
173
+                    }
174
+                    if ($parentId === -1) {
175
+                        $parentId = $this->cache->getParentId($file);
176
+                    }
177
+
178
+                    // scan the parent if it's not in the cache (id -1) and the current file is not the root folder
179
+                    if ($file and $parentId === -1) {
180
+                        $parentData = $this->scanFile($parent);
181
+                        if (!$parentData) {
182
+                            return null;
183
+                        }
184
+                        $parentId = $parentData['fileid'];
185
+                    }
186
+                    if ($parent) {
187
+                        $data['parent'] = $parentId;
188
+                    }
189
+                    if (is_null($cacheData)) {
190
+                        /** @var CacheEntry $cacheData */
191
+                        $cacheData = $this->cache->get($file);
192
+                    }
193
+                    if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) {
194
+                        // prevent empty etag
195
+                        if (empty($cacheData['etag'])) {
196
+                            $etag = $data['etag'];
197
+                        } else {
198
+                            $etag = $cacheData['etag'];
199
+                        }
200
+                        $fileId = $cacheData['fileid'];
201
+                        $data['fileid'] = $fileId;
202
+                        // only reuse data if the file hasn't explicitly changed
203
+                        if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
204
+                            $data['mtime'] = $cacheData['mtime'];
205
+                            if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
206
+                                $data['size'] = $cacheData['size'];
207
+                            }
208
+                            if ($reuseExisting & self::REUSE_ETAG) {
209
+                                $data['etag'] = $etag;
210
+                            }
211
+                        }
212
+                        // Only update metadata that has changed
213
+                        $newData = array_diff_assoc($data, $cacheData->getData());
214
+                    } else {
215
+                        $newData = $data;
216
+                        $fileId = -1;
217
+                    }
218
+                    if (!empty($newData)) {
219
+                        // Reset the checksum if the data has changed
220
+                        $newData['checksum'] = '';
221
+                        $data['fileid'] = $this->addToCache($file, $newData, $fileId);
222
+                    }
223
+                    if (isset($cacheData['size'])) {
224
+                        $data['oldSize'] = $cacheData['size'];
225
+                    } else {
226
+                        $data['oldSize'] = 0;
227
+                    }
228
+
229
+                    if (isset($cacheData['encrypted'])) {
230
+                        $data['encrypted'] = $cacheData['encrypted'];
231
+                    }
232
+
233
+                    // post-emit only if it was a file. By that we avoid counting/treating folders as files
234
+                    if ($data['mimetype'] !== 'httpd/unix-directory') {
235
+                        $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', array($file, $this->storageId));
236
+                        \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', array('path' => $file, 'storage' => $this->storageId));
237
+                    }
238
+
239
+                } else {
240
+                    $this->removeFromCache($file);
241
+                }
242
+            } catch (\Exception $e) {
243
+                if ($lock) {
244
+                    if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
245
+                        $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
246
+                    }
247
+                }
248
+                throw $e;
249
+            }
250
+
251
+            //release the acquired lock
252
+            if ($lock) {
253
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
254
+                    $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
255
+                }
256
+            }
257
+
258
+            if ($data && !isset($data['encrypted'])) {
259
+                $data['encrypted'] = false;
260
+            }
261
+            return $data;
262
+        }
263
+
264
+        return null;
265
+    }
266
+
267
+    protected function removeFromCache($path) {
268
+        \OC_Hook::emit('Scanner', 'removeFromCache', array('file' => $path));
269
+        $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', array($path));
270
+        if ($this->cacheActive) {
271
+            $this->cache->remove($path);
272
+        }
273
+    }
274
+
275
+    /**
276
+     * @param string $path
277
+     * @param array $data
278
+     * @param int $fileId
279
+     * @return int the id of the added file
280
+     */
281
+    protected function addToCache($path, $data, $fileId = -1) {
282
+        if (isset($data['scan_permissions'])) {
283
+            $data['permissions'] = $data['scan_permissions'];
284
+        }
285
+        \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
286
+        $this->emit('\OC\Files\Cache\Scanner', 'addToCache', array($path, $this->storageId, $data));
287
+        if ($this->cacheActive) {
288
+            if ($fileId !== -1) {
289
+                $this->cache->update($fileId, $data);
290
+                return $fileId;
291
+            } else {
292
+                return $this->cache->put($path, $data);
293
+            }
294
+        } else {
295
+            return -1;
296
+        }
297
+    }
298
+
299
+    /**
300
+     * @param string $path
301
+     * @param array $data
302
+     * @param int $fileId
303
+     */
304
+    protected function updateCache($path, $data, $fileId = -1) {
305
+        \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data));
306
+        $this->emit('\OC\Files\Cache\Scanner', 'updateCache', array($path, $this->storageId, $data));
307
+        if ($this->cacheActive) {
308
+            if ($fileId !== -1) {
309
+                $this->cache->update($fileId, $data);
310
+            } else {
311
+                $this->cache->put($path, $data);
312
+            }
313
+        }
314
+    }
315
+
316
+    /**
317
+     * scan a folder and all it's children
318
+     *
319
+     * @param string $path
320
+     * @param bool $recursive
321
+     * @param int $reuse
322
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
323
+     * @return array an array of the meta data of the scanned file or folder
324
+     */
325
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
326
+        if ($reuse === -1) {
327
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
328
+        }
329
+        if ($lock) {
330
+            if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
331
+                $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
332
+                $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
333
+            }
334
+        }
335
+        try {
336
+            $data = $this->scanFile($path, $reuse, -1, null, $lock);
337
+            if ($data and $data['mimetype'] === 'httpd/unix-directory') {
338
+                $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock);
339
+                $data['size'] = $size;
340
+            }
341
+        } finally {
342
+            if ($lock) {
343
+                if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
344
+                    $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
345
+                    $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
346
+                }
347
+            }
348
+        }
349
+        return $data;
350
+    }
351
+
352
+    /**
353
+     * Get the children currently in the cache
354
+     *
355
+     * @param int $folderId
356
+     * @return array[]
357
+     */
358
+    protected function getExistingChildren($folderId) {
359
+        $existingChildren = array();
360
+        $children = $this->cache->getFolderContentsById($folderId);
361
+        foreach ($children as $child) {
362
+            $existingChildren[$child['name']] = $child;
363
+        }
364
+        return $existingChildren;
365
+    }
366
+
367
+    /**
368
+     * Get the children from the storage
369
+     *
370
+     * @param string $folder
371
+     * @return string[]
372
+     */
373
+    protected function getNewChildren($folder) {
374
+        $children = array();
375
+        if ($dh = $this->storage->opendir($folder)) {
376
+            if (is_resource($dh)) {
377
+                while (($file = readdir($dh)) !== false) {
378
+                    if (!Filesystem::isIgnoredDir($file)) {
379
+                        $children[] = trim(\OC\Files\Filesystem::normalizePath($file), '/');
380
+                    }
381
+                }
382
+            }
383
+        }
384
+        return $children;
385
+    }
386
+
387
+    /**
388
+     * scan all the files and folders in a folder
389
+     *
390
+     * @param string $path
391
+     * @param bool $recursive
392
+     * @param int $reuse
393
+     * @param int $folderId id for the folder to be scanned
394
+     * @param bool $lock set to false to disable getting an additional read lock during scanning
395
+     * @return int the size of the scanned folder or -1 if the size is unknown at this stage
396
+     */
397
+    protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) {
398
+        if ($reuse === -1) {
399
+            $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
400
+        }
401
+        $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId));
402
+        $size = 0;
403
+        if (!is_null($folderId)) {
404
+            $folderId = $this->cache->getId($path);
405
+        }
406
+        $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
407
+
408
+        foreach ($childQueue as $child => $childId) {
409
+            $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock);
410
+            if ($childSize === -1) {
411
+                $size = -1;
412
+            } else if ($size !== -1) {
413
+                $size += $childSize;
414
+            }
415
+        }
416
+        if ($this->cacheActive) {
417
+            $this->cache->update($folderId, array('size' => $size));
418
+        }
419
+        $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId));
420
+        return $size;
421
+    }
422
+
423
+    private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
424
+        // we put this in it's own function so it cleans up the memory before we start recursing
425
+        $existingChildren = $this->getExistingChildren($folderId);
426
+        $newChildren = $this->getNewChildren($path);
427
+
428
+        if ($this->useTransactions) {
429
+            \OC::$server->getDatabaseConnection()->beginTransaction();
430
+        }
431
+
432
+        $exceptionOccurred = false;
433
+        $childQueue = [];
434
+        foreach ($newChildren as $file) {
435
+            $child = $path ? $path . '/' . $file : $file;
436
+            try {
437
+                $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : null;
438
+                $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock);
439
+                if ($data) {
440
+                    if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) {
441
+                        $childQueue[$child] = $data['fileid'];
442
+                    } else if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE_INCOMPLETE and $data['size'] === -1) {
443
+                        // only recurse into folders which aren't fully scanned
444
+                        $childQueue[$child] = $data['fileid'];
445
+                    } else if ($data['size'] === -1) {
446
+                        $size = -1;
447
+                    } else if ($size !== -1) {
448
+                        $size += $data['size'];
449
+                    }
450
+                }
451
+            } catch (\Doctrine\DBAL\DBALException $ex) {
452
+                // might happen if inserting duplicate while a scanning
453
+                // process is running in parallel
454
+                // log and ignore
455
+                if ($this->useTransactions) {
456
+                    \OC::$server->getDatabaseConnection()->rollback();
457
+                    \OC::$server->getDatabaseConnection()->beginTransaction();
458
+                }
459
+                \OC::$server->getLogger()->logException($ex, [
460
+                    'message' => 'Exception while scanning file "' . $child . '"',
461
+                    'level' => ILogger::DEBUG,
462
+                    'app' => 'core',
463
+                ]);
464
+                $exceptionOccurred = true;
465
+            } catch (\OCP\Lock\LockedException $e) {
466
+                if ($this->useTransactions) {
467
+                    \OC::$server->getDatabaseConnection()->rollback();
468
+                }
469
+                throw $e;
470
+            }
471
+        }
472
+        $removedChildren = \array_diff(array_keys($existingChildren), $newChildren);
473
+        foreach ($removedChildren as $childName) {
474
+            $child = $path ? $path . '/' . $childName : $childName;
475
+            $this->removeFromCache($child);
476
+        }
477
+        if ($this->useTransactions) {
478
+            \OC::$server->getDatabaseConnection()->commit();
479
+        }
480
+        if ($exceptionOccurred) {
481
+            // It might happen that the parallel scan process has already
482
+            // inserted mimetypes but those weren't available yet inside the transaction
483
+            // To make sure to have the updated mime types in such cases,
484
+            // we reload them here
485
+            \OC::$server->getMimeTypeLoader()->reset();
486
+        }
487
+        return $childQueue;
488
+    }
489
+
490
+    /**
491
+     * check if the file should be ignored when scanning
492
+     * NOTE: files with a '.part' extension are ignored as well!
493
+     *       prevents unfinished put requests to be scanned
494
+     *
495
+     * @param string $file
496
+     * @return boolean
497
+     */
498
+    public static function isPartialFile($file) {
499
+        if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
500
+            return true;
501
+        }
502
+        if (strpos($file, '.part/') !== false) {
503
+            return true;
504
+        }
505
+
506
+        return false;
507
+    }
508
+
509
+    /**
510
+     * walk over any folders that are not fully scanned yet and scan them
511
+     */
512
+    public function backgroundScan() {
513
+        if (!$this->cache->inCache('')) {
514
+            $this->runBackgroundScanJob(function () {
515
+                $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG);
516
+            }, '');
517
+        } else {
518
+            $lastPath = null;
519
+            while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
520
+                $this->runBackgroundScanJob(function () use ($path) {
521
+                    $this->scan($path, self::SCAN_RECURSIVE_INCOMPLETE, self::REUSE_ETAG | self::REUSE_SIZE);
522
+                }, $path);
523
+                // FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
524
+                // to make this possible
525
+                $lastPath = $path;
526
+            }
527
+        }
528
+    }
529
+
530
+    private function runBackgroundScanJob(callable $callback, $path) {
531
+        try {
532
+            $callback();
533
+            \OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path));
534
+            if ($this->cacheActive && $this->cache instanceof Cache) {
535
+                $this->cache->correctFolderSize($path, null, true);
536
+            }
537
+        } catch (\OCP\Files\StorageInvalidException $e) {
538
+            // skip unavailable storages
539
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
540
+            // skip unavailable storages
541
+        } catch (\OCP\Files\ForbiddenException $e) {
542
+            // skip forbidden storages
543
+        } catch (\OCP\Lock\LockedException $e) {
544
+            // skip unavailable storages
545
+        }
546
+    }
547
+
548
+    /**
549
+     * Set whether the cache is affected by scan operations
550
+     *
551
+     * @param boolean $active The active state of the cache
552
+     */
553
+    public function setCacheActive($active) {
554
+        $this->cacheActive = $active;
555
+    }
556 556
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Cache.php 2 patches
Indentation   +871 added lines, -871 removed lines patch added patch discarded remove patch
@@ -61,886 +61,886 @@
 block discarded – undo
61 61
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
62 62
  */
63 63
 class Cache implements ICache {
64
-	use MoveFromCacheTrait {
65
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
66
-	}
67
-
68
-	/**
69
-	 * @var array partial data for the cache
70
-	 */
71
-	protected $partial = array();
72
-
73
-	/**
74
-	 * @var string
75
-	 */
76
-	protected $storageId;
77
-
78
-	private $storage;
79
-
80
-	/**
81
-	 * @var Storage $storageCache
82
-	 */
83
-	protected $storageCache;
84
-
85
-	/** @var IMimeTypeLoader */
86
-	protected $mimetypeLoader;
87
-
88
-	/**
89
-	 * @var IDBConnection
90
-	 */
91
-	protected $connection;
92
-
93
-	protected $eventDispatcher;
94
-
95
-	/** @var QuerySearchHelper */
96
-	protected $querySearchHelper;
97
-
98
-	/**
99
-	 * @param IStorage $storage
100
-	 */
101
-	public function __construct(IStorage $storage) {
102
-		$this->storageId = $storage->getId();
103
-		$this->storage = $storage;
104
-		if (strlen($this->storageId) > 64) {
105
-			$this->storageId = md5($this->storageId);
106
-		}
107
-
108
-		$this->storageCache = new Storage($storage);
109
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
110
-		$this->connection = \OC::$server->getDatabaseConnection();
111
-		$this->eventDispatcher = \OC::$server->getEventDispatcher();
112
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
113
-	}
114
-
115
-	/**
116
-	 * Get the numeric storage id for this cache's storage
117
-	 *
118
-	 * @return int
119
-	 */
120
-	public function getNumericStorageId() {
121
-		return $this->storageCache->getNumericId();
122
-	}
123
-
124
-	/**
125
-	 * get the stored metadata of a file or folder
126
-	 *
127
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
128
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
129
-	 */
130
-	public function get($file) {
131
-		if (is_string($file) or $file == '') {
132
-			// normalize file
133
-			$file = $this->normalize($file);
134
-
135
-			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
136
-			$params = array($this->getNumericStorageId(), md5($file));
137
-		} else { //file id
138
-			$where = 'WHERE `fileid` = ?';
139
-			$params = array($file);
140
-		}
141
-		$sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
64
+    use MoveFromCacheTrait {
65
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
66
+    }
67
+
68
+    /**
69
+     * @var array partial data for the cache
70
+     */
71
+    protected $partial = array();
72
+
73
+    /**
74
+     * @var string
75
+     */
76
+    protected $storageId;
77
+
78
+    private $storage;
79
+
80
+    /**
81
+     * @var Storage $storageCache
82
+     */
83
+    protected $storageCache;
84
+
85
+    /** @var IMimeTypeLoader */
86
+    protected $mimetypeLoader;
87
+
88
+    /**
89
+     * @var IDBConnection
90
+     */
91
+    protected $connection;
92
+
93
+    protected $eventDispatcher;
94
+
95
+    /** @var QuerySearchHelper */
96
+    protected $querySearchHelper;
97
+
98
+    /**
99
+     * @param IStorage $storage
100
+     */
101
+    public function __construct(IStorage $storage) {
102
+        $this->storageId = $storage->getId();
103
+        $this->storage = $storage;
104
+        if (strlen($this->storageId) > 64) {
105
+            $this->storageId = md5($this->storageId);
106
+        }
107
+
108
+        $this->storageCache = new Storage($storage);
109
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
110
+        $this->connection = \OC::$server->getDatabaseConnection();
111
+        $this->eventDispatcher = \OC::$server->getEventDispatcher();
112
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
113
+    }
114
+
115
+    /**
116
+     * Get the numeric storage id for this cache's storage
117
+     *
118
+     * @return int
119
+     */
120
+    public function getNumericStorageId() {
121
+        return $this->storageCache->getNumericId();
122
+    }
123
+
124
+    /**
125
+     * get the stored metadata of a file or folder
126
+     *
127
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
128
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
129
+     */
130
+    public function get($file) {
131
+        if (is_string($file) or $file == '') {
132
+            // normalize file
133
+            $file = $this->normalize($file);
134
+
135
+            $where = 'WHERE `storage` = ? AND `path_hash` = ?';
136
+            $params = array($this->getNumericStorageId(), md5($file));
137
+        } else { //file id
138
+            $where = 'WHERE `fileid` = ?';
139
+            $params = array($file);
140
+        }
141
+        $sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
142 142
 					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
143 143
 				FROM `*PREFIX*filecache` ' . $where;
144
-		$result = $this->connection->executeQuery($sql, $params);
145
-		$data = $result->fetch();
146
-
147
-		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
148
-		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
149
-		if ($data === null) {
150
-			$data = false;
151
-		}
152
-
153
-		//merge partial data
154
-		if (!$data and is_string($file)) {
155
-			if (isset($this->partial[$file])) {
156
-				$data = $this->partial[$file];
157
-			}
158
-			return $data;
159
-		} else if (!$data) {
160
-			return $data;
161
-		} else {
162
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * Create a CacheEntry from database row
168
-	 *
169
-	 * @param array $data
170
-	 * @param IMimeTypeLoader $mimetypeLoader
171
-	 * @return CacheEntry
172
-	 */
173
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
174
-		//fix types
175
-		$data['fileid'] = (int)$data['fileid'];
176
-		$data['parent'] = (int)$data['parent'];
177
-		$data['size'] = 0 + $data['size'];
178
-		$data['mtime'] = (int)$data['mtime'];
179
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
180
-		$data['encryptedVersion'] = (int)$data['encrypted'];
181
-		$data['encrypted'] = (bool)$data['encrypted'];
182
-		$data['storage_id'] = $data['storage'];
183
-		$data['storage'] = (int)$data['storage'];
184
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
185
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
186
-		if ($data['storage_mtime'] == 0) {
187
-			$data['storage_mtime'] = $data['mtime'];
188
-		}
189
-		$data['permissions'] = (int)$data['permissions'];
190
-		return new CacheEntry($data);
191
-	}
192
-
193
-	/**
194
-	 * get the metadata of all files stored in $folder
195
-	 *
196
-	 * @param string $folder
197
-	 * @return ICacheEntry[]
198
-	 */
199
-	public function getFolderContents($folder) {
200
-		$fileId = $this->getId($folder);
201
-		return $this->getFolderContentsById($fileId);
202
-	}
203
-
204
-	/**
205
-	 * get the metadata of all files stored in $folder
206
-	 *
207
-	 * @param int $fileId the file id of the folder
208
-	 * @return ICacheEntry[]
209
-	 */
210
-	public function getFolderContentsById($fileId) {
211
-		if ($fileId > -1) {
212
-			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
144
+        $result = $this->connection->executeQuery($sql, $params);
145
+        $data = $result->fetch();
146
+
147
+        //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
148
+        //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
149
+        if ($data === null) {
150
+            $data = false;
151
+        }
152
+
153
+        //merge partial data
154
+        if (!$data and is_string($file)) {
155
+            if (isset($this->partial[$file])) {
156
+                $data = $this->partial[$file];
157
+            }
158
+            return $data;
159
+        } else if (!$data) {
160
+            return $data;
161
+        } else {
162
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
163
+        }
164
+    }
165
+
166
+    /**
167
+     * Create a CacheEntry from database row
168
+     *
169
+     * @param array $data
170
+     * @param IMimeTypeLoader $mimetypeLoader
171
+     * @return CacheEntry
172
+     */
173
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
174
+        //fix types
175
+        $data['fileid'] = (int)$data['fileid'];
176
+        $data['parent'] = (int)$data['parent'];
177
+        $data['size'] = 0 + $data['size'];
178
+        $data['mtime'] = (int)$data['mtime'];
179
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
180
+        $data['encryptedVersion'] = (int)$data['encrypted'];
181
+        $data['encrypted'] = (bool)$data['encrypted'];
182
+        $data['storage_id'] = $data['storage'];
183
+        $data['storage'] = (int)$data['storage'];
184
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
185
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
186
+        if ($data['storage_mtime'] == 0) {
187
+            $data['storage_mtime'] = $data['mtime'];
188
+        }
189
+        $data['permissions'] = (int)$data['permissions'];
190
+        return new CacheEntry($data);
191
+    }
192
+
193
+    /**
194
+     * get the metadata of all files stored in $folder
195
+     *
196
+     * @param string $folder
197
+     * @return ICacheEntry[]
198
+     */
199
+    public function getFolderContents($folder) {
200
+        $fileId = $this->getId($folder);
201
+        return $this->getFolderContentsById($fileId);
202
+    }
203
+
204
+    /**
205
+     * get the metadata of all files stored in $folder
206
+     *
207
+     * @param int $fileId the file id of the folder
208
+     * @return ICacheEntry[]
209
+     */
210
+    public function getFolderContentsById($fileId) {
211
+        if ($fileId > -1) {
212
+            $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
213 213
 						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
214 214
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
215
-			$result = $this->connection->executeQuery($sql, [$fileId]);
216
-			$files = $result->fetchAll();
217
-			return array_map(function (array $data) {
218
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);
219
-			}, $files);
220
-		}
221
-		return [];
222
-	}
223
-
224
-	/**
225
-	 * insert or update meta data for a file or folder
226
-	 *
227
-	 * @param string $file
228
-	 * @param array $data
229
-	 *
230
-	 * @return int file id
231
-	 * @throws \RuntimeException
232
-	 */
233
-	public function put($file, array $data) {
234
-		if (($id = $this->getId($file)) > -1) {
235
-			$this->update($id, $data, $file);
236
-			return $id;
237
-		} else {
238
-			return $this->insert($file, $data);
239
-		}
240
-	}
241
-
242
-	/**
243
-	 * insert meta data for a new file or folder
244
-	 *
245
-	 * @param string $file
246
-	 * @param array $data
247
-	 *
248
-	 * @return int file id
249
-	 * @throws \RuntimeException
250
-	 *
251
-	 * @suppress SqlInjectionChecker
252
-	 */
253
-	public function insert($file, array $data) {
254
-		// normalize file
255
-		$file = $this->normalize($file);
256
-
257
-		if (isset($this->partial[$file])) { //add any saved partial data
258
-			$data = array_merge($this->partial[$file], $data);
259
-			unset($this->partial[$file]);
260
-		}
261
-
262
-		$requiredFields = array('size', 'mtime', 'mimetype');
263
-		foreach ($requiredFields as $field) {
264
-			if (!isset($data[$field])) { //data not complete save as partial and return
265
-				$this->partial[$file] = $data;
266
-				return -1;
267
-			}
268
-		}
269
-
270
-		$data['path'] = $file;
271
-		$data['parent'] = $this->getParentId($file);
272
-		$data['name'] = basename($file);
273
-
274
-		list($queryParts, $params) = $this->buildParts($data);
275
-		$queryParts[] = '`storage`';
276
-		$params[] = $this->getNumericStorageId();
277
-
278
-		$queryParts = array_map(function ($item) {
279
-			return trim($item, "`");
280
-		}, $queryParts);
281
-		$values = array_combine($queryParts, $params);
282
-
283
-		try {
284
-			$builder = $this->connection->getQueryBuilder();
285
-			$builder->insert('filecache');
286
-
287
-			foreach ($values as $column => $value) {
288
-				$builder->setValue($column, $builder->createNamedParameter($value));
289
-			}
290
-
291
-			if ($builder->execute()) {
292
-				$fileId = (int)$this->connection->lastInsertId('*PREFIX*filecache');
293
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
294
-				return $fileId;
295
-			}
296
-		} catch (UniqueConstraintViolationException $e) {
297
-			// entry exists already
298
-		}
299
-
300
-		// The file was created in the mean time
301
-		if (($id = $this->getId($file)) > -1) {
302
-			$this->update($id, $data);
303
-			return $id;
304
-		} else {
305
-			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.');
306
-		}
307
-	}
308
-
309
-	/**
310
-	 * update the metadata of an existing file or folder in the cache
311
-	 *
312
-	 * @param int $id the fileid of the existing file or folder
313
-	 * @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
314
-	 */
315
-	public function update($id, array $data) {
316
-
317
-		if (isset($data['path'])) {
318
-			// normalize path
319
-			$data['path'] = $this->normalize($data['path']);
320
-		}
321
-
322
-		if (isset($data['name'])) {
323
-			// normalize path
324
-			$data['name'] = $this->normalize($data['name']);
325
-		}
326
-
327
-		list($queryParts, $params) = $this->buildParts($data);
328
-		// duplicate $params because we need the parts twice in the SQL statement
329
-		// once for the SET part, once in the WHERE clause
330
-		$params = array_merge($params, $params);
331
-		$params[] = $id;
332
-
333
-		// don't update if the data we try to set is the same as the one in the record
334
-		// some databases (Postgres) don't like superfluous updates
335
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
336
-			'WHERE (' .
337
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
338
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
339
-			') AND `fileid` = ? ';
340
-		$this->connection->executeQuery($sql, $params);
341
-
342
-		$path = $this->getPathById($id);
343
-		// path can still be null if the file doesn't exist
344
-		if ($path !== null) {
345
-			$this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
346
-		}
347
-	}
348
-
349
-	/**
350
-	 * extract query parts and params array from data array
351
-	 *
352
-	 * @param array $data
353
-	 * @return array [$queryParts, $params]
354
-	 *        $queryParts: string[], the (escaped) column names to be set in the query
355
-	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
356
-	 */
357
-	protected function buildParts(array $data) {
358
-		$fields = array(
359
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
360
-			'etag', 'permissions', 'checksum', 'storage');
361
-
362
-		$doNotCopyStorageMTime = false;
363
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
364
-			// this horrific magic tells it to not copy storage_mtime to mtime
365
-			unset($data['mtime']);
366
-			$doNotCopyStorageMTime = true;
367
-		}
368
-
369
-		$params = array();
370
-		$queryParts = array();
371
-		foreach ($data as $name => $value) {
372
-			if (array_search($name, $fields) !== false) {
373
-				if ($name === 'path') {
374
-					$params[] = md5($value);
375
-					$queryParts[] = '`path_hash`';
376
-				} elseif ($name === 'mimetype') {
377
-					$params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
378
-					$queryParts[] = '`mimepart`';
379
-					$value = $this->mimetypeLoader->getId($value);
380
-				} elseif ($name === 'storage_mtime') {
381
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
382
-						$params[] = $value;
383
-						$queryParts[] = '`mtime`';
384
-					}
385
-				} elseif ($name === 'encrypted') {
386
-					if (isset($data['encryptedVersion'])) {
387
-						$value = $data['encryptedVersion'];
388
-					} else {
389
-						// Boolean to integer conversion
390
-						$value = $value ? 1 : 0;
391
-					}
392
-				}
393
-				$params[] = $value;
394
-				$queryParts[] = '`' . $name . '`';
395
-			}
396
-		}
397
-		return array($queryParts, $params);
398
-	}
399
-
400
-	/**
401
-	 * get the file id for a file
402
-	 *
403
-	 * 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
404
-	 *
405
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
406
-	 *
407
-	 * @param string $file
408
-	 * @return int
409
-	 */
410
-	public function getId($file) {
411
-		// normalize file
412
-		$file = $this->normalize($file);
413
-
414
-		$pathHash = md5($file);
415
-
416
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
417
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
418
-		if ($row = $result->fetch()) {
419
-			return $row['fileid'];
420
-		} else {
421
-			return -1;
422
-		}
423
-	}
424
-
425
-	/**
426
-	 * get the id of the parent folder of a file
427
-	 *
428
-	 * @param string $file
429
-	 * @return int
430
-	 */
431
-	public function getParentId($file) {
432
-		if ($file === '') {
433
-			return -1;
434
-		} else {
435
-			$parent = $this->getParentPath($file);
436
-			return (int)$this->getId($parent);
437
-		}
438
-	}
439
-
440
-	private function getParentPath($path) {
441
-		$parent = dirname($path);
442
-		if ($parent === '.') {
443
-			$parent = '';
444
-		}
445
-		return $parent;
446
-	}
447
-
448
-	/**
449
-	 * check if a file is available in the cache
450
-	 *
451
-	 * @param string $file
452
-	 * @return bool
453
-	 */
454
-	public function inCache($file) {
455
-		return $this->getId($file) != -1;
456
-	}
457
-
458
-	/**
459
-	 * remove a file or folder from the cache
460
-	 *
461
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
462
-	 *
463
-	 * @param string $file
464
-	 */
465
-	public function remove($file) {
466
-		$entry = $this->get($file);
467
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
468
-		$this->connection->executeQuery($sql, array($entry['fileid']));
469
-		if ($entry['mimetype'] === 'httpd/unix-directory') {
470
-			$this->removeChildren($entry);
471
-		}
472
-	}
473
-
474
-	/**
475
-	 * Get all sub folders of a folder
476
-	 *
477
-	 * @param array $entry the cache entry of the folder to get the subfolders for
478
-	 * @return array[] the cache entries for the subfolders
479
-	 */
480
-	private function getSubFolders($entry) {
481
-		$children = $this->getFolderContentsById($entry['fileid']);
482
-		return array_filter($children, function ($child) {
483
-			return $child['mimetype'] === 'httpd/unix-directory';
484
-		});
485
-	}
486
-
487
-	/**
488
-	 * Recursively remove all children of a folder
489
-	 *
490
-	 * @param array $entry the cache entry of the folder to remove the children of
491
-	 * @throws \OC\DatabaseException
492
-	 */
493
-	private function removeChildren($entry) {
494
-		$subFolders = $this->getSubFolders($entry);
495
-		foreach ($subFolders as $folder) {
496
-			$this->removeChildren($folder);
497
-		}
498
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
499
-		$this->connection->executeQuery($sql, array($entry['fileid']));
500
-	}
501
-
502
-	/**
503
-	 * Move a file or folder in the cache
504
-	 *
505
-	 * @param string $source
506
-	 * @param string $target
507
-	 */
508
-	public function move($source, $target) {
509
-		$this->moveFromCache($this, $source, $target);
510
-	}
511
-
512
-	/**
513
-	 * Get the storage id and path needed for a move
514
-	 *
515
-	 * @param string $path
516
-	 * @return array [$storageId, $internalPath]
517
-	 */
518
-	protected function getMoveInfo($path) {
519
-		return [$this->getNumericStorageId(), $path];
520
-	}
521
-
522
-	/**
523
-	 * Move a file or folder in the cache
524
-	 *
525
-	 * @param \OCP\Files\Cache\ICache $sourceCache
526
-	 * @param string $sourcePath
527
-	 * @param string $targetPath
528
-	 * @throws \OC\DatabaseException
529
-	 * @throws \Exception if the given storages have an invalid id
530
-	 * @suppress SqlInjectionChecker
531
-	 */
532
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
533
-		if ($sourceCache instanceof Cache) {
534
-			// normalize source and target
535
-			$sourcePath = $this->normalize($sourcePath);
536
-			$targetPath = $this->normalize($targetPath);
537
-
538
-			$sourceData = $sourceCache->get($sourcePath);
539
-			$sourceId = $sourceData['fileid'];
540
-			$newParentId = $this->getParentId($targetPath);
541
-
542
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
543
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
544
-
545
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
546
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
547
-			}
548
-			if (is_null($targetStorageId) || $targetStorageId === false) {
549
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
550
-			}
551
-
552
-			$this->connection->beginTransaction();
553
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
554
-				//update all child entries
555
-				$sourceLength = mb_strlen($sourcePath);
556
-				$query = $this->connection->getQueryBuilder();
557
-
558
-				$fun = $query->func();
559
-				$newPathFunction = $fun->concat(
560
-					$query->createNamedParameter($targetPath),
561
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
562
-				);
563
-				$query->update('filecache')
564
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
565
-					->set('path_hash', $fun->md5($newPathFunction))
566
-					->set('path', $newPathFunction)
567
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
568
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
569
-
570
-				try {
571
-					$query->execute();
572
-				} catch (\OC\DatabaseException $e) {
573
-					$this->connection->rollBack();
574
-					throw $e;
575
-				}
576
-			}
577
-
578
-			$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
579
-			$this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId));
580
-			$this->connection->commit();
581
-		} else {
582
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
583
-		}
584
-	}
585
-
586
-	/**
587
-	 * remove all entries for files that are stored on the storage from the cache
588
-	 */
589
-	public function clear() {
590
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
591
-		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
592
-
593
-		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
594
-		$this->connection->executeQuery($sql, array($this->storageId));
595
-	}
596
-
597
-	/**
598
-	 * Get the scan status of a file
599
-	 *
600
-	 * - Cache::NOT_FOUND: File is not in the cache
601
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
602
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
603
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
604
-	 *
605
-	 * @param string $file
606
-	 *
607
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
608
-	 */
609
-	public function getStatus($file) {
610
-		// normalize file
611
-		$file = $this->normalize($file);
612
-
613
-		$pathHash = md5($file);
614
-		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
615
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
616
-		if ($row = $result->fetch()) {
617
-			if ((int)$row['size'] === -1) {
618
-				return self::SHALLOW;
619
-			} else {
620
-				return self::COMPLETE;
621
-			}
622
-		} else {
623
-			if (isset($this->partial[$file])) {
624
-				return self::PARTIAL;
625
-			} else {
626
-				return self::NOT_FOUND;
627
-			}
628
-		}
629
-	}
630
-
631
-	/**
632
-	 * search for files matching $pattern
633
-	 *
634
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
635
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
636
-	 */
637
-	public function search($pattern) {
638
-		// normalize pattern
639
-		$pattern = $this->normalize($pattern);
640
-
641
-		if ($pattern === '%%') {
642
-			return [];
643
-		}
644
-
645
-
646
-		$sql = '
215
+            $result = $this->connection->executeQuery($sql, [$fileId]);
216
+            $files = $result->fetchAll();
217
+            return array_map(function (array $data) {
218
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);
219
+            }, $files);
220
+        }
221
+        return [];
222
+    }
223
+
224
+    /**
225
+     * insert or update meta data for a file or folder
226
+     *
227
+     * @param string $file
228
+     * @param array $data
229
+     *
230
+     * @return int file id
231
+     * @throws \RuntimeException
232
+     */
233
+    public function put($file, array $data) {
234
+        if (($id = $this->getId($file)) > -1) {
235
+            $this->update($id, $data, $file);
236
+            return $id;
237
+        } else {
238
+            return $this->insert($file, $data);
239
+        }
240
+    }
241
+
242
+    /**
243
+     * insert meta data for a new file or folder
244
+     *
245
+     * @param string $file
246
+     * @param array $data
247
+     *
248
+     * @return int file id
249
+     * @throws \RuntimeException
250
+     *
251
+     * @suppress SqlInjectionChecker
252
+     */
253
+    public function insert($file, array $data) {
254
+        // normalize file
255
+        $file = $this->normalize($file);
256
+
257
+        if (isset($this->partial[$file])) { //add any saved partial data
258
+            $data = array_merge($this->partial[$file], $data);
259
+            unset($this->partial[$file]);
260
+        }
261
+
262
+        $requiredFields = array('size', 'mtime', 'mimetype');
263
+        foreach ($requiredFields as $field) {
264
+            if (!isset($data[$field])) { //data not complete save as partial and return
265
+                $this->partial[$file] = $data;
266
+                return -1;
267
+            }
268
+        }
269
+
270
+        $data['path'] = $file;
271
+        $data['parent'] = $this->getParentId($file);
272
+        $data['name'] = basename($file);
273
+
274
+        list($queryParts, $params) = $this->buildParts($data);
275
+        $queryParts[] = '`storage`';
276
+        $params[] = $this->getNumericStorageId();
277
+
278
+        $queryParts = array_map(function ($item) {
279
+            return trim($item, "`");
280
+        }, $queryParts);
281
+        $values = array_combine($queryParts, $params);
282
+
283
+        try {
284
+            $builder = $this->connection->getQueryBuilder();
285
+            $builder->insert('filecache');
286
+
287
+            foreach ($values as $column => $value) {
288
+                $builder->setValue($column, $builder->createNamedParameter($value));
289
+            }
290
+
291
+            if ($builder->execute()) {
292
+                $fileId = (int)$this->connection->lastInsertId('*PREFIX*filecache');
293
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
294
+                return $fileId;
295
+            }
296
+        } catch (UniqueConstraintViolationException $e) {
297
+            // entry exists already
298
+        }
299
+
300
+        // The file was created in the mean time
301
+        if (($id = $this->getId($file)) > -1) {
302
+            $this->update($id, $data);
303
+            return $id;
304
+        } else {
305
+            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.');
306
+        }
307
+    }
308
+
309
+    /**
310
+     * update the metadata of an existing file or folder in the cache
311
+     *
312
+     * @param int $id the fileid of the existing file or folder
313
+     * @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
314
+     */
315
+    public function update($id, array $data) {
316
+
317
+        if (isset($data['path'])) {
318
+            // normalize path
319
+            $data['path'] = $this->normalize($data['path']);
320
+        }
321
+
322
+        if (isset($data['name'])) {
323
+            // normalize path
324
+            $data['name'] = $this->normalize($data['name']);
325
+        }
326
+
327
+        list($queryParts, $params) = $this->buildParts($data);
328
+        // duplicate $params because we need the parts twice in the SQL statement
329
+        // once for the SET part, once in the WHERE clause
330
+        $params = array_merge($params, $params);
331
+        $params[] = $id;
332
+
333
+        // don't update if the data we try to set is the same as the one in the record
334
+        // some databases (Postgres) don't like superfluous updates
335
+        $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
336
+            'WHERE (' .
337
+            implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
338
+            implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
339
+            ') AND `fileid` = ? ';
340
+        $this->connection->executeQuery($sql, $params);
341
+
342
+        $path = $this->getPathById($id);
343
+        // path can still be null if the file doesn't exist
344
+        if ($path !== null) {
345
+            $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
346
+        }
347
+    }
348
+
349
+    /**
350
+     * extract query parts and params array from data array
351
+     *
352
+     * @param array $data
353
+     * @return array [$queryParts, $params]
354
+     *        $queryParts: string[], the (escaped) column names to be set in the query
355
+     *        $params: mixed[], the new values for the columns, to be passed as params to the query
356
+     */
357
+    protected function buildParts(array $data) {
358
+        $fields = array(
359
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
360
+            'etag', 'permissions', 'checksum', 'storage');
361
+
362
+        $doNotCopyStorageMTime = false;
363
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
364
+            // this horrific magic tells it to not copy storage_mtime to mtime
365
+            unset($data['mtime']);
366
+            $doNotCopyStorageMTime = true;
367
+        }
368
+
369
+        $params = array();
370
+        $queryParts = array();
371
+        foreach ($data as $name => $value) {
372
+            if (array_search($name, $fields) !== false) {
373
+                if ($name === 'path') {
374
+                    $params[] = md5($value);
375
+                    $queryParts[] = '`path_hash`';
376
+                } elseif ($name === 'mimetype') {
377
+                    $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
378
+                    $queryParts[] = '`mimepart`';
379
+                    $value = $this->mimetypeLoader->getId($value);
380
+                } elseif ($name === 'storage_mtime') {
381
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
382
+                        $params[] = $value;
383
+                        $queryParts[] = '`mtime`';
384
+                    }
385
+                } elseif ($name === 'encrypted') {
386
+                    if (isset($data['encryptedVersion'])) {
387
+                        $value = $data['encryptedVersion'];
388
+                    } else {
389
+                        // Boolean to integer conversion
390
+                        $value = $value ? 1 : 0;
391
+                    }
392
+                }
393
+                $params[] = $value;
394
+                $queryParts[] = '`' . $name . '`';
395
+            }
396
+        }
397
+        return array($queryParts, $params);
398
+    }
399
+
400
+    /**
401
+     * get the file id for a file
402
+     *
403
+     * 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
404
+     *
405
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
406
+     *
407
+     * @param string $file
408
+     * @return int
409
+     */
410
+    public function getId($file) {
411
+        // normalize file
412
+        $file = $this->normalize($file);
413
+
414
+        $pathHash = md5($file);
415
+
416
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
417
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
418
+        if ($row = $result->fetch()) {
419
+            return $row['fileid'];
420
+        } else {
421
+            return -1;
422
+        }
423
+    }
424
+
425
+    /**
426
+     * get the id of the parent folder of a file
427
+     *
428
+     * @param string $file
429
+     * @return int
430
+     */
431
+    public function getParentId($file) {
432
+        if ($file === '') {
433
+            return -1;
434
+        } else {
435
+            $parent = $this->getParentPath($file);
436
+            return (int)$this->getId($parent);
437
+        }
438
+    }
439
+
440
+    private function getParentPath($path) {
441
+        $parent = dirname($path);
442
+        if ($parent === '.') {
443
+            $parent = '';
444
+        }
445
+        return $parent;
446
+    }
447
+
448
+    /**
449
+     * check if a file is available in the cache
450
+     *
451
+     * @param string $file
452
+     * @return bool
453
+     */
454
+    public function inCache($file) {
455
+        return $this->getId($file) != -1;
456
+    }
457
+
458
+    /**
459
+     * remove a file or folder from the cache
460
+     *
461
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
462
+     *
463
+     * @param string $file
464
+     */
465
+    public function remove($file) {
466
+        $entry = $this->get($file);
467
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
468
+        $this->connection->executeQuery($sql, array($entry['fileid']));
469
+        if ($entry['mimetype'] === 'httpd/unix-directory') {
470
+            $this->removeChildren($entry);
471
+        }
472
+    }
473
+
474
+    /**
475
+     * Get all sub folders of a folder
476
+     *
477
+     * @param array $entry the cache entry of the folder to get the subfolders for
478
+     * @return array[] the cache entries for the subfolders
479
+     */
480
+    private function getSubFolders($entry) {
481
+        $children = $this->getFolderContentsById($entry['fileid']);
482
+        return array_filter($children, function ($child) {
483
+            return $child['mimetype'] === 'httpd/unix-directory';
484
+        });
485
+    }
486
+
487
+    /**
488
+     * Recursively remove all children of a folder
489
+     *
490
+     * @param array $entry the cache entry of the folder to remove the children of
491
+     * @throws \OC\DatabaseException
492
+     */
493
+    private function removeChildren($entry) {
494
+        $subFolders = $this->getSubFolders($entry);
495
+        foreach ($subFolders as $folder) {
496
+            $this->removeChildren($folder);
497
+        }
498
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
499
+        $this->connection->executeQuery($sql, array($entry['fileid']));
500
+    }
501
+
502
+    /**
503
+     * Move a file or folder in the cache
504
+     *
505
+     * @param string $source
506
+     * @param string $target
507
+     */
508
+    public function move($source, $target) {
509
+        $this->moveFromCache($this, $source, $target);
510
+    }
511
+
512
+    /**
513
+     * Get the storage id and path needed for a move
514
+     *
515
+     * @param string $path
516
+     * @return array [$storageId, $internalPath]
517
+     */
518
+    protected function getMoveInfo($path) {
519
+        return [$this->getNumericStorageId(), $path];
520
+    }
521
+
522
+    /**
523
+     * Move a file or folder in the cache
524
+     *
525
+     * @param \OCP\Files\Cache\ICache $sourceCache
526
+     * @param string $sourcePath
527
+     * @param string $targetPath
528
+     * @throws \OC\DatabaseException
529
+     * @throws \Exception if the given storages have an invalid id
530
+     * @suppress SqlInjectionChecker
531
+     */
532
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
533
+        if ($sourceCache instanceof Cache) {
534
+            // normalize source and target
535
+            $sourcePath = $this->normalize($sourcePath);
536
+            $targetPath = $this->normalize($targetPath);
537
+
538
+            $sourceData = $sourceCache->get($sourcePath);
539
+            $sourceId = $sourceData['fileid'];
540
+            $newParentId = $this->getParentId($targetPath);
541
+
542
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
543
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
544
+
545
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
546
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
547
+            }
548
+            if (is_null($targetStorageId) || $targetStorageId === false) {
549
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
550
+            }
551
+
552
+            $this->connection->beginTransaction();
553
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
554
+                //update all child entries
555
+                $sourceLength = mb_strlen($sourcePath);
556
+                $query = $this->connection->getQueryBuilder();
557
+
558
+                $fun = $query->func();
559
+                $newPathFunction = $fun->concat(
560
+                    $query->createNamedParameter($targetPath),
561
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
562
+                );
563
+                $query->update('filecache')
564
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
565
+                    ->set('path_hash', $fun->md5($newPathFunction))
566
+                    ->set('path', $newPathFunction)
567
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
568
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
569
+
570
+                try {
571
+                    $query->execute();
572
+                } catch (\OC\DatabaseException $e) {
573
+                    $this->connection->rollBack();
574
+                    throw $e;
575
+                }
576
+            }
577
+
578
+            $sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
579
+            $this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId));
580
+            $this->connection->commit();
581
+        } else {
582
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
583
+        }
584
+    }
585
+
586
+    /**
587
+     * remove all entries for files that are stored on the storage from the cache
588
+     */
589
+    public function clear() {
590
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
591
+        $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
592
+
593
+        $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
594
+        $this->connection->executeQuery($sql, array($this->storageId));
595
+    }
596
+
597
+    /**
598
+     * Get the scan status of a file
599
+     *
600
+     * - Cache::NOT_FOUND: File is not in the cache
601
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
602
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
603
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
604
+     *
605
+     * @param string $file
606
+     *
607
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
608
+     */
609
+    public function getStatus($file) {
610
+        // normalize file
611
+        $file = $this->normalize($file);
612
+
613
+        $pathHash = md5($file);
614
+        $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
615
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
616
+        if ($row = $result->fetch()) {
617
+            if ((int)$row['size'] === -1) {
618
+                return self::SHALLOW;
619
+            } else {
620
+                return self::COMPLETE;
621
+            }
622
+        } else {
623
+            if (isset($this->partial[$file])) {
624
+                return self::PARTIAL;
625
+            } else {
626
+                return self::NOT_FOUND;
627
+            }
628
+        }
629
+    }
630
+
631
+    /**
632
+     * search for files matching $pattern
633
+     *
634
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
635
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
636
+     */
637
+    public function search($pattern) {
638
+        // normalize pattern
639
+        $pattern = $this->normalize($pattern);
640
+
641
+        if ($pattern === '%%') {
642
+            return [];
643
+        }
644
+
645
+
646
+        $sql = '
647 647
 			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
648 648
 				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
649 649
 				 `encrypted`, `etag`, `permissions`, `checksum`
650 650
 			FROM `*PREFIX*filecache`
651 651
 			WHERE `storage` = ? AND `name` ILIKE ?';
652
-		$result = $this->connection->executeQuery($sql,
653
-			[$this->getNumericStorageId(), $pattern]
654
-		);
655
-
656
-		return $this->searchResultToCacheEntries($result);
657
-	}
658
-
659
-	/**
660
-	 * @param Statement $result
661
-	 * @return CacheEntry[]
662
-	 */
663
-	private function searchResultToCacheEntries(Statement $result) {
664
-		$files = $result->fetchAll();
665
-
666
-		return array_map(function (array $data) {
667
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
668
-		}, $files);
669
-	}
670
-
671
-	/**
672
-	 * search for files by mimetype
673
-	 *
674
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
675
-	 *        where it will search for all mimetypes in the group ('image/*')
676
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
677
-	 */
678
-	public function searchByMime($mimetype) {
679
-		if (strpos($mimetype, '/')) {
680
-			$where = '`mimetype` = ?';
681
-		} else {
682
-			$where = '`mimepart` = ?';
683
-		}
684
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
652
+        $result = $this->connection->executeQuery($sql,
653
+            [$this->getNumericStorageId(), $pattern]
654
+        );
655
+
656
+        return $this->searchResultToCacheEntries($result);
657
+    }
658
+
659
+    /**
660
+     * @param Statement $result
661
+     * @return CacheEntry[]
662
+     */
663
+    private function searchResultToCacheEntries(Statement $result) {
664
+        $files = $result->fetchAll();
665
+
666
+        return array_map(function (array $data) {
667
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
668
+        }, $files);
669
+    }
670
+
671
+    /**
672
+     * search for files by mimetype
673
+     *
674
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
675
+     *        where it will search for all mimetypes in the group ('image/*')
676
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
677
+     */
678
+    public function searchByMime($mimetype) {
679
+        if (strpos($mimetype, '/')) {
680
+            $where = '`mimetype` = ?';
681
+        } else {
682
+            $where = '`mimepart` = ?';
683
+        }
684
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
685 685
 				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
686
-		$mimetype = $this->mimetypeLoader->getId($mimetype);
687
-		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
688
-
689
-		return $this->searchResultToCacheEntries($result);
690
-	}
691
-
692
-	public function searchQuery(ISearchQuery $searchQuery) {
693
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
694
-
695
-		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
696
-			->from('filecache', 'file');
697
-
698
-		$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
699
-
700
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
701
-			$query
702
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
703
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
704
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
705
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
706
-				))
707
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
708
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
709
-		}
710
-
711
-		$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
712
-
713
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
714
-
715
-		if ($searchQuery->getLimit()) {
716
-			$query->setMaxResults($searchQuery->getLimit());
717
-		}
718
-		if ($searchQuery->getOffset()) {
719
-			$query->setFirstResult($searchQuery->getOffset());
720
-		}
721
-
722
-		$result = $query->execute();
723
-		return $this->searchResultToCacheEntries($result);
724
-	}
725
-
726
-	/**
727
-	 * Search for files by tag of a given users.
728
-	 *
729
-	 * Note that every user can tag files differently.
730
-	 *
731
-	 * @param string|int $tag name or tag id
732
-	 * @param string $userId owner of the tags
733
-	 * @return ICacheEntry[] file data
734
-	 */
735
-	public function searchByTag($tag, $userId) {
736
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
737
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
738
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
739
-			'FROM `*PREFIX*filecache` `file`, ' .
740
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
741
-			'`*PREFIX*vcategory` `tag` ' .
742
-			// JOIN filecache to vcategory_to_object
743
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
744
-			// JOIN vcategory_to_object to vcategory
745
-			'AND `tagmap`.`type` = `tag`.`type` ' .
746
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
747
-			// conditions
748
-			'AND `file`.`storage` = ? ' .
749
-			'AND `tag`.`type` = \'files\' ' .
750
-			'AND `tag`.`uid` = ? ';
751
-		if (is_int($tag)) {
752
-			$sql .= 'AND `tag`.`id` = ? ';
753
-		} else {
754
-			$sql .= 'AND `tag`.`category` = ? ';
755
-		}
756
-		$result = $this->connection->executeQuery(
757
-			$sql,
758
-			[
759
-				$this->getNumericStorageId(),
760
-				$userId,
761
-				$tag
762
-			]
763
-		);
764
-
765
-		$files = $result->fetchAll();
766
-
767
-		return array_map(function (array $data) {
768
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
769
-		}, $files);
770
-	}
771
-
772
-	/**
773
-	 * Re-calculate the folder size and the size of all parent folders
774
-	 *
775
-	 * @param string|boolean $path
776
-	 * @param array $data (optional) meta data of the folder
777
-	 */
778
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
779
-		$this->calculateFolderSize($path, $data);
780
-		if ($path !== '') {
781
-			$parent = dirname($path);
782
-			if ($parent === '.' or $parent === '/') {
783
-				$parent = '';
784
-			}
785
-			if ($isBackgroundScan) {
786
-				$parentData = $this->get($parent);
787
-				if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
788
-					$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
789
-				}
790
-			} else {
791
-				$this->correctFolderSize($parent);
792
-			}
793
-		}
794
-	}
795
-
796
-	/**
797
-	 * get the incomplete count that shares parent $folder
798
-	 *
799
-	 * @param int $fileId the file id of the folder
800
-	 * @return int
801
-	 */
802
-	public function getIncompleteChildrenCount($fileId) {
803
-		if ($fileId > -1) {
804
-			$sql = 'SELECT count(*)
686
+        $mimetype = $this->mimetypeLoader->getId($mimetype);
687
+        $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
688
+
689
+        return $this->searchResultToCacheEntries($result);
690
+    }
691
+
692
+    public function searchQuery(ISearchQuery $searchQuery) {
693
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
694
+
695
+        $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
696
+            ->from('filecache', 'file');
697
+
698
+        $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
699
+
700
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
701
+            $query
702
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
703
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
704
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
705
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
706
+                ))
707
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
708
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
709
+        }
710
+
711
+        $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
712
+
713
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
714
+
715
+        if ($searchQuery->getLimit()) {
716
+            $query->setMaxResults($searchQuery->getLimit());
717
+        }
718
+        if ($searchQuery->getOffset()) {
719
+            $query->setFirstResult($searchQuery->getOffset());
720
+        }
721
+
722
+        $result = $query->execute();
723
+        return $this->searchResultToCacheEntries($result);
724
+    }
725
+
726
+    /**
727
+     * Search for files by tag of a given users.
728
+     *
729
+     * Note that every user can tag files differently.
730
+     *
731
+     * @param string|int $tag name or tag id
732
+     * @param string $userId owner of the tags
733
+     * @return ICacheEntry[] file data
734
+     */
735
+    public function searchByTag($tag, $userId) {
736
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
737
+            '`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
738
+            '`encrypted`, `etag`, `permissions`, `checksum` ' .
739
+            'FROM `*PREFIX*filecache` `file`, ' .
740
+            '`*PREFIX*vcategory_to_object` `tagmap`, ' .
741
+            '`*PREFIX*vcategory` `tag` ' .
742
+            // JOIN filecache to vcategory_to_object
743
+            'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
744
+            // JOIN vcategory_to_object to vcategory
745
+            'AND `tagmap`.`type` = `tag`.`type` ' .
746
+            'AND `tagmap`.`categoryid` = `tag`.`id` ' .
747
+            // conditions
748
+            'AND `file`.`storage` = ? ' .
749
+            'AND `tag`.`type` = \'files\' ' .
750
+            'AND `tag`.`uid` = ? ';
751
+        if (is_int($tag)) {
752
+            $sql .= 'AND `tag`.`id` = ? ';
753
+        } else {
754
+            $sql .= 'AND `tag`.`category` = ? ';
755
+        }
756
+        $result = $this->connection->executeQuery(
757
+            $sql,
758
+            [
759
+                $this->getNumericStorageId(),
760
+                $userId,
761
+                $tag
762
+            ]
763
+        );
764
+
765
+        $files = $result->fetchAll();
766
+
767
+        return array_map(function (array $data) {
768
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
769
+        }, $files);
770
+    }
771
+
772
+    /**
773
+     * Re-calculate the folder size and the size of all parent folders
774
+     *
775
+     * @param string|boolean $path
776
+     * @param array $data (optional) meta data of the folder
777
+     */
778
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
779
+        $this->calculateFolderSize($path, $data);
780
+        if ($path !== '') {
781
+            $parent = dirname($path);
782
+            if ($parent === '.' or $parent === '/') {
783
+                $parent = '';
784
+            }
785
+            if ($isBackgroundScan) {
786
+                $parentData = $this->get($parent);
787
+                if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
788
+                    $this->correctFolderSize($parent, $parentData, $isBackgroundScan);
789
+                }
790
+            } else {
791
+                $this->correctFolderSize($parent);
792
+            }
793
+        }
794
+    }
795
+
796
+    /**
797
+     * get the incomplete count that shares parent $folder
798
+     *
799
+     * @param int $fileId the file id of the folder
800
+     * @return int
801
+     */
802
+    public function getIncompleteChildrenCount($fileId) {
803
+        if ($fileId > -1) {
804
+            $sql = 'SELECT count(*)
805 805
 					FROM `*PREFIX*filecache` WHERE `parent` = ? AND size = -1';
806
-			$result = $this->connection->executeQuery($sql, [$fileId]);
807
-			return (int)$result->fetchColumn();
808
-		}
809
-		return -1;
810
-	}
811
-
812
-	/**
813
-	 * calculate the size of a folder and set it in the cache
814
-	 *
815
-	 * @param string $path
816
-	 * @param array $entry (optional) meta data of the folder
817
-	 * @return int
818
-	 */
819
-	public function calculateFolderSize($path, $entry = null) {
820
-		$totalSize = 0;
821
-		if (is_null($entry) or !isset($entry['fileid'])) {
822
-			$entry = $this->get($path);
823
-		}
824
-		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
825
-			$id = $entry['fileid'];
826
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
827
-				'FROM `*PREFIX*filecache` ' .
828
-				'WHERE `parent` = ? AND `storage` = ?';
829
-			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
830
-			if ($row = $result->fetch()) {
831
-				$result->closeCursor();
832
-				list($sum, $min) = array_values($row);
833
-				$sum = 0 + $sum;
834
-				$min = 0 + $min;
835
-				if ($min === -1) {
836
-					$totalSize = $min;
837
-				} else {
838
-					$totalSize = $sum;
839
-				}
840
-				$update = array();
841
-				if ($entry['size'] !== $totalSize) {
842
-					$update['size'] = $totalSize;
843
-				}
844
-				if (count($update) > 0) {
845
-					$this->update($id, $update);
846
-				}
847
-			} else {
848
-				$result->closeCursor();
849
-			}
850
-		}
851
-		return $totalSize;
852
-	}
853
-
854
-	/**
855
-	 * get all file ids on the files on the storage
856
-	 *
857
-	 * @return int[]
858
-	 */
859
-	public function getAll() {
860
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
861
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
862
-		$ids = array();
863
-		while ($row = $result->fetch()) {
864
-			$ids[] = $row['fileid'];
865
-		}
866
-		return $ids;
867
-	}
868
-
869
-	/**
870
-	 * find a folder in the cache which has not been fully scanned
871
-	 *
872
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
873
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
874
-	 * likely the folder where we stopped scanning previously
875
-	 *
876
-	 * @return string|bool the path of the folder or false when no folder matched
877
-	 */
878
-	public function getIncomplete() {
879
-		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
880
-			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
881
-		$query->execute([$this->getNumericStorageId()]);
882
-		if ($row = $query->fetch()) {
883
-			return $row['path'];
884
-		} else {
885
-			return false;
886
-		}
887
-	}
888
-
889
-	/**
890
-	 * get the path of a file on this storage by it's file id
891
-	 *
892
-	 * @param int $id the file id of the file or folder to search
893
-	 * @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
894
-	 */
895
-	public function getPathById($id) {
896
-		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
897
-		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
898
-		if ($row = $result->fetch()) {
899
-			// Oracle stores empty strings as null...
900
-			if ($row['path'] === null) {
901
-				return '';
902
-			}
903
-			return $row['path'];
904
-		} else {
905
-			return null;
906
-		}
907
-	}
908
-
909
-	/**
910
-	 * get the storage id of the storage for a file and the internal path of the file
911
-	 * unlike getPathById this does not limit the search to files on this storage and
912
-	 * instead does a global search in the cache table
913
-	 *
914
-	 * @param int $id
915
-	 * @deprecated use getPathById() instead
916
-	 * @return array first element holding the storage id, second the path
917
-	 */
918
-	static public function getById($id) {
919
-		$connection = \OC::$server->getDatabaseConnection();
920
-		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
921
-		$result = $connection->executeQuery($sql, array($id));
922
-		if ($row = $result->fetch()) {
923
-			$numericId = $row['storage'];
924
-			$path = $row['path'];
925
-		} else {
926
-			return null;
927
-		}
928
-
929
-		if ($id = Storage::getStorageId($numericId)) {
930
-			return array($id, $path);
931
-		} else {
932
-			return null;
933
-		}
934
-	}
935
-
936
-	/**
937
-	 * normalize the given path
938
-	 *
939
-	 * @param string $path
940
-	 * @return string
941
-	 */
942
-	public function normalize($path) {
943
-
944
-		return trim(\OC_Util::normalizeUnicode($path), '/');
945
-	}
806
+            $result = $this->connection->executeQuery($sql, [$fileId]);
807
+            return (int)$result->fetchColumn();
808
+        }
809
+        return -1;
810
+    }
811
+
812
+    /**
813
+     * calculate the size of a folder and set it in the cache
814
+     *
815
+     * @param string $path
816
+     * @param array $entry (optional) meta data of the folder
817
+     * @return int
818
+     */
819
+    public function calculateFolderSize($path, $entry = null) {
820
+        $totalSize = 0;
821
+        if (is_null($entry) or !isset($entry['fileid'])) {
822
+            $entry = $this->get($path);
823
+        }
824
+        if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
825
+            $id = $entry['fileid'];
826
+            $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
827
+                'FROM `*PREFIX*filecache` ' .
828
+                'WHERE `parent` = ? AND `storage` = ?';
829
+            $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
830
+            if ($row = $result->fetch()) {
831
+                $result->closeCursor();
832
+                list($sum, $min) = array_values($row);
833
+                $sum = 0 + $sum;
834
+                $min = 0 + $min;
835
+                if ($min === -1) {
836
+                    $totalSize = $min;
837
+                } else {
838
+                    $totalSize = $sum;
839
+                }
840
+                $update = array();
841
+                if ($entry['size'] !== $totalSize) {
842
+                    $update['size'] = $totalSize;
843
+                }
844
+                if (count($update) > 0) {
845
+                    $this->update($id, $update);
846
+                }
847
+            } else {
848
+                $result->closeCursor();
849
+            }
850
+        }
851
+        return $totalSize;
852
+    }
853
+
854
+    /**
855
+     * get all file ids on the files on the storage
856
+     *
857
+     * @return int[]
858
+     */
859
+    public function getAll() {
860
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
861
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
862
+        $ids = array();
863
+        while ($row = $result->fetch()) {
864
+            $ids[] = $row['fileid'];
865
+        }
866
+        return $ids;
867
+    }
868
+
869
+    /**
870
+     * find a folder in the cache which has not been fully scanned
871
+     *
872
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
873
+     * use the one with the highest id gives the best result with the background scanner, since that is most
874
+     * likely the folder where we stopped scanning previously
875
+     *
876
+     * @return string|bool the path of the folder or false when no folder matched
877
+     */
878
+    public function getIncomplete() {
879
+        $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
880
+            . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
881
+        $query->execute([$this->getNumericStorageId()]);
882
+        if ($row = $query->fetch()) {
883
+            return $row['path'];
884
+        } else {
885
+            return false;
886
+        }
887
+    }
888
+
889
+    /**
890
+     * get the path of a file on this storage by it's file id
891
+     *
892
+     * @param int $id the file id of the file or folder to search
893
+     * @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
894
+     */
895
+    public function getPathById($id) {
896
+        $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
897
+        $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
898
+        if ($row = $result->fetch()) {
899
+            // Oracle stores empty strings as null...
900
+            if ($row['path'] === null) {
901
+                return '';
902
+            }
903
+            return $row['path'];
904
+        } else {
905
+            return null;
906
+        }
907
+    }
908
+
909
+    /**
910
+     * get the storage id of the storage for a file and the internal path of the file
911
+     * unlike getPathById this does not limit the search to files on this storage and
912
+     * instead does a global search in the cache table
913
+     *
914
+     * @param int $id
915
+     * @deprecated use getPathById() instead
916
+     * @return array first element holding the storage id, second the path
917
+     */
918
+    static public function getById($id) {
919
+        $connection = \OC::$server->getDatabaseConnection();
920
+        $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
921
+        $result = $connection->executeQuery($sql, array($id));
922
+        if ($row = $result->fetch()) {
923
+            $numericId = $row['storage'];
924
+            $path = $row['path'];
925
+        } else {
926
+            return null;
927
+        }
928
+
929
+        if ($id = Storage::getStorageId($numericId)) {
930
+            return array($id, $path);
931
+        } else {
932
+            return null;
933
+        }
934
+    }
935
+
936
+    /**
937
+     * normalize the given path
938
+     *
939
+     * @param string $path
940
+     * @return string
941
+     */
942
+    public function normalize($path) {
943
+
944
+        return trim(\OC_Util::normalizeUnicode($path), '/');
945
+    }
946 946
 }
947 947
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -172,21 +172,21 @@  discard block
 block discarded – undo
172 172
 	 */
173 173
 	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
174 174
 		//fix types
175
-		$data['fileid'] = (int)$data['fileid'];
176
-		$data['parent'] = (int)$data['parent'];
175
+		$data['fileid'] = (int) $data['fileid'];
176
+		$data['parent'] = (int) $data['parent'];
177 177
 		$data['size'] = 0 + $data['size'];
178
-		$data['mtime'] = (int)$data['mtime'];
179
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
180
-		$data['encryptedVersion'] = (int)$data['encrypted'];
181
-		$data['encrypted'] = (bool)$data['encrypted'];
178
+		$data['mtime'] = (int) $data['mtime'];
179
+		$data['storage_mtime'] = (int) $data['storage_mtime'];
180
+		$data['encryptedVersion'] = (int) $data['encrypted'];
181
+		$data['encrypted'] = (bool) $data['encrypted'];
182 182
 		$data['storage_id'] = $data['storage'];
183
-		$data['storage'] = (int)$data['storage'];
183
+		$data['storage'] = (int) $data['storage'];
184 184
 		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
185 185
 		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
186 186
 		if ($data['storage_mtime'] == 0) {
187 187
 			$data['storage_mtime'] = $data['mtime'];
188 188
 		}
189
-		$data['permissions'] = (int)$data['permissions'];
189
+		$data['permissions'] = (int) $data['permissions'];
190 190
 		return new CacheEntry($data);
191 191
 	}
192 192
 
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
215 215
 			$result = $this->connection->executeQuery($sql, [$fileId]);
216 216
 			$files = $result->fetchAll();
217
-			return array_map(function (array $data) {
217
+			return array_map(function(array $data) {
218 218
 				return self::cacheEntryFromData($data, $this->mimetypeLoader);
219 219
 			}, $files);
220 220
 		}
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
 		$queryParts[] = '`storage`';
276 276
 		$params[] = $this->getNumericStorageId();
277 277
 
278
-		$queryParts = array_map(function ($item) {
278
+		$queryParts = array_map(function($item) {
279 279
 			return trim($item, "`");
280 280
 		}, $queryParts);
281 281
 		$values = array_combine($queryParts, $params);
@@ -289,7 +289,7 @@  discard block
 block discarded – undo
289 289
 			}
290 290
 
291 291
 			if ($builder->execute()) {
292
-				$fileId = (int)$this->connection->lastInsertId('*PREFIX*filecache');
292
+				$fileId = (int) $this->connection->lastInsertId('*PREFIX*filecache');
293 293
 				$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
294 294
 				return $fileId;
295 295
 			}
@@ -332,10 +332,10 @@  discard block
 block discarded – undo
332 332
 
333 333
 		// don't update if the data we try to set is the same as the one in the record
334 334
 		// some databases (Postgres) don't like superfluous updates
335
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
336
-			'WHERE (' .
337
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
338
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
335
+		$sql = 'UPDATE `*PREFIX*filecache` SET '.implode(' = ?, ', $queryParts).'=? '.
336
+			'WHERE ('.
337
+			implode(' <> ? OR ', $queryParts).' <> ? OR '.
338
+			implode(' IS NULL OR ', $queryParts).' IS NULL'.
339 339
 			') AND `fileid` = ? ';
340 340
 		$this->connection->executeQuery($sql, $params);
341 341
 
@@ -391,7 +391,7 @@  discard block
 block discarded – undo
391 391
 					}
392 392
 				}
393 393
 				$params[] = $value;
394
-				$queryParts[] = '`' . $name . '`';
394
+				$queryParts[] = '`'.$name.'`';
395 395
 			}
396 396
 		}
397 397
 		return array($queryParts, $params);
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
 			return -1;
434 434
 		} else {
435 435
 			$parent = $this->getParentPath($file);
436
-			return (int)$this->getId($parent);
436
+			return (int) $this->getId($parent);
437 437
 		}
438 438
 	}
439 439
 
@@ -479,7 +479,7 @@  discard block
 block discarded – undo
479 479
 	 */
480 480
 	private function getSubFolders($entry) {
481 481
 		$children = $this->getFolderContentsById($entry['fileid']);
482
-		return array_filter($children, function ($child) {
482
+		return array_filter($children, function($child) {
483 483
 			return $child['mimetype'] === 'httpd/unix-directory';
484 484
 		});
485 485
 	}
@@ -543,10 +543,10 @@  discard block
 block discarded – undo
543 543
 			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
544 544
 
545 545
 			if (is_null($sourceStorageId) || $sourceStorageId === false) {
546
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
546
+				throw new \Exception('Invalid source storage id: '.$sourceStorageId);
547 547
 			}
548 548
 			if (is_null($targetStorageId) || $targetStorageId === false) {
549
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
549
+				throw new \Exception('Invalid target storage id: '.$targetStorageId);
550 550
 			}
551 551
 
552 552
 			$this->connection->beginTransaction();
@@ -565,7 +565,7 @@  discard block
 block discarded – undo
565 565
 					->set('path_hash', $fun->md5($newPathFunction))
566 566
 					->set('path', $newPathFunction)
567 567
 					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
568
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
568
+					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath).'/%')));
569 569
 
570 570
 				try {
571 571
 					$query->execute();
@@ -614,7 +614,7 @@  discard block
 block discarded – undo
614 614
 		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
615 615
 		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
616 616
 		if ($row = $result->fetch()) {
617
-			if ((int)$row['size'] === -1) {
617
+			if ((int) $row['size'] === -1) {
618 618
 				return self::SHALLOW;
619 619
 			} else {
620 620
 				return self::COMPLETE;
@@ -663,7 +663,7 @@  discard block
 block discarded – undo
663 663
 	private function searchResultToCacheEntries(Statement $result) {
664 664
 		$files = $result->fetchAll();
665 665
 
666
-		return array_map(function (array $data) {
666
+		return array_map(function(array $data) {
667 667
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
668 668
 		}, $files);
669 669
 	}
@@ -682,7 +682,7 @@  discard block
 block discarded – undo
682 682
 			$where = '`mimepart` = ?';
683 683
 		}
684 684
 		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
685
-				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
685
+				FROM `*PREFIX*filecache` WHERE ' . $where.' AND `storage` = ?';
686 686
 		$mimetype = $this->mimetypeLoader->getId($mimetype);
687 687
 		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
688 688
 
@@ -733,20 +733,20 @@  discard block
 block discarded – undo
733 733
 	 * @return ICacheEntry[] file data
734 734
 	 */
735 735
 	public function searchByTag($tag, $userId) {
736
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
737
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
738
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
739
-			'FROM `*PREFIX*filecache` `file`, ' .
740
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
741
-			'`*PREFIX*vcategory` `tag` ' .
736
+		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, '.
737
+			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, '.
738
+			'`encrypted`, `etag`, `permissions`, `checksum` '.
739
+			'FROM `*PREFIX*filecache` `file`, '.
740
+			'`*PREFIX*vcategory_to_object` `tagmap`, '.
741
+			'`*PREFIX*vcategory` `tag` '.
742 742
 			// JOIN filecache to vcategory_to_object
743
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
743
+			'WHERE `file`.`fileid` = `tagmap`.`objid` '.
744 744
 			// JOIN vcategory_to_object to vcategory
745
-			'AND `tagmap`.`type` = `tag`.`type` ' .
746
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
745
+			'AND `tagmap`.`type` = `tag`.`type` '.
746
+			'AND `tagmap`.`categoryid` = `tag`.`id` '.
747 747
 			// conditions
748
-			'AND `file`.`storage` = ? ' .
749
-			'AND `tag`.`type` = \'files\' ' .
748
+			'AND `file`.`storage` = ? '.
749
+			'AND `tag`.`type` = \'files\' '.
750 750
 			'AND `tag`.`uid` = ? ';
751 751
 		if (is_int($tag)) {
752 752
 			$sql .= 'AND `tag`.`id` = ? ';
@@ -764,7 +764,7 @@  discard block
 block discarded – undo
764 764
 
765 765
 		$files = $result->fetchAll();
766 766
 
767
-		return array_map(function (array $data) {
767
+		return array_map(function(array $data) {
768 768
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
769 769
 		}, $files);
770 770
 	}
@@ -804,7 +804,7 @@  discard block
 block discarded – undo
804 804
 			$sql = 'SELECT count(*)
805 805
 					FROM `*PREFIX*filecache` WHERE `parent` = ? AND size = -1';
806 806
 			$result = $this->connection->executeQuery($sql, [$fileId]);
807
-			return (int)$result->fetchColumn();
807
+			return (int) $result->fetchColumn();
808 808
 		}
809 809
 		return -1;
810 810
 	}
@@ -823,8 +823,8 @@  discard block
 block discarded – undo
823 823
 		}
824 824
 		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
825 825
 			$id = $entry['fileid'];
826
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
827
-				'FROM `*PREFIX*filecache` ' .
826
+			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 '.
827
+				'FROM `*PREFIX*filecache` '.
828 828
 				'WHERE `parent` = ? AND `storage` = ?';
829 829
 			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
830 830
 			if ($row = $result->fetch()) {
Please login to merge, or discard this patch.