Completed
Pull Request — master (#5785)
by Morris
14:53
created
lib/private/Repair/NC13/RepairInvalidPaths.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
 		$count = 0;
148 148
 		foreach ($entries as $entry) {
149 149
 			$count++;
150
-			$calculatedPath = $entry['parent_path'] . '/' . $entry['name'];
150
+			$calculatedPath = $entry['parent_path'].'/'.$entry['name'];
151 151
 			if ($newId = $this->getId($entry['parent_storage'], $calculatedPath)) {
152 152
 				// a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
153 153
 				$this->reparent($entry['fileid'], $newId);
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 		if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) {
167 167
 			$count = $this->repair();
168 168
 
169
-			$output->info('Repaired ' . $count . ' paths');
169
+			$output->info('Repaired '.$count.' paths');
170 170
 		}
171 171
 	}
172 172
 }
Please login to merge, or discard this patch.
Indentation   +150 added lines, -150 removed lines patch added patch discarded remove patch
@@ -29,154 +29,154 @@
 block discarded – undo
29 29
 use OCP\Migration\IRepairStep;
30 30
 
31 31
 class RepairInvalidPaths implements IRepairStep {
32
-	const MAX_ROWS = 1000;
33
-
34
-	/** @var IDBConnection */
35
-	private $connection;
36
-	/** @var IConfig */
37
-	private $config;
38
-
39
-	private $getIdQuery;
40
-	private $updateQuery;
41
-	private $reparentQuery;
42
-	private $deleteQuery;
43
-
44
-	public function __construct(IDBConnection $connection, IConfig $config) {
45
-		$this->connection = $connection;
46
-		$this->config = $config;
47
-	}
48
-
49
-
50
-	public function getName() {
51
-		return 'Repair invalid paths in file cache';
52
-	}
53
-
54
-	/**
55
-	 * @return \Generator
56
-	 * @suppress SqlInjectionChecker
57
-	 */
58
-	private function getInvalidEntries() {
59
-		$builder = $this->connection->getQueryBuilder();
60
-
61
-		$computedPath = $builder->func()->concat(
62
-			'p.path',
63
-			$builder->func()->concat($builder->createNamedParameter('/'), 'f.name')
64
-		);
65
-
66
-		//select f.path, f.parent,p.path from oc_filecache f inner join oc_filecache p on f.parent=p.fileid and p.path!='' where f.path != p.path || '/' || f.name;
67
-		$query = $builder->select('f.fileid', 'f.path', 'p.path AS parent_path', 'f.name', 'f.parent', 'f.storage', 'p.storage as parent_storage')
68
-			->from('filecache', 'f')
69
-			->innerJoin('f', 'filecache', 'p', $builder->expr()->andX(
70
-				$builder->expr()->eq('f.parent', 'p.fileid'),
71
-				$builder->expr()->neq('p.name', $builder->createNamedParameter(''))
72
-			))
73
-			->where($builder->expr()->neq('f.path', $computedPath))
74
-			->setMaxResults(self::MAX_ROWS);
75
-
76
-		do {
77
-			$result = $query->execute();
78
-			$rows = $result->fetchAll();
79
-			foreach ($rows as $row) {
80
-				yield $row;
81
-			}
82
-			$result->closeCursor();
83
-		} while (count($rows) > 0);
84
-	}
85
-
86
-	private function getId($storage, $path) {
87
-		if (!$this->getIdQuery) {
88
-			$builder = $this->connection->getQueryBuilder();
89
-
90
-			$this->getIdQuery = $builder->select('fileid')
91
-				->from('filecache')
92
-				->where($builder->expr()->eq('storage', $builder->createParameter('storage')))
93
-				->andWhere($builder->expr()->eq('path', $builder->createParameter('path')));
94
-		}
95
-
96
-		$this->getIdQuery->setParameter('storage', $storage, IQueryBuilder::PARAM_INT);
97
-		$this->getIdQuery->setParameter('path', $path);
98
-
99
-		return $this->getIdQuery->execute()->fetchColumn();
100
-	}
101
-
102
-	/**
103
-	 * @param string $fileid
104
-	 * @param string $newPath
105
-	 * @param string $newStorage
106
-	 * @suppress SqlInjectionChecker
107
-	 */
108
-	private function update($fileid, $newPath, $newStorage) {
109
-		if (!$this->updateQuery) {
110
-			$builder = $this->connection->getQueryBuilder();
111
-
112
-			$this->updateQuery = $builder->update('filecache')
113
-				->set('path', $builder->createParameter('newpath'))
114
-				->set('path_hash', $builder->func()->md5($builder->createParameter('newpath')))
115
-				->set('storage', $builder->createParameter('newstorage'))
116
-				->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
117
-		}
118
-
119
-		$this->updateQuery->setParameter('newpath', $newPath);
120
-		$this->updateQuery->setParameter('newstorage', $newStorage);
121
-		$this->updateQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
122
-
123
-		$this->updateQuery->execute();
124
-	}
125
-
126
-	private function reparent($from, $to) {
127
-		if (!$this->reparentQuery) {
128
-			$builder = $this->connection->getQueryBuilder();
129
-
130
-			$this->reparentQuery = $builder->update('filecache')
131
-				->set('parent', $builder->createParameter('to'))
132
-				->where($builder->expr()->eq('fileid', $builder->createParameter('from')));
133
-		}
134
-
135
-		$this->reparentQuery->setParameter('from', $from);
136
-		$this->reparentQuery->setParameter('to', $to);
137
-
138
-		$this->reparentQuery->execute();
139
-	}
140
-
141
-	private function delete($fileid) {
142
-		if (!$this->deleteQuery) {
143
-			$builder = $this->connection->getQueryBuilder();
144
-
145
-			$this->deleteQuery = $builder->delete('filecache')
146
-				->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
147
-		}
148
-
149
-		$this->deleteQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
150
-
151
-		$this->deleteQuery->execute();
152
-	}
153
-
154
-	private function repair() {
155
-		$this->connection->beginTransaction();
156
-		$entries = $this->getInvalidEntries();
157
-		$count = 0;
158
-		foreach ($entries as $entry) {
159
-			$count++;
160
-			$calculatedPath = $entry['parent_path'] . '/' . $entry['name'];
161
-			if ($newId = $this->getId($entry['parent_storage'], $calculatedPath)) {
162
-				// a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
163
-				$this->reparent($entry['fileid'], $newId);
164
-				$this->delete($entry['fileid']);
165
-			} else {
166
-				$this->update($entry['fileid'], $calculatedPath, $entry['parent_storage']);
167
-			}
168
-		}
169
-		$this->connection->commit();
170
-		return $count;
171
-	}
172
-
173
-	public function run(IOutput $output) {
174
-		$versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
175
-		// was added to 12.0.0.30 and 13.0.0.1
176
-		if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) {
177
-			$count = $this->repair();
178
-
179
-			$output->info('Repaired ' . $count . ' paths');
180
-		}
181
-	}
32
+    const MAX_ROWS = 1000;
33
+
34
+    /** @var IDBConnection */
35
+    private $connection;
36
+    /** @var IConfig */
37
+    private $config;
38
+
39
+    private $getIdQuery;
40
+    private $updateQuery;
41
+    private $reparentQuery;
42
+    private $deleteQuery;
43
+
44
+    public function __construct(IDBConnection $connection, IConfig $config) {
45
+        $this->connection = $connection;
46
+        $this->config = $config;
47
+    }
48
+
49
+
50
+    public function getName() {
51
+        return 'Repair invalid paths in file cache';
52
+    }
53
+
54
+    /**
55
+     * @return \Generator
56
+     * @suppress SqlInjectionChecker
57
+     */
58
+    private function getInvalidEntries() {
59
+        $builder = $this->connection->getQueryBuilder();
60
+
61
+        $computedPath = $builder->func()->concat(
62
+            'p.path',
63
+            $builder->func()->concat($builder->createNamedParameter('/'), 'f.name')
64
+        );
65
+
66
+        //select f.path, f.parent,p.path from oc_filecache f inner join oc_filecache p on f.parent=p.fileid and p.path!='' where f.path != p.path || '/' || f.name;
67
+        $query = $builder->select('f.fileid', 'f.path', 'p.path AS parent_path', 'f.name', 'f.parent', 'f.storage', 'p.storage as parent_storage')
68
+            ->from('filecache', 'f')
69
+            ->innerJoin('f', 'filecache', 'p', $builder->expr()->andX(
70
+                $builder->expr()->eq('f.parent', 'p.fileid'),
71
+                $builder->expr()->neq('p.name', $builder->createNamedParameter(''))
72
+            ))
73
+            ->where($builder->expr()->neq('f.path', $computedPath))
74
+            ->setMaxResults(self::MAX_ROWS);
75
+
76
+        do {
77
+            $result = $query->execute();
78
+            $rows = $result->fetchAll();
79
+            foreach ($rows as $row) {
80
+                yield $row;
81
+            }
82
+            $result->closeCursor();
83
+        } while (count($rows) > 0);
84
+    }
85
+
86
+    private function getId($storage, $path) {
87
+        if (!$this->getIdQuery) {
88
+            $builder = $this->connection->getQueryBuilder();
89
+
90
+            $this->getIdQuery = $builder->select('fileid')
91
+                ->from('filecache')
92
+                ->where($builder->expr()->eq('storage', $builder->createParameter('storage')))
93
+                ->andWhere($builder->expr()->eq('path', $builder->createParameter('path')));
94
+        }
95
+
96
+        $this->getIdQuery->setParameter('storage', $storage, IQueryBuilder::PARAM_INT);
97
+        $this->getIdQuery->setParameter('path', $path);
98
+
99
+        return $this->getIdQuery->execute()->fetchColumn();
100
+    }
101
+
102
+    /**
103
+     * @param string $fileid
104
+     * @param string $newPath
105
+     * @param string $newStorage
106
+     * @suppress SqlInjectionChecker
107
+     */
108
+    private function update($fileid, $newPath, $newStorage) {
109
+        if (!$this->updateQuery) {
110
+            $builder = $this->connection->getQueryBuilder();
111
+
112
+            $this->updateQuery = $builder->update('filecache')
113
+                ->set('path', $builder->createParameter('newpath'))
114
+                ->set('path_hash', $builder->func()->md5($builder->createParameter('newpath')))
115
+                ->set('storage', $builder->createParameter('newstorage'))
116
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
117
+        }
118
+
119
+        $this->updateQuery->setParameter('newpath', $newPath);
120
+        $this->updateQuery->setParameter('newstorage', $newStorage);
121
+        $this->updateQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
122
+
123
+        $this->updateQuery->execute();
124
+    }
125
+
126
+    private function reparent($from, $to) {
127
+        if (!$this->reparentQuery) {
128
+            $builder = $this->connection->getQueryBuilder();
129
+
130
+            $this->reparentQuery = $builder->update('filecache')
131
+                ->set('parent', $builder->createParameter('to'))
132
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('from')));
133
+        }
134
+
135
+        $this->reparentQuery->setParameter('from', $from);
136
+        $this->reparentQuery->setParameter('to', $to);
137
+
138
+        $this->reparentQuery->execute();
139
+    }
140
+
141
+    private function delete($fileid) {
142
+        if (!$this->deleteQuery) {
143
+            $builder = $this->connection->getQueryBuilder();
144
+
145
+            $this->deleteQuery = $builder->delete('filecache')
146
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
147
+        }
148
+
149
+        $this->deleteQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
150
+
151
+        $this->deleteQuery->execute();
152
+    }
153
+
154
+    private function repair() {
155
+        $this->connection->beginTransaction();
156
+        $entries = $this->getInvalidEntries();
157
+        $count = 0;
158
+        foreach ($entries as $entry) {
159
+            $count++;
160
+            $calculatedPath = $entry['parent_path'] . '/' . $entry['name'];
161
+            if ($newId = $this->getId($entry['parent_storage'], $calculatedPath)) {
162
+                // a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
163
+                $this->reparent($entry['fileid'], $newId);
164
+                $this->delete($entry['fileid']);
165
+            } else {
166
+                $this->update($entry['fileid'], $calculatedPath, $entry['parent_storage']);
167
+            }
168
+        }
169
+        $this->connection->commit();
170
+        return $count;
171
+    }
172
+
173
+    public function run(IOutput $output) {
174
+        $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
175
+        // was added to 12.0.0.30 and 13.0.0.1
176
+        if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) {
177
+            $count = $this->repair();
178
+
179
+            $output->info('Repaired ' . $count . ' paths');
180
+        }
181
+    }
182 182
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Cache.php 1 patch
Indentation   +829 added lines, -829 removed lines patch added patch discarded remove patch
@@ -56,843 +56,843 @@
 block discarded – undo
