Completed
Pull Request — master (#5785)
by Robin
15:48
created
lib/private/Repair/NC13/RepairInvalidPaths.php 3 patches
Doc Comments   +6 added lines patch added patch discarded remove patch
@@ -79,6 +79,9 @@  discard block
 block discarded – undo
79 79
 		} while (count($rows) > 0);
80 80
 	}
81 81
 
82
+	/**
83
+	 * @param string $path
84
+	 */
82 85
 	private function getId($storage, $path) {
83 86
 		if (!$this->getIdQuery) {
84 87
 			$builder = $this->connection->getQueryBuilder();
@@ -95,6 +98,9 @@  discard block
 block discarded – undo
95 98
 		return $this->getIdQuery->execute()->fetchColumn();
96 99
 	}
97 100
 
101
+	/**
102
+	 * @param string $newPath
103
+	 */
98 104
 	private function update($fileid, $newPath, $newStorage) {
99 105
 		if (!$this->updateQuery) {
100 106
 			$builder = $this->connection->getQueryBuilder();
Please login to merge, or discard this patch.
Indentation   +140 added lines, -140 removed lines patch added patch discarded remove patch
@@ -29,144 +29,144 @@
 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
-	private function getInvalidEntries() {
55
-		$builder = $this->connection->getQueryBuilder();
56
-
57
-		$computedPath = $builder->func()->concat(
58
-			'p.path',
59
-			$builder->func()->concat($builder->createNamedParameter('/'), 'f.name')
60
-		);
61
-
62
-		//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;
63
-		$query = $builder->select('f.fileid', 'f.path', 'p.path AS parent_path', 'f.name', 'f.parent', 'f.storage', 'p.storage as parent_storage')
64
-			->from('filecache', 'f')
65
-			->innerJoin('f', 'filecache', 'p', $builder->expr()->andX(
66
-				$builder->expr()->eq('f.parent', 'p.fileid'),
67
-				$builder->expr()->neq('p.name', $builder->createNamedParameter(''))
68
-			))
69
-			->where($builder->expr()->neq('f.path', $computedPath))
70
-			->setMaxResults(self::MAX_ROWS);
71
-
72
-		do {
73
-			$result = $query->execute();
74
-			$rows = $result->fetchAll();
75
-			foreach ($rows as $row) {
76
-				yield $row;
77
-			}
78
-			$result->closeCursor();
79
-		} while (count($rows) > 0);
80
-	}
81
-
82
-	private function getId($storage, $path) {
83
-		if (!$this->getIdQuery) {
84
-			$builder = $this->connection->getQueryBuilder();
85
-
86
-			$this->getIdQuery = $builder->select('fileid')
87
-				->from('filecache')
88
-				->where($builder->expr()->eq('storage', $builder->createParameter('storage')))
89
-				->andWhere($builder->expr()->eq('path', $builder->createParameter('path')));
90
-		}
91
-
92
-		$this->getIdQuery->setParameter('storage', $storage, IQueryBuilder::PARAM_INT);
93
-		$this->getIdQuery->setParameter('path', $path);
94
-
95
-		return $this->getIdQuery->execute()->fetchColumn();
96
-	}
97
-
98
-	private function update($fileid, $newPath, $newStorage) {
99
-		if (!$this->updateQuery) {
100
-			$builder = $this->connection->getQueryBuilder();
101
-
102
-			$this->updateQuery = $builder->update('filecache')
103
-				->set('path', $builder->createParameter('newpath'))
104
-				->set('path_hash', $builder->func()->md5($builder->createParameter('newpath')))
105
-				->set('storage', $builder->createParameter('newstorage'))
106
-				->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
107
-		}
108
-
109
-		$this->updateQuery->setParameter('newpath', $newPath);
110
-		$this->updateQuery->setParameter('newstorage', $newStorage);
111
-		$this->updateQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
112
-
113
-		$this->updateQuery->execute();
114
-	}
115
-
116
-	private function reparent($from, $to) {
117
-		if (!$this->reparentQuery) {
118
-			$builder = $this->connection->getQueryBuilder();
119
-
120
-			$this->reparentQuery = $builder->update('filecache')
121
-				->set('parent', $builder->createParameter('to'))
122
-				->where($builder->expr()->eq('fileid', $builder->createParameter('from')));
123
-		}
124
-
125
-		$this->reparentQuery->setParameter('from', $from);
126
-		$this->reparentQuery->setParameter('to', $to);
127
-
128
-		$this->reparentQuery->execute();
129
-	}
130
-
131
-	private function delete($fileid) {
132
-		if (!$this->deleteQuery) {
133
-			$builder = $this->connection->getQueryBuilder();
134
-
135
-			$this->deleteQuery = $builder->delete('filecache')
136
-				->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
137
-		}
138
-
139
-		$this->deleteQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
140
-
141
-		$this->deleteQuery->execute();
142
-	}
143
-
144
-	private function repair() {
145
-		$this->connection->beginTransaction();
146
-		$entries = $this->getInvalidEntries();
147
-		$count = 0;
148
-		foreach ($entries as $entry) {
149
-			$count++;
150
-			$calculatedPath = $entry['parent_path'] . '/' . $entry['name'];
151
-			if ($newId = $this->getId($entry['parent_storage'], $calculatedPath)) {
152
-				// a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
153
-				$this->reparent($entry['fileid'], $newId);
154
-				$this->delete($entry['fileid']);
155
-			} else {
156
-				$this->update($entry['fileid'], $calculatedPath, $entry['parent_storage']);
157
-			}
158
-		}
159
-		$this->connection->commit();
160
-		return $count;
161
-	}
162
-
163
-	public function run(IOutput $output) {
164
-		$versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
165
-		// was added to 12.0.0.30 and 13.0.0.1
166
-		if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) {
167
-			$count = $this->repair();
168
-
169
-			$output->info('Repaired ' . $count . ' paths');
170
-		}
171
-	}
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
+    private function getInvalidEntries() {
55
+        $builder = $this->connection->getQueryBuilder();
56
+
57
+        $computedPath = $builder->func()->concat(
58
+            'p.path',
59
+            $builder->func()->concat($builder->createNamedParameter('/'), 'f.name')
60
+        );
61
+
62
+        //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;
63
+        $query = $builder->select('f.fileid', 'f.path', 'p.path AS parent_path', 'f.name', 'f.parent', 'f.storage', 'p.storage as parent_storage')
64
+            ->from('filecache', 'f')
65
+            ->innerJoin('f', 'filecache', 'p', $builder->expr()->andX(
66
+                $builder->expr()->eq('f.parent', 'p.fileid'),
67
+                $builder->expr()->neq('p.name', $builder->createNamedParameter(''))
68
+            ))
69
+            ->where($builder->expr()->neq('f.path', $computedPath))
70
+            ->setMaxResults(self::MAX_ROWS);
71
+
72
+        do {
73
+            $result = $query->execute();
74
+            $rows = $result->fetchAll();
75
+            foreach ($rows as $row) {
76
+                yield $row;
77
+            }
78
+            $result->closeCursor();
79
+        } while (count($rows) > 0);
80
+    }
81
+
82
+    private function getId($storage, $path) {
83
+        if (!$this->getIdQuery) {
84
+            $builder = $this->connection->getQueryBuilder();
85
+
86
+            $this->getIdQuery = $builder->select('fileid')
87
+                ->from('filecache')
88
+                ->where($builder->expr()->eq('storage', $builder->createParameter('storage')))
89
+                ->andWhere($builder->expr()->eq('path', $builder->createParameter('path')));
90
+        }
91
+
92
+        $this->getIdQuery->setParameter('storage', $storage, IQueryBuilder::PARAM_INT);
93
+        $this->getIdQuery->setParameter('path', $path);
94
+
95
+        return $this->getIdQuery->execute()->fetchColumn();
96
+    }
97
+
98
+    private function update($fileid, $newPath, $newStorage) {
99
+        if (!$this->updateQuery) {
100
+            $builder = $this->connection->getQueryBuilder();
101
+
102
+            $this->updateQuery = $builder->update('filecache')
103
+                ->set('path', $builder->createParameter('newpath'))
104
+                ->set('path_hash', $builder->func()->md5($builder->createParameter('newpath')))
105
+                ->set('storage', $builder->createParameter('newstorage'))
106
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
107
+        }
108
+
109
+        $this->updateQuery->setParameter('newpath', $newPath);
110
+        $this->updateQuery->setParameter('newstorage', $newStorage);
111
+        $this->updateQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
112
+
113
+        $this->updateQuery->execute();
114
+    }
115
+
116
+    private function reparent($from, $to) {
117
+        if (!$this->reparentQuery) {
118
+            $builder = $this->connection->getQueryBuilder();
119
+
120
+            $this->reparentQuery = $builder->update('filecache')
121
+                ->set('parent', $builder->createParameter('to'))
122
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('from')));
123
+        }
124
+
125
+        $this->reparentQuery->setParameter('from', $from);
126
+        $this->reparentQuery->setParameter('to', $to);
127
+
128
+        $this->reparentQuery->execute();
129
+    }
130
+
131
+    private function delete($fileid) {
132
+        if (!$this->deleteQuery) {
133
+            $builder = $this->connection->getQueryBuilder();
134
+
135
+            $this->deleteQuery = $builder->delete('filecache')
136
+                ->where($builder->expr()->eq('fileid', $builder->createParameter('fileid')));
137
+        }
138
+
139
+        $this->deleteQuery->setParameter('fileid', $fileid, IQueryBuilder::PARAM_INT);
140
+
141
+        $this->deleteQuery->execute();
142
+    }
143
+
144
+    private function repair() {
145
+        $this->connection->beginTransaction();
146
+        $entries = $this->getInvalidEntries();
147
+        $count = 0;
148
+        foreach ($entries as $entry) {
149
+            $count++;
150
+            $calculatedPath = $entry['parent_path'] . '/' . $entry['name'];
151
+            if ($newId = $this->getId($entry['parent_storage'], $calculatedPath)) {
152
+                // a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
153
+                $this->reparent($entry['fileid'], $newId);
154
+                $this->delete($entry['fileid']);
155
+            } else {
156
+                $this->update($entry['fileid'], $calculatedPath, $entry['parent_storage']);
157
+            }
158
+        }
159
+        $this->connection->commit();
160
+        return $count;
161
+    }
162
+
163
+    public function run(IOutput $output) {
164
+        $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
165
+        // was added to 12.0.0.30 and 13.0.0.1
166
+        if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) {
167
+            $count = $this->repair();
168
+
169
+            $output->info('Repaired ' . $count . ' paths');
170
+        }
171
+    }
172 172
 }
Please login to merge, or discard this patch.
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.
lib/private/Files/Cache/Cache.php 1 patch
Indentation   +828 added lines, -828 removed lines patch added patch discarded remove patch
@@ -56,842 +56,842 @@
 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
-	 */
506
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
507
-		if ($sourceCache instanceof Cache) {
508
-			// normalize source and target
509
-			$sourcePath = $this->normalize($sourcePath);
510
-			$targetPath = $this->normalize($targetPath);
511
-
512
-			$sourceData = $sourceCache->get($sourcePath);
513
-			$sourceId = $sourceData['fileid'];
514
-			$newParentId = $this->getParentId($targetPath);
515
-
516
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
517
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
518
-
519
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
520
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
521
-			}
522
-			if (is_null($targetStorageId) || $targetStorageId === false) {
523
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
524
-			}
525
-
526
-			$this->connection->beginTransaction();
527
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
528
-				//update all child entries
529
-				$sourceLength = strlen($sourcePath);
530
-				$query = $this->connection->getQueryBuilder();
531
-
532
-				$fun = $query->func();
533
-				$newPathFunction = $fun->concat(
534
-					$query->createNamedParameter($targetPath),
535
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
536
-				);
537
-				$query->update('filecache')
538
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
539
-					->set('path_hash', $fun->md5($newPathFunction))
540
-					->set('path', $newPathFunction)
541
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
542
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
543
-
544
-				try {
545
-					$query->execute();
546
-				} catch (\OC\DatabaseException $e) {
547
-					$this->connection->rollBack();
548
-					throw $e;
549
-				}
550
-			}
551
-
552
-			$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
553
-			$this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), \OC_Util::basename($targetPath), $newParentId, $sourceId));
554
-			$this->connection->commit();
555
-		} else {
556
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
557
-		}
558
-	}
559
-
560
-	/**
561
-	 * remove all entries for files that are stored on the storage from the cache
562
-	 */
563
-	public function clear() {
564
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
565
-		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
566
-
567
-		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
568
-		$this->connection->executeQuery($sql, array($this->storageId));
569
-	}
570
-
571
-	/**
572
-	 * Get the scan status of a file
573
-	 *
574
-	 * - Cache::NOT_FOUND: File is not in the cache
575
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
576
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
577
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
578
-	 *
579
-	 * @param string $file
580
-	 *
581
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
582
-	 */
583
-	public function getStatus($file) {
584
-		// normalize file
585
-		$file = $this->normalize($file);
586
-
587
-		$pathHash = md5($file);
588
-		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
589
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
590
-		if ($row = $result->fetch()) {
591
-			if ((int)$row['size'] === -1) {
592
-				return self::SHALLOW;
593
-			} else {
594
-				return self::COMPLETE;
595
-			}
596
-		} else {
597
-			if (isset($this->partial[$file])) {
598
-				return self::PARTIAL;
599
-			} else {
600
-				return self::NOT_FOUND;
601
-			}
602
-		}
603
-	}
604
-
605
-	/**
606
-	 * search for files matching $pattern
607
-	 *
608
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
609
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
610
-	 */
611
-	public function search($pattern) {
612
-		// normalize pattern
613
-		$pattern = $this->normalize($pattern);
614
-
615
-		if ($pattern === '%%') {
616
-			return [];
617
-		}
618
-
619
-
620
-		$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
+     */
506
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
507
+        if ($sourceCache instanceof Cache) {
508
+            // normalize source and target
509
+            $sourcePath = $this->normalize($sourcePath);
510
+            $targetPath = $this->normalize($targetPath);
511
+
512
+            $sourceData = $sourceCache->get($sourcePath);
513
+            $sourceId = $sourceData['fileid'];
514
+            $newParentId = $this->getParentId($targetPath);
515
+
516
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
517
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
518
+
519
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
520
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
521
+            }
522
+            if (is_null($targetStorageId) || $targetStorageId === false) {
523
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
524
+            }
525
+
526
+            $this->connection->beginTransaction();
527
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
528
+                //update all child entries
529
+                $sourceLength = strlen($sourcePath);
530
+                $query = $this->connection->getQueryBuilder();
531
+
532
+                $fun = $query->func();
533
+                $newPathFunction = $fun->concat(
534
+                    $query->createNamedParameter($targetPath),
535
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
536
+                );
537
+                $query->update('filecache')
538
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
539
+                    ->set('path_hash', $fun->md5($newPathFunction))
540
+                    ->set('path', $newPathFunction)
541
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
542
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
543
+
544
+                try {
545
+                    $query->execute();
546
+                } catch (\OC\DatabaseException $e) {
547
+                    $this->connection->rollBack();
548
+                    throw $e;
549
+                }
550
+            }
551
+
552
+            $sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
553
+            $this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), \OC_Util::basename($targetPath), $newParentId, $sourceId));
554
+            $this->connection->commit();
555
+        } else {
556
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
557
+        }
558
+    }
559
+
560
+    /**
561
+     * remove all entries for files that are stored on the storage from the cache
562
+     */
563
+    public function clear() {
564
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
565
+        $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
566
+
567
+        $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
568
+        $this->connection->executeQuery($sql, array($this->storageId));
569
+    }
570
+
571
+    /**
572
+     * Get the scan status of a file
573
+     *
574
+     * - Cache::NOT_FOUND: File is not in the cache
575
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
576
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
577
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
578
+     *
579
+     * @param string $file
580
+     *
581
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
582
+     */
583
+    public function getStatus($file) {
584
+        // normalize file
585
+        $file = $this->normalize($file);
586
+
587
+        $pathHash = md5($file);
588
+        $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
589
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
590
+        if ($row = $result->fetch()) {
591
+            if ((int)$row['size'] === -1) {
592
+                return self::SHALLOW;
593
+            } else {
594
+                return self::COMPLETE;
595
+            }
596
+        } else {
597
+            if (isset($this->partial[$file])) {
598
+                return self::PARTIAL;
599
+            } else {
600
+                return self::NOT_FOUND;
601
+            }
602
+        }
603
+    }
604
+
605
+    /**
606
+     * search for files matching $pattern
607
+     *
608
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
609
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
610
+     */
611
+    public function search($pattern) {
612
+        // normalize pattern
613
+        $pattern = $this->normalize($pattern);
614
+
615
+        if ($pattern === '%%') {
616
+            return [];
617
+        }
618
+
619
+
620
+        $sql = '
621 621
 			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
622 622
 				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
623 623
 				 `encrypted`, `etag`, `permissions`, `checksum`
624 624
 			FROM `*PREFIX*filecache`
625 625
 			WHERE `storage` = ? AND `name` ILIKE ?';
626
-		$result = $this->connection->executeQuery($sql,
627
-			[$this->getNumericStorageId(), $pattern]
628
-		);
629
-
630
-		return $this->searchResultToCacheEntries($result);
631
-	}
632
-
633
-	/**
634
-	 * @param Statement $result
635
-	 * @return CacheEntry[]
636
-	 */
637
-	private function searchResultToCacheEntries(Statement $result) {
638
-		$files = $result->fetchAll();
639
-
640
-		return array_map(function (array $data) {
641
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
642
-		}, $files);
643
-	}
644
-
645
-	/**
646
-	 * search for files by mimetype
647
-	 *
648
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
649
-	 *        where it will search for all mimetypes in the group ('image/*')
650
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
651
-	 */
652
-	public function searchByMime($mimetype) {
653
-		if (strpos($mimetype, '/')) {
654
-			$where = '`mimetype` = ?';
655
-		} else {
656
-			$where = '`mimepart` = ?';
657
-		}
658
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
626
+        $result = $this->connection->executeQuery($sql,
627
+            [$this->getNumericStorageId(), $pattern]
628
+        );
629
+
630
+        return $this->searchResultToCacheEntries($result);
631
+    }
632
+
633
+    /**
634
+     * @param Statement $result
635
+     * @return CacheEntry[]
636
+     */
637
+    private function searchResultToCacheEntries(Statement $result) {
638
+        $files = $result->fetchAll();
639
+
640
+        return array_map(function (array $data) {
641
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
642
+        }, $files);
643
+    }
644
+
645
+    /**
646
+     * search for files by mimetype
647
+     *
648
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
649
+     *        where it will search for all mimetypes in the group ('image/*')
650
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
651
+     */
652
+    public function searchByMime($mimetype) {
653
+        if (strpos($mimetype, '/')) {
654
+            $where = '`mimetype` = ?';
655
+        } else {
656
+            $where = '`mimepart` = ?';
657
+        }
658
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
659 659
 				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
660
-		$mimetype = $this->mimetypeLoader->getId($mimetype);
661
-		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
662
-
663
-		return $this->searchResultToCacheEntries($result);
664
-	}
665
-
666
-	public function searchQuery(ISearchQuery $searchQuery) {
667
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
668
-
669
-		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
670
-			->from('filecache', 'file');
671
-
672
-		$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
673
-
674
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
675
-			$query
676
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
677
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
678
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
679
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
680
-				))
681
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
682
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
683
-		}
684
-
685
-		$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
686
-
687
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
688
-
689
-		if ($searchQuery->getLimit()) {
690
-			$query->setMaxResults($searchQuery->getLimit());
691
-		}
692
-		if ($searchQuery->getOffset()) {
693
-			$query->setFirstResult($searchQuery->getOffset());
694
-		}
695
-
696
-		$result = $query->execute();
697
-		return $this->searchResultToCacheEntries($result);
698
-	}
699
-
700
-	/**
701
-	 * Search for files by tag of a given users.
702
-	 *
703
-	 * Note that every user can tag files differently.
704
-	 *
705
-	 * @param string|int $tag name or tag id
706
-	 * @param string $userId owner of the tags
707
-	 * @return ICacheEntry[] file data
708
-	 */
709
-	public function searchByTag($tag, $userId) {
710
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
711
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
712
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
713
-			'FROM `*PREFIX*filecache` `file`, ' .
714
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
715
-			'`*PREFIX*vcategory` `tag` ' .
716
-			// JOIN filecache to vcategory_to_object
717
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
718
-			// JOIN vcategory_to_object to vcategory
719
-			'AND `tagmap`.`type` = `tag`.`type` ' .
720
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
721
-			// conditions
722
-			'AND `file`.`storage` = ? ' .
723
-			'AND `tag`.`type` = \'files\' ' .
724
-			'AND `tag`.`uid` = ? ';
725
-		if (is_int($tag)) {
726
-			$sql .= 'AND `tag`.`id` = ? ';
727
-		} else {
728
-			$sql .= 'AND `tag`.`category` = ? ';
729
-		}
730
-		$result = $this->connection->executeQuery(
731
-			$sql,
732
-			[
733
-				$this->getNumericStorageId(),
734
-				$userId,
735
-				$tag
736
-			]
737
-		);
738
-
739
-		$files = $result->fetchAll();
740
-
741
-		return array_map(function (array $data) {
742
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
743
-		}, $files);
744
-	}
745
-
746
-	/**
747
-	 * Re-calculate the folder size and the size of all parent folders
748
-	 *
749
-	 * @param string|boolean $path
750
-	 * @param array $data (optional) meta data of the folder
751
-	 */
752
-	public function correctFolderSize($path, $data = null) {
753
-		$this->calculateFolderSize($path, $data);
754
-		if ($path !== '') {
755
-			$parent = dirname($path);
756
-			if ($parent === '.' or $parent === '/') {
757
-				$parent = '';
758
-			}
759
-			$this->correctFolderSize($parent);
760
-		}
761
-	}
762
-
763
-	/**
764
-	 * calculate the size of a folder and set it in the cache
765
-	 *
766
-	 * @param string $path
767
-	 * @param array $entry (optional) meta data of the folder
768
-	 * @return int
769
-	 */
770
-	public function calculateFolderSize($path, $entry = null) {
771
-		$totalSize = 0;
772
-		if (is_null($entry) or !isset($entry['fileid'])) {
773
-			$entry = $this->get($path);
774
-		}
775
-		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
776
-			$id = $entry['fileid'];
777
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
778
-				'FROM `*PREFIX*filecache` ' .
779
-				'WHERE `parent` = ? AND `storage` = ?';
780
-			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
781
-			if ($row = $result->fetch()) {
782
-				$result->closeCursor();
783
-				list($sum, $min) = array_values($row);
784
-				$sum = 0 + $sum;
785
-				$min = 0 + $min;
786
-				if ($min === -1) {
787
-					$totalSize = $min;
788
-				} else {
789
-					$totalSize = $sum;
790
-				}
791
-				$update = array();
792
-				if ($entry['size'] !== $totalSize) {
793
-					$update['size'] = $totalSize;
794
-				}
795
-				if (count($update) > 0) {
796
-					$this->update($id, $update);
797
-				}
798
-			} else {
799
-				$result->closeCursor();
800
-			}
801
-		}
802
-		return $totalSize;
803
-	}
804
-
805
-	/**
806
-	 * get all file ids on the files on the storage
807
-	 *
808
-	 * @return int[]
809
-	 */
810
-	public function getAll() {
811
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
812
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
813
-		$ids = array();
814
-		while ($row = $result->fetch()) {
815
-			$ids[] = $row['fileid'];
816
-		}
817
-		return $ids;
818
-	}
819
-
820
-	/**
821
-	 * find a folder in the cache which has not been fully scanned
822
-	 *
823
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
824
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
825
-	 * likely the folder where we stopped scanning previously
826
-	 *
827
-	 * @return string|bool the path of the folder or false when no folder matched
828
-	 */
829
-	public function getIncomplete() {
830
-		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
831
-			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
832
-		$query->execute([$this->getNumericStorageId()]);
833
-		if ($row = $query->fetch()) {
834
-			return $row['path'];
835
-		} else {
836
-			return false;
837
-		}
838
-	}
839
-
840
-	/**
841
-	 * get the path of a file on this storage by it's file id
842
-	 *
843
-	 * @param int $id the file id of the file or folder to search
844
-	 * @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
845
-	 */
846
-	public function getPathById($id) {
847
-		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
848
-		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
849
-		if ($row = $result->fetch()) {
850
-			// Oracle stores empty strings as null...
851
-			if ($row['path'] === null) {
852
-				return '';
853
-			}
854
-			return $row['path'];
855
-		} else {
856
-			return null;
857
-		}
858
-	}
859
-
860
-	/**
861
-	 * get the storage id of the storage for a file and the internal path of the file
862
-	 * unlike getPathById this does not limit the search to files on this storage and
863
-	 * instead does a global search in the cache table
864
-	 *
865
-	 * @param int $id
866
-	 * @deprecated use getPathById() instead
867
-	 * @return array first element holding the storage id, second the path
868
-	 */
869
-	static public function getById($id) {
870
-		$connection = \OC::$server->getDatabaseConnection();
871
-		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
872
-		$result = $connection->executeQuery($sql, array($id));
873
-		if ($row = $result->fetch()) {
874
-			$numericId = $row['storage'];
875
-			$path = $row['path'];
876
-		} else {
877
-			return null;
878
-		}
879
-
880
-		if ($id = Storage::getStorageId($numericId)) {
881
-			return array($id, $path);
882
-		} else {
883
-			return null;
884
-		}
885
-	}
886
-
887
-	/**
888
-	 * normalize the given path
889
-	 *
890
-	 * @param string $path
891
-	 * @return string
892
-	 */
893
-	public function normalize($path) {
894
-
895
-		return trim(\OC_Util::normalizeUnicode($path), '/');
896
-	}
660
+        $mimetype = $this->mimetypeLoader->getId($mimetype);
661
+        $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
662
+
663
+        return $this->searchResultToCacheEntries($result);
664
+    }
665
+
666
+    public function searchQuery(ISearchQuery $searchQuery) {
667
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
668
+
669
+        $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
670
+            ->from('filecache', 'file');
671
+
672
+        $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
673
+
674
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
675
+            $query
676
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
677
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
678
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
679
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
680
+                ))
681
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
682
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
683
+        }
684
+
685
+        $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
686
+
687
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
688
+
689
+        if ($searchQuery->getLimit()) {
690
+            $query->setMaxResults($searchQuery->getLimit());
691
+        }
692
+        if ($searchQuery->getOffset()) {
693
+            $query->setFirstResult($searchQuery->getOffset());
694
+        }
695
+
696
+        $result = $query->execute();
697
+        return $this->searchResultToCacheEntries($result);
698
+    }
699
+
700
+    /**
701
+     * Search for files by tag of a given users.
702
+     *
703
+     * Note that every user can tag files differently.
704
+     *
705
+     * @param string|int $tag name or tag id
706
+     * @param string $userId owner of the tags
707
+     * @return ICacheEntry[] file data
708
+     */
709
+    public function searchByTag($tag, $userId) {
710
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
711
+            '`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
712
+            '`encrypted`, `etag`, `permissions`, `checksum` ' .
713
+            'FROM `*PREFIX*filecache` `file`, ' .
714
+            '`*PREFIX*vcategory_to_object` `tagmap`, ' .
715
+            '`*PREFIX*vcategory` `tag` ' .
716
+            // JOIN filecache to vcategory_to_object
717
+            'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
718
+            // JOIN vcategory_to_object to vcategory
719
+            'AND `tagmap`.`type` = `tag`.`type` ' .
720
+            'AND `tagmap`.`categoryid` = `tag`.`id` ' .
721
+            // conditions
722
+            'AND `file`.`storage` = ? ' .
723
+            'AND `tag`.`type` = \'files\' ' .
724
+            'AND `tag`.`uid` = ? ';
725
+        if (is_int($tag)) {
726
+            $sql .= 'AND `tag`.`id` = ? ';
727
+        } else {
728
+            $sql .= 'AND `tag`.`category` = ? ';
729
+        }
730
+        $result = $this->connection->executeQuery(
731
+            $sql,
732
+            [
733
+                $this->getNumericStorageId(),
734
+                $userId,
735
+                $tag
736
+            ]
737
+        );
738
+
739
+        $files = $result->fetchAll();
740
+
741
+        return array_map(function (array $data) {
742
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
743
+        }, $files);
744
+    }
745
+
746
+    /**
747
+     * Re-calculate the folder size and the size of all parent folders
748
+     *
749
+     * @param string|boolean $path
750
+     * @param array $data (optional) meta data of the folder
751
+     */
752
+    public function correctFolderSize($path, $data = null) {
753
+        $this->calculateFolderSize($path, $data);
754
+        if ($path !== '') {
755
+            $parent = dirname($path);
756
+            if ($parent === '.' or $parent === '/') {
757
+                $parent = '';
758
+            }
759
+            $this->correctFolderSize($parent);
760
+        }
761
+    }
762
+
763
+    /**
764
+     * calculate the size of a folder and set it in the cache
765
+     *
766
+     * @param string $path
767
+     * @param array $entry (optional) meta data of the folder
768
+     * @return int
769
+     */
770
+    public function calculateFolderSize($path, $entry = null) {
771
+        $totalSize = 0;
772
+        if (is_null($entry) or !isset($entry['fileid'])) {
773
+            $entry = $this->get($path);
774
+        }
775
+        if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
776
+            $id = $entry['fileid'];
777
+            $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
778
+                'FROM `*PREFIX*filecache` ' .
779
+                'WHERE `parent` = ? AND `storage` = ?';
780
+            $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
781
+            if ($row = $result->fetch()) {
782
+                $result->closeCursor();
783
+                list($sum, $min) = array_values($row);
784
+                $sum = 0 + $sum;
785
+                $min = 0 + $min;
786
+                if ($min === -1) {
787
+                    $totalSize = $min;
788
+                } else {
789
+                    $totalSize = $sum;
790
+                }
791
+                $update = array();
792
+                if ($entry['size'] !== $totalSize) {
793
+                    $update['size'] = $totalSize;
794
+                }
795
+                if (count($update) > 0) {
796
+                    $this->update($id, $update);
797
+                }
798
+            } else {
799
+                $result->closeCursor();
800
+            }
801
+        }
802
+        return $totalSize;
803
+    }
804
+
805
+    /**
806
+     * get all file ids on the files on the storage
807
+     *
808
+     * @return int[]
809
+     */
810
+    public function getAll() {
811
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
812
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
813
+        $ids = array();
814
+        while ($row = $result->fetch()) {
815
+            $ids[] = $row['fileid'];
816
+        }
817
+        return $ids;
818
+    }
819
+
820
+    /**
821
+     * find a folder in the cache which has not been fully scanned
822
+     *
823
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
824
+     * use the one with the highest id gives the best result with the background scanner, since that is most
825
+     * likely the folder where we stopped scanning previously
826
+     *
827
+     * @return string|bool the path of the folder or false when no folder matched
828
+     */
829
+    public function getIncomplete() {
830
+        $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
831
+            . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
832
+        $query->execute([$this->getNumericStorageId()]);
833
+        if ($row = $query->fetch()) {
834
+            return $row['path'];
835
+        } else {
836
+            return false;
837
+        }
838
+    }
839
+
840
+    /**
841
+     * get the path of a file on this storage by it's file id
842
+     *
843
+     * @param int $id the file id of the file or folder to search
844
+     * @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
845
+     */
846
+    public function getPathById($id) {
847
+        $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
848
+        $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
849
+        if ($row = $result->fetch()) {
850
+            // Oracle stores empty strings as null...
851
+            if ($row['path'] === null) {
852
+                return '';
853
+            }
854
+            return $row['path'];
855
+        } else {
856
+            return null;
857
+        }
858
+    }
859
+
860
+    /**
861
+     * get the storage id of the storage for a file and the internal path of the file
862
+     * unlike getPathById this does not limit the search to files on this storage and
863
+     * instead does a global search in the cache table
864
+     *
865
+     * @param int $id
866
+     * @deprecated use getPathById() instead
867
+     * @return array first element holding the storage id, second the path
868
+     */
869
+    static public function getById($id) {
870
+        $connection = \OC::$server->getDatabaseConnection();
871
+        $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
872
+        $result = $connection->executeQuery($sql, array($id));
873
+        if ($row = $result->fetch()) {
874
+            $numericId = $row['storage'];
875
+            $path = $row['path'];
876
+        } else {
877
+            return null;
878
+        }
879
+
880
+        if ($id = Storage::getStorageId($numericId)) {
881
+            return array($id, $path);
882
+        } else {
883
+            return null;
884
+        }
885
+    }
886
+
887
+    /**
888
+     * normalize the given path
889
+     *
890
+     * @param string $path
891
+     * @return string
892
+     */
893
+    public function normalize($path) {
894
+
895
+        return trim(\OC_Util::normalizeUnicode($path), '/');
896
+    }
897 897
 }
Please login to merge, or discard this patch.