56 56
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
57 57
  */
58 58
 class Cache implements ICache {
59
-	use MoveFromCacheTrait {
60
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
61
-	}
62
-
63
-	/**
64
-	 * @var array partial data for the cache
65
-	 */
66
-	protected $partial = array();
67
-
68
-	/**
69
-	 * @var string
70
-	 */
71
-	protected $storageId;
72
-
73
-	/**
74
-	 * @var Storage $storageCache
75
-	 */
76
-	protected $storageCache;
77
-
78
-	/** @var IMimeTypeLoader */
79
-	protected $mimetypeLoader;
80
-
81
-	/**
82
-	 * @var IDBConnection
83
-	 */
84
-	protected $connection;
85
-
86
-	/** @var QuerySearchHelper */
87
-	protected $querySearchHelper;
88
-
89
-	/**
90
-	 * @param \OC\Files\Storage\Storage|string $storage
91
-	 */
92
-	public function __construct($storage) {
93
-		if ($storage instanceof \OC\Files\Storage\Storage) {
94
-			$this->storageId = $storage->getId();
95
-		} else {
96
-			$this->storageId = $storage;
97
-		}
98
-		if (strlen($this->storageId) > 64) {
99
-			$this->storageId = md5($this->storageId);
100
-		}
101
-
102
-		$this->storageCache = new Storage($storage);
103
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
104
-		$this->connection = \OC::$server->getDatabaseConnection();
105
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
106
-	}
107
-
108
-	/**
109
-	 * Get the numeric storage id for this cache's storage
110
-	 *
111
-	 * @return int
112
-	 */
113
-	public function getNumericStorageId() {
114
-		return $this->storageCache->getNumericId();
115
-	}
116
-
117
-	/**
118
-	 * get the stored metadata of a file or folder
119
-	 *
120
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
121
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
122
-	 */
123
-	public function get($file) {
124
-		if (is_string($file) or $file == '') {
125
-			// normalize file
126
-			$file = $this->normalize($file);
127
-
128
-			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
129
-			$params = array($this->getNumericStorageId(), md5($file));
130
-		} else { //file id
131
-			$where = 'WHERE `fileid` = ?';
132
-			$params = array($file);
133
-		}
134
-		$sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
59
+    use MoveFromCacheTrait {
60
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
61
+    }
62
+
63
+    /**
64
+     * @var array partial data for the cache
65
+     */
66
+    protected $partial = array();
67
+
68
+    /**
69
+     * @var string
70
+     */
71
+    protected $storageId;
72
+
73
+    /**
74
+     * @var Storage $storageCache
75
+     */
76
+    protected $storageCache;
77
+
78
+    /** @var IMimeTypeLoader */
79
+    protected $mimetypeLoader;
80
+
81
+    /**
82
+     * @var IDBConnection
83
+     */
84
+    protected $connection;
85
+
86
+    /** @var QuerySearchHelper */
87
+    protected $querySearchHelper;
88
+
89
+    /**
90
+     * @param \OC\Files\Storage\Storage|string $storage
91
+     */
92
+    public function __construct($storage) {
93
+        if ($storage instanceof \OC\Files\Storage\Storage) {
94
+            $this->storageId = $storage->getId();
95
+        } else {
96
+            $this->storageId = $storage;
97
+        }
98
+        if (strlen($this->storageId) > 64) {
99
+            $this->storageId = md5($this->storageId);
100
+        }
101
+
102
+        $this->storageCache = new Storage($storage);
103
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
104
+        $this->connection = \OC::$server->getDatabaseConnection();
105
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
106
+    }
107
+
108
+    /**
109
+     * Get the numeric storage id for this cache's storage
110
+     *
111
+     * @return int
112
+     */
113
+    public function getNumericStorageId() {
114
+        return $this->storageCache->getNumericId();
115
+    }
116
+
117
+    /**
118
+     * get the stored metadata of a file or folder
119
+     *
120
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
121
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
122
+     */
123
+    public function get($file) {
124
+        if (is_string($file) or $file == '') {
125
+            // normalize file
126
+            $file = $this->normalize($file);
127
+
128
+            $where = 'WHERE `storage` = ? AND `path_hash` = ?';
129
+            $params = array($this->getNumericStorageId(), md5($file));
130
+        } else { //file id
131
+            $where = 'WHERE `fileid` = ?';
132
+            $params = array($file);
133
+        }
134
+        $sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
135 135
 					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
136 136
 				FROM `*PREFIX*filecache` ' . $where;
137
-		$result = $this->connection->executeQuery($sql, $params);
138
-		$data = $result->fetch();
139
-
140
-		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
141
-		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
142
-		if ($data === null) {
143
-			$data = false;
144
-		}
145
-
146
-		//merge partial data
147
-		if (!$data and is_string($file)) {
148
-			if (isset($this->partial[$file])) {
149
-				$data = $this->partial[$file];
150
-			}
151
-			return $data;
152
-		} else {
153
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * Create a CacheEntry from database row
159
-	 *
160
-	 * @param array $data
161
-	 * @param IMimeTypeLoader $mimetypeLoader
162
-	 * @return CacheEntry
163
-	 */
164
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
165
-		//fix types
166
-		$data['fileid'] = (int)$data['fileid'];
167
-		$data['parent'] = (int)$data['parent'];
168
-		$data['size'] = 0 + $data['size'];
169
-		$data['mtime'] = (int)$data['mtime'];
170
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
171
-		$data['encryptedVersion'] = (int)$data['encrypted'];
172
-		$data['encrypted'] = (bool)$data['encrypted'];
173
-		$data['storage_id'] = $data['storage'];
174
-		$data['storage'] = (int)$data['storage'];
175
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
176
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
177
-		if ($data['storage_mtime'] == 0) {
178
-			$data['storage_mtime'] = $data['mtime'];
179
-		}
180
-		$data['permissions'] = (int)$data['permissions'];
181
-		return new CacheEntry($data);
182
-	}
183
-
184
-	/**
185
-	 * get the metadata of all files stored in $folder
186
-	 *
187
-	 * @param string $folder
188
-	 * @return ICacheEntry[]
189
-	 */
190
-	public function getFolderContents($folder) {
191
-		$fileId = $this->getId($folder);
192
-		return $this->getFolderContentsById($fileId);
193
-	}
194
-
195
-	/**
196
-	 * get the metadata of all files stored in $folder
197
-	 *
198
-	 * @param int $fileId the file id of the folder
199
-	 * @return ICacheEntry[]
200
-	 */
201
-	public function getFolderContentsById($fileId) {
202
-		if ($fileId > -1) {
203
-			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
137
+        $result = $this->connection->executeQuery($sql, $params);
138
+        $data = $result->fetch();
139
+
140
+        //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
141
+        //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
142
+        if ($data === null) {
143
+            $data = false;
144
+        }
145
+
146
+        //merge partial data
147
+        if (!$data and is_string($file)) {
148
+            if (isset($this->partial[$file])) {
149
+                $data = $this->partial[$file];
150
+            }
151
+            return $data;
152
+        } else {
153
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
154
+        }
155
+    }
156
+
157
+    /**
158
+     * Create a CacheEntry from database row
159
+     *
160
+     * @param array $data
161
+     * @param IMimeTypeLoader $mimetypeLoader
162
+     * @return CacheEntry
163
+     */
164
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
165
+        //fix types
166
+        $data['fileid'] = (int)$data['fileid'];
167
+        $data['parent'] = (int)$data['parent'];
168
+        $data['size'] = 0 + $data['size'];
169
+        $data['mtime'] = (int)$data['mtime'];
170
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
171
+        $data['encryptedVersion'] = (int)$data['encrypted'];
172
+        $data['encrypted'] = (bool)$data['encrypted'];
173
+        $data['storage_id'] = $data['storage'];
174
+        $data['storage'] = (int)$data['storage'];
175
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
176
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
177
+        if ($data['storage_mtime'] == 0) {
178
+            $data['storage_mtime'] = $data['mtime'];
179
+        }
180
+        $data['permissions'] = (int)$data['permissions'];
181
+        return new CacheEntry($data);
182
+    }
183
+
184
+    /**
185
+     * get the metadata of all files stored in $folder
186
+     *
187
+     * @param string $folder
188
+     * @return ICacheEntry[]
189
+     */
190
+    public function getFolderContents($folder) {
191
+        $fileId = $this->getId($folder);
192
+        return $this->getFolderContentsById($fileId);
193
+    }
194
+
195
+    /**
196
+     * get the metadata of all files stored in $folder
197
+     *
198
+     * @param int $fileId the file id of the folder
199
+     * @return ICacheEntry[]
200
+     */
201
+    public function getFolderContentsById($fileId) {
202
+        if ($fileId > -1) {
203
+            $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
204 204
 						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
205 205
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
206
-			$result = $this->connection->executeQuery($sql, [$fileId]);
207
-			$files = $result->fetchAll();
208
-			return array_map(function (array $data) {
209
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);;
210
-			}, $files);
211
-		} else {
212
-			return array();
213
-		}
214
-	}
215
-
216
-	/**
217
-	 * insert or update meta data for a file or folder
218
-	 *
219
-	 * @param string $file
220
-	 * @param array $data
221
-	 *
222
-	 * @return int file id
223
-	 * @throws \RuntimeException
224
-	 */
225
-	public function put($file, array $data) {
226
-		if (($id = $this->getId($file)) > -1) {
227
-			$this->update($id, $data);
228
-			return $id;
229
-		} else {
230
-			return $this->insert($file, $data);
231
-		}
232
-	}
233
-
234
-	/**
235
-	 * insert meta data for a new file or folder
236
-	 *
237
-	 * @param string $file
238
-	 * @param array $data
239
-	 *
240
-	 * @return int file id
241
-	 * @throws \RuntimeException
242
-	 */
243
-	public function insert($file, array $data) {
244
-		// normalize file
245
-		$file = $this->normalize($file);
246
-
247
-		if (isset($this->partial[$file])) { //add any saved partial data
248
-			$data = array_merge($this->partial[$file], $data);
249
-			unset($this->partial[$file]);
250
-		}
251
-
252
-		$requiredFields = array('size', 'mtime', 'mimetype');
253
-		foreach ($requiredFields as $field) {
254
-			if (!isset($data[$field])) { //data not complete save as partial and return
255
-				$this->partial[$file] = $data;
256
-				return -1;
257
-			}
258
-		}
259
-
260
-		$data['path'] = $file;
261
-		$data['parent'] = $this->getParentId($file);
262
-		$data['name'] = \OC_Util::basename($file);
263
-
264
-		list($queryParts, $params) = $this->buildParts($data);
265
-		$queryParts[] = '`storage`';
266
-		$params[] = $this->getNumericStorageId();
267
-
268
-		$queryParts = array_map(function ($item) {
269
-			return trim($item, "`");
270
-		}, $queryParts);
271
-		$values = array_combine($queryParts, $params);
272
-		if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
273
-			'storage',
274
-			'path_hash',
275
-		])
276
-		) {
277
-			return (int)$this->connection->lastInsertId('*PREFIX*filecache');
278
-		}
279
-
280
-		// The file was created in the mean time
281
-		if (($id = $this->getId($file)) > -1) {
282
-			$this->update($id, $data);
283
-			return $id;
284
-		} else {
285
-			throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
286
-		}
287
-	}
288
-
289
-	/**
290
-	 * update the metadata of an existing file or folder in the cache
291
-	 *
292
-	 * @param int $id the fileid of the existing file or folder
293
-	 * @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
294
-	 */
295
-	public function update($id, array $data) {
296
-
297
-		if (isset($data['path'])) {
298
-			// normalize path
299
-			$data['path'] = $this->normalize($data['path']);
300
-		}
301
-
302
-		if (isset($data['name'])) {
303
-			// normalize path
304
-			$data['name'] = $this->normalize($data['name']);
305
-		}
306
-
307
-		list($queryParts, $params) = $this->buildParts($data);
308
-		// duplicate $params because we need the parts twice in the SQL statement
309
-		// once for the SET part, once in the WHERE clause
310
-		$params = array_merge($params, $params);
311
-		$params[] = $id;
312
-
313
-		// don't update if the data we try to set is the same as the one in the record
314
-		// some databases (Postgres) don't like superfluous updates
315
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
316
-			'WHERE (' .
317
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
318
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
319
-			') AND `fileid` = ? ';
320
-		$this->connection->executeQuery($sql, $params);
321
-
322
-	}
323
-
324
-	/**
325
-	 * extract query parts and params array from data array
326
-	 *
327
-	 * @param array $data
328
-	 * @return array [$queryParts, $params]
329
-	 *        $queryParts: string[], the (escaped) column names to be set in the query
330
-	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
331
-	 */
332
-	protected function buildParts(array $data) {
333
-		$fields = array(
334
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
335
-			'etag', 'permissions', 'checksum', 'storage');
336
-
337
-		$doNotCopyStorageMTime = false;
338
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
339
-			// this horrific magic tells it to not copy storage_mtime to mtime
340
-			unset($data['mtime']);
341
-			$doNotCopyStorageMTime = true;
342
-		}
343
-
344
-		$params = array();
345
-		$queryParts = array();
346
-		foreach ($data as $name => $value) {
347
-			if (array_search($name, $fields) !== false) {
348
-				if ($name === 'path') {
349
-					$params[] = md5($value);
350
-					$queryParts[] = '`path_hash`';
351
-				} elseif ($name === 'mimetype') {
352
-					$params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
353
-					$queryParts[] = '`mimepart`';
354
-					$value = $this->mimetypeLoader->getId($value);
355
-				} elseif ($name === 'storage_mtime') {
356
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
357
-						$params[] = $value;
358
-						$queryParts[] = '`mtime`';
359
-					}
360
-				} elseif ($name === 'encrypted') {
361
-					if (isset($data['encryptedVersion'])) {
362
-						$value = $data['encryptedVersion'];
363
-					} else {
364
-						// Boolean to integer conversion
365
-						$value = $value ? 1 : 0;
366
-					}
367
-				}
368
-				$params[] = $value;
369
-				$queryParts[] = '`' . $name . '`';
370
-			}
371
-		}
372
-		return array($queryParts, $params);
373
-	}
374
-
375
-	/**
376
-	 * get the file id for a file
377
-	 *
378
-	 * 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
379
-	 *
380
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
381
-	 *
382
-	 * @param string $file
383
-	 * @return int
384
-	 */
385
-	public function getId($file) {
386
-		// normalize file
387
-		$file = $this->normalize($file);
388
-
389
-		$pathHash = md5($file);
390
-
391
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
392
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
393
-		if ($row = $result->fetch()) {
394
-			return $row['fileid'];
395
-		} else {
396
-			return -1;
397
-		}
398
-	}
399
-
400
-	/**
401
-	 * get the id of the parent folder of a file
402
-	 *
403
-	 * @param string $file
404
-	 * @return int
405
-	 */
406
-	public function getParentId($file) {
407
-		if ($file === '') {
408
-			return -1;
409
-		} else {
410
-			$parent = $this->getParentPath($file);
411
-			return (int)$this->getId($parent);
412
-		}
413
-	}
414
-
415
-	private function getParentPath($path) {
416
-		$parent = dirname($path);
417
-		if ($parent === '.') {
418
-			$parent = '';
419
-		}
420
-		return $parent;
421
-	}
422
-
423
-	/**
424
-	 * check if a file is available in the cache
425
-	 *
426
-	 * @param string $file
427
-	 * @return bool
428
-	 */
429
-	public function inCache($file) {
430
-		return $this->getId($file) != -1;
431
-	}
432
-
433
-	/**
434
-	 * remove a file or folder from the cache
435
-	 *
436
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
437
-	 *
438
-	 * @param string $file
439
-	 */
440
-	public function remove($file) {
441
-		$entry = $this->get($file);
442
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
443
-		$this->connection->executeQuery($sql, array($entry['fileid']));
444
-		if ($entry['mimetype'] === 'httpd/unix-directory') {
445
-			$this->removeChildren($entry);
446
-		}
447
-	}
448
-
449
-	/**
450
-	 * Get all sub folders of a folder
451
-	 *
452
-	 * @param array $entry the cache entry of the folder to get the subfolders for
453
-	 * @return array[] the cache entries for the subfolders
454
-	 */
455
-	private function getSubFolders($entry) {
456
-		$children = $this->getFolderContentsById($entry['fileid']);
457
-		return array_filter($children, function ($child) {
458
-			return $child['mimetype'] === 'httpd/unix-directory';
459
-		});
460
-	}
461
-
462
-	/**
463
-	 * Recursively remove all children of a folder
464
-	 *
465
-	 * @param array $entry the cache entry of the folder to remove the children of
466
-	 * @throws \OC\DatabaseException
467
-	 */
468
-	private function removeChildren($entry) {
469
-		$subFolders = $this->getSubFolders($entry);
470
-		foreach ($subFolders as $folder) {
471
-			$this->removeChildren($folder);
472
-		}
473
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
474
-		$this->connection->executeQuery($sql, array($entry['fileid']));
475
-	}
476
-
477
-	/**
478
-	 * Move a file or folder in the cache
479
-	 *
480
-	 * @param string $source
481
-	 * @param string $target
482
-	 */
483
-	public function move($source, $target) {
484
-		$this->moveFromCache($this, $source, $target);
485
-	}
486
-
487
-	/**
488
-	 * Get the storage id and path needed for a move
489
-	 *
490
-	 * @param string $path
491
-	 * @return array [$storageId, $internalPath]
492
-	 */
493
-	protected function getMoveInfo($path) {
494
-		return [$this->getNumericStorageId(), $path];
495
-	}
496
-
497
-	/**
498
-	 * Move a file or folder in the cache
499
-	 *
500
-	 * @param \OCP\Files\Cache\ICache $sourceCache
501
-	 * @param string $sourcePath
502
-	 * @param string $targetPath
503
-	 * @throws \OC\DatabaseException
504
-	 * @throws \Exception if the given storages have an invalid id
505
-	 * @suppress SqlInjectionChecker
506
-	 */
507
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
508
-		if ($sourceCache instanceof Cache) {
509
-			// normalize source and target
510
-			$sourcePath = $this->normalize($sourcePath);
511
-			$targetPath = $this->normalize($targetPath);
512
-
513
-			$sourceData = $sourceCache->get($sourcePath);
514
-			$sourceId = $sourceData['fileid'];
515
-			$newParentId = $this->getParentId($targetPath);
516
-
517
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
518
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
519
-
520
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
521
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
522
-			}
523
-			if (is_null($targetStorageId) || $targetStorageId === false) {
524
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
525
-			}
526
-
527
-			$this->connection->beginTransaction();
528
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
529
-				//update all child entries
530
-				$sourceLength = strlen($sourcePath);
531
-				$query = $this->connection->getQueryBuilder();
532
-
533
-				$fun = $query->func();
534
-				$newPathFunction = $fun->concat(
535
-					$query->createNamedParameter($targetPath),
536
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
537
-				);
538
-				$query->update('filecache')
539
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
540
-					->set('path_hash', $fun->md5($newPathFunction))
541
-					->set('path', $newPathFunction)
542
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
543
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
544
-
545
-				try {
546
-					$query->execute();
547
-				} catch (\OC\DatabaseException $e) {
548
-					$this->connection->rollBack();
549
-					throw $e;
550
-				}
551
-			}
552
-
553
-			$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
554
-			$this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), \OC_Util::basename($targetPath), $newParentId, $sourceId));
555
-			$this->connection->commit();
556
-		} else {
557
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
558
-		}
559
-	}
560
-
561
-	/**
562
-	 * remove all entries for files that are stored on the storage from the cache
563
-	 */
564
-	public function clear() {
565
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
566
-		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
567
-
568
-		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
569
-		$this->connection->executeQuery($sql, array($this->storageId));
570
-	}
571
-
572
-	/**
573
-	 * Get the scan status of a file
574
-	 *
575
-	 * - Cache::NOT_FOUND: File is not in the cache
576
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
577
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
578
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
579
-	 *
580
-	 * @param string $file
581
-	 *
582
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
583
-	 */
584
-	public function getStatus($file) {
585
-		// normalize file
586
-		$file = $this->normalize($file);
587
-
588
-		$pathHash = md5($file);
589
-		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
590
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
591
-		if ($row = $result->fetch()) {
592
-			if ((int)$row['size'] === -1) {
593
-				return self::SHALLOW;
594
-			} else {
595
-				return self::COMPLETE;
596
-			}
597
-		} else {
598
-			if (isset($this->partial[$file])) {
599
-				return self::PARTIAL;
600
-			} else {
601
-				return self::NOT_FOUND;
602
-			}
603
-		}
604
-	}
605
-
606
-	/**
607
-	 * search for files matching $pattern
608
-	 *
609
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
610
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
611
-	 */
612
-	public function search($pattern) {
613
-		// normalize pattern
614
-		$pattern = $this->normalize($pattern);
615
-
616
-		if ($pattern === '%%') {
617
-			return [];
618
-		}
619
-
620
-
621
-		$sql = '
206
+            $result = $this->connection->executeQuery($sql, [$fileId]);
207
+            $files = $result->fetchAll();
208
+            return array_map(function (array $data) {
209
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);;
210
+            }, $files);
211
+        } else {
212
+            return array();
213
+        }
214
+    }
215
+
216
+    /**
217
+     * insert or update meta data for a file or folder
218
+     *
219
+     * @param string $file
220
+     * @param array $data
221
+     *
222
+     * @return int file id
223
+     * @throws \RuntimeException
224
+     */
225
+    public function put($file, array $data) {
226
+        if (($id = $this->getId($file)) > -1) {
227
+            $this->update($id, $data);
228
+            return $id;
229
+        } else {
230
+            return $this->insert($file, $data);
231
+        }
232
+    }
233
+
234
+    /**
235
+     * insert meta data for a new file or folder
236
+     *
237
+     * @param string $file
238
+     * @param array $data
239
+     *
240
+     * @return int file id
241
+     * @throws \RuntimeException
242
+     */
243
+    public function insert($file, array $data) {
244
+        // normalize file
245
+        $file = $this->normalize($file);
246
+
247
+        if (isset($this->partial[$file])) { //add any saved partial data
248
+            $data = array_merge($this->partial[$file], $data);
249
+            unset($this->partial[$file]);
250
+        }
251
+
252
+        $requiredFields = array('size', 'mtime', 'mimetype');
253
+        foreach ($requiredFields as $field) {
254
+            if (!isset($data[$field])) { //data not complete save as partial and return
255
+                $this->partial[$file] = $data;
256
+                return -1;
257
+            }
258
+        }
259
+
260
+        $data['path'] = $file;
261
+        $data['parent'] = $this->getParentId($file);
262
+        $data['name'] = \OC_Util::basename($file);
263
+
264
+        list($queryParts, $params) = $this->buildParts($data);
265
+        $queryParts[] = '`storage`';
266
+        $params[] = $this->getNumericStorageId();
267
+
268
+        $queryParts = array_map(function ($item) {
269
+            return trim($item, "`");
270
+        }, $queryParts);
271
+        $values = array_combine($queryParts, $params);
272
+        if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
273
+            'storage',
274
+            'path_hash',
275
+        ])
276
+        ) {
277
+            return (int)$this->connection->lastInsertId('*PREFIX*filecache');
278
+        }
279
+
280
+        // The file was created in the mean time
281
+        if (($id = $this->getId($file)) > -1) {
282
+            $this->update($id, $data);
283
+            return $id;
284
+        } else {
285
+            throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
286
+        }
287
+    }
288
+
289
+    /**
290
+     * update the metadata of an existing file or folder in the cache
291
+     *
292
+     * @param int $id the fileid of the existing file or folder
293
+     * @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
294
+     */
295
+    public function update($id, array $data) {
296
+
297
+        if (isset($data['path'])) {
298
+            // normalize path
299
+            $data['path'] = $this->normalize($data['path']);
300
+        }
301
+
302
+        if (isset($data['name'])) {
303
+            // normalize path
304
+            $data['name'] = $this->normalize($data['name']);
305
+        }
306
+
307
+        list($queryParts, $params) = $this->buildParts($data);
308
+        // duplicate $params because we need the parts twice in the SQL statement
309
+        // once for the SET part, once in the WHERE clause
310
+        $params = array_merge($params, $params);
311
+        $params[] = $id;
312
+
313
+        // don't update if the data we try to set is the same as the one in the record
314
+        // some databases (Postgres) don't like superfluous updates
315
+        $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
316
+            'WHERE (' .
317
+            implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
318
+            implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
319
+            ') AND `fileid` = ? ';
320
+        $this->connection->executeQuery($sql, $params);
321
+
322
+    }
323
+
324
+    /**
325
+     * extract query parts and params array from data array
326
+     *
327
+     * @param array $data
328
+     * @return array [$queryParts, $params]
329
+     *        $queryParts: string[], the (escaped) column names to be set in the query
330
+     *        $params: mixed[], the new values for the columns, to be passed as params to the query
331
+     */
332
+    protected function buildParts(array $data) {
333
+        $fields = array(
334
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
335
+            'etag', 'permissions', 'checksum', 'storage');
336
+
337
+        $doNotCopyStorageMTime = false;
338
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
339
+            // this horrific magic tells it to not copy storage_mtime to mtime
340
+            unset($data['mtime']);
341
+            $doNotCopyStorageMTime = true;
342
+        }
343
+
344
+        $params = array();
345
+        $queryParts = array();
346
+        foreach ($data as $name => $value) {
347
+            if (array_search($name, $fields) !== false) {
348
+                if ($name === 'path') {
349
+                    $params[] = md5($value);
350
+                    $queryParts[] = '`path_hash`';
351
+                } elseif ($name === 'mimetype') {
352
+                    $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
353
+                    $queryParts[] = '`mimepart`';
354
+                    $value = $this->mimetypeLoader->getId($value);
355
+                } elseif ($name === 'storage_mtime') {
356
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
357
+                        $params[] = $value;
358
+                        $queryParts[] = '`mtime`';
359
+                    }
360
+                } elseif ($name === 'encrypted') {
361
+                    if (isset($data['encryptedVersion'])) {
362
+                        $value = $data['encryptedVersion'];
363
+                    } else {
364
+                        // Boolean to integer conversion
365
+                        $value = $value ? 1 : 0;
366
+                    }
367
+                }
368
+                $params[] = $value;
369
+                $queryParts[] = '`' . $name . '`';
370
+            }
371
+        }
372
+        return array($queryParts, $params);
373
+    }
374
+
375
+    /**
376
+     * get the file id for a file
377
+     *
378
+     * 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
379
+     *
380
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
381
+     *
382
+     * @param string $file
383
+     * @return int
384
+     */
385
+    public function getId($file) {
386
+        // normalize file
387
+        $file = $this->normalize($file);
388
+
389
+        $pathHash = md5($file);
390
+
391
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
392
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
393
+        if ($row = $result->fetch()) {
394
+            return $row['fileid'];
395
+        } else {
396
+            return -1;
397
+        }
398
+    }
399
+
400
+    /**
401
+     * get the id of the parent folder of a file
402
+     *
403
+     * @param string $file
404
+     * @return int
405
+     */
406
+    public function getParentId($file) {
407
+        if ($file === '') {
408
+            return -1;
409
+        } else {
410
+            $parent = $this->getParentPath($file);
411
+            return (int)$this->getId($parent);
412
+        }
413
+    }
414
+
415
+    private function getParentPath($path) {
416
+        $parent = dirname($path);
417
+        if ($parent === '.') {
418
+            $parent = '';
419
+        }
420
+        return $parent;
421
+    }
422
+
423
+    /**
424
+     * check if a file is available in the cache
425
+     *
426
+     * @param string $file
427
+     * @return bool
428
+     */
429
+    public function inCache($file) {
430
+        return $this->getId($file) != -1;
431
+    }
432
+
433
+    /**
434
+     * remove a file or folder from the cache
435
+     *
436
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
437
+     *
438
+     * @param string $file
439
+     */
440
+    public function remove($file) {
441
+        $entry = $this->get($file);
442
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
443
+        $this->connection->executeQuery($sql, array($entry['fileid']));
444
+        if ($entry['mimetype'] === 'httpd/unix-directory') {
445
+            $this->removeChildren($entry);
446
+        }
447
+    }
448
+
449
+    /**
450
+     * Get all sub folders of a folder
451
+     *
452
+     * @param array $entry the cache entry of the folder to get the subfolders for
453
+     * @return array[] the cache entries for the subfolders
454
+     */
455
+    private function getSubFolders($entry) {
456
+        $children = $this->getFolderContentsById($entry['fileid']);
457
+        return array_filter($children, function ($child) {
458
+            return $child['mimetype'] === 'httpd/unix-directory';
459
+        });
460
+    }
461
+
462
+    /**
463
+     * Recursively remove all children of a folder
464
+     *
465
+     * @param array $entry the cache entry of the folder to remove the children of
466
+     * @throws \OC\DatabaseException
467
+     */
468
+    private function removeChildren($entry) {
469
+        $subFolders = $this->getSubFolders($entry);
470
+        foreach ($subFolders as $folder) {
471
+            $this->removeChildren($folder);
472
+        }
473
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
474
+        $this->connection->executeQuery($sql, array($entry['fileid']));
475
+    }
476
+
477
+    /**
478
+     * Move a file or folder in the cache
479
+     *
480
+     * @param string $source
481
+     * @param string $target
482
+     */
483
+    public function move($source, $target) {
484
+        $this->moveFromCache($this, $source, $target);
485
+    }
486
+
487
+    /**
488
+     * Get the storage id and path needed for a move
489
+     *
490
+     * @param string $path
491
+     * @return array [$storageId, $internalPath]
492
+     */
493
+    protected function getMoveInfo($path) {
494
+        return [$this->getNumericStorageId(), $path];
495
+    }
496
+
497
+    /**
498
+     * Move a file or folder in the cache
499
+     *
500
+     * @param \OCP\Files\Cache\ICache $sourceCache
501
+     * @param string $sourcePath
502
+     * @param string $targetPath
503
+     * @throws \OC\DatabaseException
504
+     * @throws \Exception if the given storages have an invalid id
505
+     * @suppress SqlInjectionChecker
506
+     */
507
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
508
+        if ($sourceCache instanceof Cache) {
509
+            // normalize source and target
510
+            $sourcePath = $this->normalize($sourcePath);
511
+            $targetPath = $this->normalize($targetPath);
512
+
513
+            $sourceData = $sourceCache->get($sourcePath);
514
+            $sourceId = $sourceData['fileid'];
515
+            $newParentId = $this->getParentId($targetPath);
516
+
517
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
518
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
519
+
520
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
521
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
522
+            }
523
+            if (is_null($targetStorageId) || $targetStorageId === false) {
524
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
525
+            }
526
+
527
+            $this->connection->beginTransaction();
528
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
529
+                //update all child entries
530
+                $sourceLength = strlen($sourcePath);
531
+                $query = $this->connection->getQueryBuilder();
532
+
533
+                $fun = $query->func();
534
+                $newPathFunction = $fun->concat(
535
+                    $query->createNamedParameter($targetPath),
536
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
537
+                );
538
+                $query->update('filecache')
539
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
540
+                    ->set('path_hash', $fun->md5($newPathFunction))
541
+                    ->set('path', $newPathFunction)
542
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
543
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
544
+
545
+                try {
546
+                    $query->execute();
547
+                } catch (\OC\DatabaseException $e) {
548
+                    $this->connection->rollBack();
549
+                    throw $e;
550
+                }
551
+            }
552
+
553
+            $sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
554
+            $this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), \OC_Util::basename($targetPath), $newParentId, $sourceId));
555
+            $this->connection->commit();
556
+        } else {
557
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
558
+        }
559
+    }
560
+
561
+    /**
562
+     * remove all entries for files that are stored on the storage from the cache
563
+     */
564
+    public function clear() {
565
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
566
+        $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
567
+
568
+        $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
569
+        $this->connection->executeQuery($sql, array($this->storageId));
570
+    }
571
+
572
+    /**
573
+     * Get the scan status of a file
574
+     *
575
+     * - Cache::NOT_FOUND: File is not in the cache
576
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
577
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
578
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
579
+     *
580
+     * @param string $file
581
+     *
582
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
583
+     */
584
+    public function getStatus($file) {
585
+        // normalize file
586
+        $file = $this->normalize($file);
587
+
588
+        $pathHash = md5($file);
589
+        $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
590
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
591
+        if ($row = $result->fetch()) {
592
+            if ((int)$row['size'] === -1) {
593
+                return self::SHALLOW;
594
+            } else {
595
+                return self::COMPLETE;
596
+            }
597
+        } else {
598
+            if (isset($this->partial[$file])) {
599
+                return self::PARTIAL;
600
+            } else {
601
+                return self::NOT_FOUND;
602
+            }
603
+        }
604
+    }
605
+
606
+    /**
607
+     * search for files matching $pattern
608
+     *
609
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
610
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
611
+     */
612
+    public function search($pattern) {
613
+        // normalize pattern
614
+        $pattern = $this->normalize($pattern);
615
+
616
+        if ($pattern === '%%') {
617
+            return [];
618
+        }
619
+
620
+
621
+        $sql = '
622 622
 			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
623 623
 				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
624 624
 				 `encrypted`, `etag`, `permissions`, `checksum`
625 625
 			FROM `*PREFIX*filecache`
626 626
 			WHERE `storage` = ? AND `name` ILIKE ?';
627
-		$result = $this->connection->executeQuery($sql,
628
-			[$this->getNumericStorageId(), $pattern]
629
-		);
630
-
631
-		return $this->searchResultToCacheEntries($result);
632
-	}
633
-
634
-	/**
635
-	 * @param Statement $result
636
-	 * @return CacheEntry[]
637
-	 */
638
-	private function searchResultToCacheEntries(Statement $result) {
639
-		$files = $result->fetchAll();
640
-
641
-		return array_map(function (array $data) {
642
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
643
-		}, $files);
644
-	}
645
-
646
-	/**
647
-	 * search for files by mimetype
648
-	 *
649
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
650
-	 *        where it will search for all mimetypes in the group ('image/*')
651
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
652
-	 */
653
-	public function searchByMime($mimetype) {
654
-		if (strpos($mimetype, '/')) {
655
-			$where = '`mimetype` = ?';
656
-		} else {
657
-			$where = '`mimepart` = ?';
658
-		}
659
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
627
+        $result = $this->connection->executeQuery($sql,
628
+            [$this->getNumericStorageId(), $pattern]
629
+        );
630
+
631
+        return $this->searchResultToCacheEntries($result);
632
+    }
633
+
634
+    /**
635
+     * @param Statement $result
636
+     * @return CacheEntry[]
637
+     */
638
+    private function searchResultToCacheEntries(Statement $result) {
639
+        $files = $result->fetchAll();
640
+
641
+        return array_map(function (array $data) {
642
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
643
+        }, $files);
644
+    }
645
+
646
+    /**
647
+     * search for files by mimetype
648
+     *
649
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
650
+     *        where it will search for all mimetypes in the group ('image/*')
651
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
652
+     */
653
+    public function searchByMime($mimetype) {
654
+        if (strpos($mimetype, '/')) {
655
+            $where = '`mimetype` = ?';
656
+        } else {
657
+            $where = '`mimepart` = ?';
658
+        }
659
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
660 660
 				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
661
-		$mimetype = $this->mimetypeLoader->getId($mimetype);
662
-		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
663
-
664
-		return $this->searchResultToCacheEntries($result);
665
-	}
666
-
667
-	public function searchQuery(ISearchQuery $searchQuery) {
668
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
669
-
670
-		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
671
-			->from('filecache', 'file');
672
-
673
-		$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
674
-
675
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
676
-			$query
677
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
678
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
679
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
680
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
681
-				))
682
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
683
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
684
-		}
685
-
686
-		$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
687
-
688
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
689
-
690
-		if ($searchQuery->getLimit()) {
691
-			$query->setMaxResults($searchQuery->getLimit());
692
-		}
693
-		if ($searchQuery->getOffset()) {
694
-			$query->setFirstResult($searchQuery->getOffset());
695
-		}
696
-
697
-		$result = $query->execute();
698
-		return $this->searchResultToCacheEntries($result);
699
-	}
700
-
701
-	/**
702
-	 * Search for files by tag of a given users.
703
-	 *
704
-	 * Note that every user can tag files differently.
705
-	 *
706
-	 * @param string|int $tag name or tag id
707
-	 * @param string $userId owner of the tags
708
-	 * @return ICacheEntry[] file data
709
-	 */
710
-	public function searchByTag($tag, $userId) {
711
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
712
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
713
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
714
-			'FROM `*PREFIX*filecache` `file`, ' .
715
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
716
-			'`*PREFIX*vcategory` `tag` ' .
717
-			// JOIN filecache to vcategory_to_object
718
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
719
-			// JOIN vcategory_to_object to vcategory
720
-			'AND `tagmap`.`type` = `tag`.`type` ' .
721
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
722
-			// conditions
723
-			'AND `file`.`storage` = ? ' .
724
-			'AND `tag`.`type` = \'files\' ' .
725
-			'AND `tag`.`uid` = ? ';
726
-		if (is_int($tag)) {
727
-			$sql .= 'AND `tag`.`id` = ? ';
728
-		} else {
729
-			$sql .= 'AND `tag`.`category` = ? ';
730
-		}
731
-		$result = $this->connection->executeQuery(
732
-			$sql,
733
-			[
734
-				$this->getNumericStorageId(),
735
-				$userId,
736
-				$tag
737
-			]
738
-		);
739
-
740
-		$files = $result->fetchAll();
741
-
742
-		return array_map(function (array $data) {
743
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
744
-		}, $files);
745
-	}
746
-
747
-	/**
748
-	 * Re-calculate the folder size and the size of all parent folders
749
-	 *
750
-	 * @param string|boolean $path
751
-	 * @param array $data (optional) meta data of the folder
752
-	 */
753
-	public function correctFolderSize($path, $data = null) {
754
-		$this->calculateFolderSize($path, $data);
755
-		if ($path !== '') {
756
-			$parent = dirname($path);
757
-			if ($parent === '.' or $parent === '/') {
758
-				$parent = '';
759
-			}
760
-			$this->correctFolderSize($parent);
761
-		}
762
-	}
763
-
764
-	/**
765
-	 * calculate the size of a folder and set it in the cache
766
-	 *
767
-	 * @param string $path
768
-	 * @param array $entry (optional) meta data of the folder
769
-	 * @return int
770
-	 */
771
-	public function calculateFolderSize($path, $entry = null) {
772
-		$totalSize = 0;
773
-		if (is_null($entry) or !isset($entry['fileid'])) {
774
-			$entry = $this->get($path);
775
-		}
776
-		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
777
-			$id = $entry['fileid'];
778
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
779
-				'FROM `*PREFIX*filecache` ' .
780
-				'WHERE `parent` = ? AND `storage` = ?';
781
-			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
782
-			if ($row = $result->fetch()) {
783
-				$result->closeCursor();
784
-				list($sum, $min) = array_values($row);
785
-				$sum = 0 + $sum;
786
-				$min = 0 + $min;
787
-				if ($min === -1) {
788
-					$totalSize = $min;
789
-				} else {
790
-					$totalSize = $sum;
791
-				}
792
-				$update = array();
793
-				if ($entry['size'] !== $totalSize) {
794
-					$update['size'] = $totalSize;
795
-				}
796
-				if (count($update) > 0) {
797
-					$this->update($id, $update);
798
-				}
799
-			} else {
800
-				$result->closeCursor();
801
-			}
802
-		}
803
-		return $totalSize;
804
-	}
805
-
806
-	/**
807
-	 * get all file ids on the files on the storage
808
-	 *
809
-	 * @return int[]
810
-	 */
811
-	public function getAll() {
812
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
813
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
814
-		$ids = array();
815
-		while ($row = $result->fetch()) {
816
-			$ids[] = $row['fileid'];
817
-		}
818
-		return $ids;
819
-	}
820
-
821
-	/**
822
-	 * find a folder in the cache which has not been fully scanned
823
-	 *
824
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
825
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
826
-	 * likely the folder where we stopped scanning previously
827
-	 *
828
-	 * @return string|bool the path of the folder or false when no folder matched
829
-	 */
830
-	public function getIncomplete() {
831
-		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
832
-			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
833
-		$query->execute([$this->getNumericStorageId()]);
834
-		if ($row = $query->fetch()) {
835
-			return $row['path'];
836
-		} else {
837
-			return false;
838
-		}
839
-	}
840
-
841
-	/**
842
-	 * get the path of a file on this storage by it's file id
843
-	 *
844
-	 * @param int $id the file id of the file or folder to search
845
-	 * @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
846
-	 */
847
-	public function getPathById($id) {
848
-		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
849
-		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
850
-		if ($row = $result->fetch()) {
851
-			// Oracle stores empty strings as null...
852
-			if ($row['path'] === null) {
853
-				return '';
854
-			}
855
-			return $row['path'];
856
-		} else {
857
-			return null;
858
-		}
859
-	}
860
-
861
-	/**
862
-	 * get the storage id of the storage for a file and the internal path of the file
863
-	 * unlike getPathById this does not limit the search to files on this storage and
864
-	 * instead does a global search in the cache table
865
-	 *
866
-	 * @param int $id
867
-	 * @deprecated use getPathById() instead
868
-	 * @return array first element holding the storage id, second the path
869
-	 */
870
-	static public function getById($id) {
871
-		$connection = \OC::$server->getDatabaseConnection();
872
-		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
873
-		$result = $connection->executeQuery($sql, array($id));
874
-		if ($row = $result->fetch()) {
875
-			$numericId = $row['storage'];
876
-			$path = $row['path'];
877
-		} else {
878
-			return null;
879
-		}
880
-
881
-		if ($id = Storage::getStorageId($numericId)) {
882
-			return array($id, $path);
883
-		} else {
884
-			return null;
885
-		}
886
-	}
887
-
888
-	/**
889
-	 * normalize the given path
890
-	 *
891
-	 * @param string $path
892
-	 * @return string
893
-	 */
894
-	public function normalize($path) {
895
-
896
-		return trim(\OC_Util::normalizeUnicode($path), '/');
897
-	}
661
+        $mimetype = $this->mimetypeLoader->getId($mimetype);
662
+        $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
663
+
664
+        return $this->searchResultToCacheEntries($result);
665
+    }
666
+
667
+    public function searchQuery(ISearchQuery $searchQuery) {
668
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
669
+
670
+        $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
671
+            ->from('filecache', 'file');
672
+
673
+        $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
674
+
675
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
676
+            $query
677
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
678
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
679
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
680
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
681
+                ))
682
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
683
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
684
+        }
685
+
686
+        $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
687
+
688
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
689
+
690
+        if ($searchQuery->getLimit()) {
691
+            $query->setMaxResults($searchQuery->getLimit());
692
+        }
693
+        if ($searchQuery->getOffset()) {
694
+            $query->setFirstResult($searchQuery->getOffset());
695
+        }
696
+
697
+        $result = $query->execute();
698
+        return $this->searchResultToCacheEntries($result);
699
+    }
700
+
701
+    /**
702
+     * Search for files by tag of a given users.
703
+     *
704
+     * Note that every user can tag files differently.
705
+     *
706
+     * @param string|int $tag name or tag id
707
+     * @param string $userId owner of the tags
708
+     * @return ICacheEntry[] file data
709
+     */
710
+    public function searchByTag($tag, $userId) {
711
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
712
+            '`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
713
+            '`encrypted`, `etag`, `permissions`, `checksum` ' .
714
+            'FROM `*PREFIX*filecache` `file`, ' .
715
+            '`*PREFIX*vcategory_to_object` `tagmap`, ' .
716
+            '`*PREFIX*vcategory` `tag` ' .
717
+            // JOIN filecache to vcategory_to_object
718
+            'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
719
+            // JOIN vcategory_to_object to vcategory
720
+            'AND `tagmap`.`type` = `tag`.`type` ' .
721
+            'AND `tagmap`.`categoryid` = `tag`.`id` ' .
722
+            // conditions
723
+            'AND `file`.`storage` = ? ' .
724
+            'AND `tag`.`type` = \'files\' ' .
725
+            'AND `tag`.`uid` = ? ';
726
+        if (is_int($tag)) {
727
+            $sql .= 'AND `tag`.`id` = ? ';
728
+        } else {
729
+            $sql .= 'AND `tag`.`category` = ? ';
730
+        }
731
+        $result = $this->connection->executeQuery(
732
+            $sql,
733
+            [
734
+                $this->getNumericStorageId(),
735
+                $userId,
736
+                $tag
737
+            ]
738
+        );
739
+
740
+        $files = $result->fetchAll();
741
+
742
+        return array_map(function (array $data) {
743
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
744
+        }, $files);
745
+    }
746
+
747
+    /**
748
+     * Re-calculate the folder size and the size of all parent folders
749
+     *
750
+     * @param string|boolean $path
751
+     * @param array $data (optional) meta data of the folder
752
+     */
753
+    public function correctFolderSize($path, $data = null) {
754
+        $this->calculateFolderSize($path, $data);
755
+        if ($path !== '') {
756
+            $parent = dirname($path);
757
+            if ($parent === '.' or $parent === '/') {
758
+                $parent = '';
759
+            }
760
+            $this->correctFolderSize($parent);
761
+        }
762
+    }
763
+
764
+    /**
765
+     * calculate the size of a folder and set it in the cache
766
+     *
767
+     * @param string $path
768
+     * @param array $entry (optional) meta data of the folder
769
+     * @return int
770
+     */
771
+    public function calculateFolderSize($path, $entry = null) {
772
+        $totalSize = 0;
773
+        if (is_null($entry) or !isset($entry['fileid'])) {
774
+            $entry = $this->get($path);
775
+        }
776
+        if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
777
+            $id = $entry['fileid'];
778
+            $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
779
+                'FROM `*PREFIX*filecache` ' .
780
+                'WHERE `parent` = ? AND `storage` = ?';
781
+            $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
782
+            if ($row = $result->fetch()) {
783
+                $result->closeCursor();
784
+                list($sum, $min) = array_values($row);
785
+                $sum = 0 + $sum;
786
+                $min = 0 + $min;
787
+                if ($min === -1) {
788
+                    $totalSize = $min;
789
+                } else {
790
+                    $totalSize = $sum;
791
+                }
792
+                $update = array();
793
+                if ($entry['size'] !== $totalSize) {
794
+                    $update['size'] = $totalSize;
795
+                }
796
+                if (count($update) > 0) {
797
+                    $this->update($id, $update);
798
+                }
799
+            } else {
800
+                $result->closeCursor();
801
+            }
802
+        }
803
+        return $totalSize;
804
+    }
805
+
806
+    /**
807
+     * get all file ids on the files on the storage
808
+     *
809
+     * @return int[]
810
+     */
811
+    public function getAll() {
812
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
813
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
814
+        $ids = array();
815
+        while ($row = $result->fetch()) {
816
+            $ids[] = $row['fileid'];
817
+        }
818
+        return $ids;
819
+    }
820
+
821
+    /**
822
+     * find a folder in the cache which has not been fully scanned
823
+     *
824
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
825
+     * use the one with the highest id gives the best result with the background scanner, since that is most
826
+     * likely the folder where we stopped scanning previously
827
+     *
828
+     * @return string|bool the path of the folder or false when no folder matched
829
+     */
830
+    public function getIncomplete() {
831
+        $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
832
+            . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
833
+        $query->execute([$this->getNumericStorageId()]);
834
+        if ($row = $query->fetch()) {
835
+            return $row['path'];
836
+        } else {
837
+            return false;
838
+        }
839
+    }
840
+
841
+    /**
842
+     * get the path of a file on this storage by it's file id
843
+     *
844
+     * @param int $id the file id of the file or folder to search
845
+     * @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
846
+     */
847
+    public function getPathById($id) {
848
+        $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
849
+        $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
850
+        if ($row = $result->fetch()) {
851
+            // Oracle stores empty strings as null...
852
+            if ($row['path'] === null) {
853
+                return '';
854
+            }
855
+            return $row['path'];
856
+        } else {
857
+            return null;
858
+        }
859
+    }
860
+
861
+    /**
862
+     * get the storage id of the storage for a file and the internal path of the file
863
+     * unlike getPathById this does not limit the search to files on this storage and
864
+     * instead does a global search in the cache table
865
+     *
866
+     * @param int $id
867
+     * @deprecated use getPathById() instead
868
+     * @return array first element holding the storage id, second the path
869
+     */
870
+    static public function getById($id) {
871
+        $connection = \OC::$server->getDatabaseConnection();
872
+        $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
873
+        $result = $connection->executeQuery($sql, array($id));
874
+        if ($row = $result->fetch()) {
875
+            $numericId = $row['storage'];
876
+            $path = $row['path'];
877
+        } else {
878
+            return null;
879
+        }
880
+
881
+        if ($id = Storage::getStorageId($numericId)) {
882
+            return array($id, $path);
883
+        } else {
884
+            return null;
885
+        }
886
+    }
887
+
888
+    /**
889
+     * normalize the given path
890
+     *
891
+     * @param string $path
892
+     * @return string
893
+     */
894
+    public function normalize($path) {
895
+
896
+        return trim(\OC_Util::normalizeUnicode($path), '/');
897
+    }
898 898
 }
Please login to merge, or discard this patch.