Completed
Pull Request — master (#5817)
by Lukas
28:34 queued 14:41
created
lib/private/Files/Cache/Propagator.php 2 patches
Indentation   +155 added lines, -155 removed lines patch added patch discarded remove patch
@@ -30,161 +30,161 @@
 block discarded – undo
30 30
  * Propagate etags and mtimes within the storage
31 31
  */
32 32
 class Propagator implements IPropagator {
33
-	private $inBatch = false;
34
-
35
-	private $batch = [];
36
-
37
-	/**
38
-	 * @var \OC\Files\Storage\Storage
39
-	 */
40
-	protected $storage;
41
-
42
-	/**
43
-	 * @var IDBConnection
44
-	 */
45
-	private $connection;
46
-
47
-	/**
48
-	 * @param \OC\Files\Storage\Storage $storage
49
-	 * @param IDBConnection $connection
50
-	 */
51
-	public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection) {
52
-		$this->storage = $storage;
53
-		$this->connection = $connection;
54
-	}
55
-
56
-
57
-	/**
58
-	 * @param string $internalPath
59
-	 * @param int $time
60
-	 * @param int $sizeDifference number of bytes the file has grown
61
-	 * @suppress SqlInjectionChecker
62
-	 */
63
-	public function propagateChange($internalPath, $time, $sizeDifference = 0) {
64
-		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
65
-
66
-		$parents = $this->getParents($internalPath);
67
-
68
-		if ($this->inBatch) {
69
-			foreach ($parents as $parent) {
70
-				$this->addToBatch($parent, $time, $sizeDifference);
71
-			}
72
-			return;
73
-		}
74
-
75
-		$parentHashes = array_map('md5', $parents);
76
-		$etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
77
-
78
-		$builder = $this->connection->getQueryBuilder();
79
-		$hashParams = array_map(function ($hash) use ($builder) {
80
-			return $builder->expr()->literal($hash);
81
-		}, $parentHashes);
82
-
83
-		$builder->update('filecache')
84
-			->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter((int)$time, IQueryBuilder::PARAM_INT) . ')'))
85
-			->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR))
86
-			->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
87
-			->andWhere($builder->expr()->in('path_hash', $hashParams));
88
-
89
-		$builder->execute();
90
-
91
-		if ($sizeDifference !== 0) {
92
-			// we need to do size separably so we can ignore entries with uncalculated size
93
-			$builder = $this->connection->getQueryBuilder();
94
-			$builder->update('filecache')
95
-				->set('size', $builder->createFunction('`size` + ' . $builder->createNamedParameter($sizeDifference)))
96
-				->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
97
-				->andWhere($builder->expr()->in('path_hash', $hashParams))
98
-				->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
99
-		}
100
-
101
-		$builder->execute();
102
-	}
103
-
104
-	protected function getParents($path) {
105
-		$parts = explode('/', $path);
106
-		$parent = '';
107
-		$parents = [];
108
-		foreach ($parts as $part) {
109
-			$parents[] = $parent;
110
-			$parent = trim($parent . '/' . $part, '/');
111
-		}
112
-		return $parents;
113
-	}
114
-
115
-	/**
116
-	 * Mark the beginning of a propagation batch
117
-	 *
118
-	 * Note that not all cache setups support propagation in which case this will be a noop
119
-	 *
120
-	 * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent
121
-	 * before the batch is committed.
122
-	 */
123
-	public function beginBatch() {
124
-		$this->inBatch = true;
125
-	}
126
-
127
-	private function addToBatch($internalPath, $time, $sizeDifference) {
128
-		if (!isset($this->batch[$internalPath])) {
129
-			$this->batch[$internalPath] = [
130
-				'hash' => md5($internalPath),
131
-				'time' => $time,
132
-				'size' => $sizeDifference
133
-			];
134
-		} else {
135
-			$this->batch[$internalPath]['size'] += $sizeDifference;
136
-			if ($time > $this->batch[$internalPath]['time']) {
137
-				$this->batch[$internalPath]['time'] = $time;
138
-			}
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Commit the active propagation batch
144
-	 * @suppress SqlInjectionChecker
145
-	 */
146
-	public function commitBatch() {
147
-		if (!$this->inBatch) {
148
-			throw new \BadMethodCallException('Not in batch');
149
-		}
150
-		$this->inBatch = false;
151
-
152
-		$this->connection->beginTransaction();
153
-
154
-		$query = $this->connection->getQueryBuilder();
155
-		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
156
-
157
-		$query->update('filecache')
158
-			->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')'))
159
-			->set('etag', $query->expr()->literal(uniqid()))
160
-			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
161
-			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
162
-
163
-		$sizeQuery = $this->connection->getQueryBuilder();
164
-		$sizeQuery->update('filecache')
165
-			->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size')))
166
-			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
167
-			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')))
168
-			->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
169
-
170
-		foreach ($this->batch as $item) {
171
-			$query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
172
-			$query->setParameter('hash', $item['hash']);
173
-
174
-			$query->execute();
175
-
176
-			if ($item['size']) {
177
-				$sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
178
-				$sizeQuery->setParameter('hash', $item['hash']);
179
-
180
-				$sizeQuery->execute();
181
-			}
182
-		}
183
-
184
-		$this->batch = [];
185
-
186
-		$this->connection->commit();
187
-	}
33
+    private $inBatch = false;
34
+
35
+    private $batch = [];
36
+
37
+    /**
38
+     * @var \OC\Files\Storage\Storage
39
+     */
40
+    protected $storage;
41
+
42
+    /**
43
+     * @var IDBConnection
44
+     */
45
+    private $connection;
46
+
47
+    /**
48
+     * @param \OC\Files\Storage\Storage $storage
49
+     * @param IDBConnection $connection
50
+     */
51
+    public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection) {
52
+        $this->storage = $storage;
53
+        $this->connection = $connection;
54
+    }
55
+
56
+
57
+    /**
58
+     * @param string $internalPath
59
+     * @param int $time
60
+     * @param int $sizeDifference number of bytes the file has grown
61
+     * @suppress SqlInjectionChecker
62
+     */
63
+    public function propagateChange($internalPath, $time, $sizeDifference = 0) {
64
+        $storageId = (int)$this->storage->getStorageCache()->getNumericId();
65
+
66
+        $parents = $this->getParents($internalPath);
67
+
68
+        if ($this->inBatch) {
69
+            foreach ($parents as $parent) {
70
+                $this->addToBatch($parent, $time, $sizeDifference);
71
+            }
72
+            return;
73
+        }
74
+
75
+        $parentHashes = array_map('md5', $parents);
76
+        $etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
77
+
78
+        $builder = $this->connection->getQueryBuilder();
79
+        $hashParams = array_map(function ($hash) use ($builder) {
80
+            return $builder->expr()->literal($hash);
81
+        }, $parentHashes);
82
+
83
+        $builder->update('filecache')
84
+            ->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter((int)$time, IQueryBuilder::PARAM_INT) . ')'))
85
+            ->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR))
86
+            ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
87
+            ->andWhere($builder->expr()->in('path_hash', $hashParams));
88
+
89
+        $builder->execute();
90
+
91
+        if ($sizeDifference !== 0) {
92
+            // we need to do size separably so we can ignore entries with uncalculated size
93
+            $builder = $this->connection->getQueryBuilder();
94
+            $builder->update('filecache')
95
+                ->set('size', $builder->createFunction('`size` + ' . $builder->createNamedParameter($sizeDifference)))
96
+                ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
97
+                ->andWhere($builder->expr()->in('path_hash', $hashParams))
98
+                ->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
99
+        }
100
+
101
+        $builder->execute();
102
+    }
103
+
104
+    protected function getParents($path) {
105
+        $parts = explode('/', $path);
106
+        $parent = '';
107
+        $parents = [];
108
+        foreach ($parts as $part) {
109
+            $parents[] = $parent;
110
+            $parent = trim($parent . '/' . $part, '/');
111
+        }
112
+        return $parents;
113
+    }
114
+
115
+    /**
116
+     * Mark the beginning of a propagation batch
117
+     *
118
+     * Note that not all cache setups support propagation in which case this will be a noop
119
+     *
120
+     * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent
121
+     * before the batch is committed.
122
+     */
123
+    public function beginBatch() {
124
+        $this->inBatch = true;
125
+    }
126
+
127
+    private function addToBatch($internalPath, $time, $sizeDifference) {
128
+        if (!isset($this->batch[$internalPath])) {
129
+            $this->batch[$internalPath] = [
130
+                'hash' => md5($internalPath),
131
+                'time' => $time,
132
+                'size' => $sizeDifference
133
+            ];
134
+        } else {
135
+            $this->batch[$internalPath]['size'] += $sizeDifference;
136
+            if ($time > $this->batch[$internalPath]['time']) {
137
+                $this->batch[$internalPath]['time'] = $time;
138
+            }
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Commit the active propagation batch
144
+     * @suppress SqlInjectionChecker
145
+     */
146
+    public function commitBatch() {
147
+        if (!$this->inBatch) {
148
+            throw new \BadMethodCallException('Not in batch');
149
+        }
150
+        $this->inBatch = false;
151
+
152
+        $this->connection->beginTransaction();
153
+
154
+        $query = $this->connection->getQueryBuilder();
155
+        $storageId = (int)$this->storage->getStorageCache()->getNumericId();
156
+
157
+        $query->update('filecache')
158
+            ->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')'))
159
+            ->set('etag', $query->expr()->literal(uniqid()))
160
+            ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
161
+            ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
162
+
163
+        $sizeQuery = $this->connection->getQueryBuilder();
164
+        $sizeQuery->update('filecache')
165
+            ->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size')))
166
+            ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
167
+            ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')))
168
+            ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
169
+
170
+        foreach ($this->batch as $item) {
171
+            $query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
172
+            $query->setParameter('hash', $item['hash']);
173
+
174
+            $query->execute();
175
+
176
+            if ($item['size']) {
177
+                $sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
178
+                $sizeQuery->setParameter('hash', $item['hash']);
179
+
180
+                $sizeQuery->execute();
181
+            }
182
+        }
183
+
184
+        $this->batch = [];
185
+
186
+        $this->connection->commit();
187
+    }
188 188
 
189 189
 
190 190
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -61,7 +61,7 @@  discard block
 block discarded – undo
61 61
 	 * @suppress SqlInjectionChecker
62 62
 	 */
63 63
 	public function propagateChange($internalPath, $time, $sizeDifference = 0) {
64
-		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
64
+		$storageId = (int) $this->storage->getStorageCache()->getNumericId();
65 65
 
66 66
 		$parents = $this->getParents($internalPath);
67 67
 
@@ -76,12 +76,12 @@  discard block
 block discarded – undo
76 76
 		$etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
77 77
 
78 78
 		$builder = $this->connection->getQueryBuilder();
79
-		$hashParams = array_map(function ($hash) use ($builder) {
79
+		$hashParams = array_map(function($hash) use ($builder) {
80 80
 			return $builder->expr()->literal($hash);
81 81
 		}, $parentHashes);
82 82
 
83 83
 		$builder->update('filecache')
84
-			->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter((int)$time, IQueryBuilder::PARAM_INT) . ')'))
84
+			->set('mtime', $builder->createFunction('GREATEST(`mtime`, '.$builder->createNamedParameter((int) $time, IQueryBuilder::PARAM_INT).')'))
85 85
 			->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR))
86 86
 			->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
87 87
 			->andWhere($builder->expr()->in('path_hash', $hashParams));
@@ -92,7 +92,7 @@  discard block
 block discarded – undo
92 92
 			// we need to do size separably so we can ignore entries with uncalculated size
93 93
 			$builder = $this->connection->getQueryBuilder();
94 94
 			$builder->update('filecache')
95
-				->set('size', $builder->createFunction('`size` + ' . $builder->createNamedParameter($sizeDifference)))
95
+				->set('size', $builder->createFunction('`size` + '.$builder->createNamedParameter($sizeDifference)))
96 96
 				->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
97 97
 				->andWhere($builder->expr()->in('path_hash', $hashParams))
98 98
 				->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
 		$parents = [];
108 108
 		foreach ($parts as $part) {
109 109
 			$parents[] = $parent;
110
-			$parent = trim($parent . '/' . $part, '/');
110
+			$parent = trim($parent.'/'.$part, '/');
111 111
 		}
112 112
 		return $parents;
113 113
 	}
@@ -152,17 +152,17 @@  discard block
 block discarded – undo
152 152
 		$this->connection->beginTransaction();
153 153
 
154 154
 		$query = $this->connection->getQueryBuilder();
155
-		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
155
+		$storageId = (int) $this->storage->getStorageCache()->getNumericId();
156 156
 
157 157
 		$query->update('filecache')
158
-			->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')'))
158
+			->set('mtime', $query->createFunction('GREATEST(`mtime`, '.$query->createParameter('time').')'))
159 159
 			->set('etag', $query->expr()->literal(uniqid()))
160 160
 			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
161 161
 			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
162 162
 
163 163
 		$sizeQuery = $this->connection->getQueryBuilder();
164 164
 		$sizeQuery->update('filecache')
165
-			->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size')))
165
+			->set('size', $sizeQuery->createFunction('`size` + '.$sizeQuery->createParameter('size')))
166 166
 			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
167 167
 			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')))
168 168
 			->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
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');
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');
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.
lib/private/Security/Bruteforce/Throttler.php 1 patch
Indentation   +208 added lines, -208 removed lines patch added patch discarded remove patch
@@ -43,212 +43,212 @@
 block discarded – undo
43 43
  * @package OC\Security\Bruteforce
44 44
  */
45 45
 class Throttler {
46
-	const LOGIN_ACTION = 'login';
47
-
48
-	/** @var IDBConnection */
49
-	private $db;
50
-	/** @var ITimeFactory */
51
-	private $timeFactory;
52
-	/** @var ILogger */
53
-	private $logger;
54
-	/** @var IConfig */
55
-	private $config;
56
-
57
-	/**
58
-	 * @param IDBConnection $db
59
-	 * @param ITimeFactory $timeFactory
60
-	 * @param ILogger $logger
61
-	 * @param IConfig $config
62
-	 */
63
-	public function __construct(IDBConnection $db,
64
-								ITimeFactory $timeFactory,
65
-								ILogger $logger,
66
-								IConfig $config) {
67
-		$this->db = $db;
68
-		$this->timeFactory = $timeFactory;
69
-		$this->logger = $logger;
70
-		$this->config = $config;
71
-	}
72
-
73
-	/**
74
-	 * Convert a number of seconds into the appropriate DateInterval
75
-	 *
76
-	 * @param int $expire
77
-	 * @return \DateInterval
78
-	 */
79
-	private function getCutoff($expire) {
80
-		$d1 = new \DateTime();
81
-		$d2 = clone $d1;
82
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
83
-		return $d2->diff($d1);
84
-	}
85
-
86
-	/**
87
-	 * Register a failed attempt to bruteforce a security control
88
-	 *
89
-	 * @param string $action
90
-	 * @param string $ip
91
-	 * @param array $metadata Optional metadata logged to the database
92
-	 * @suppress SqlInjectionChecker
93
-	 */
94
-	public function registerAttempt($action,
95
-									$ip,
96
-									array $metadata = []) {
97
-		// No need to log if the bruteforce protection is disabled
98
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
99
-			return;
100
-		}
101
-
102
-		$ipAddress = new IpAddress($ip);
103
-		$values = [
104
-			'action' => $action,
105
-			'occurred' => $this->timeFactory->getTime(),
106
-			'ip' => (string)$ipAddress,
107
-			'subnet' => $ipAddress->getSubnet(),
108
-			'metadata' => json_encode($metadata),
109
-		];
110
-
111
-		$this->logger->notice(
112
-			sprintf(
113
-				'Bruteforce attempt from "%s" detected for action "%s".',
114
-				$ip,
115
-				$action
116
-			),
117
-			[
118
-				'app' => 'core',
119
-			]
120
-		);
121
-
122
-		$qb = $this->db->getQueryBuilder();
123
-		$qb->insert('bruteforce_attempts');
124
-		foreach($values as $column => $value) {
125
-			$qb->setValue($column, $qb->createNamedParameter($value));
126
-		}
127
-		$qb->execute();
128
-	}
129
-
130
-	/**
131
-	 * Check if the IP is whitelisted
132
-	 *
133
-	 * @param string $ip
134
-	 * @return bool
135
-	 */
136
-	private function isIPWhitelisted($ip) {
137
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
138
-			return true;
139
-		}
140
-
141
-		$keys = $this->config->getAppKeys('bruteForce');
142
-		$keys = array_filter($keys, function($key) {
143
-			$regex = '/^whitelist_/S';
144
-			return preg_match($regex, $key) === 1;
145
-		});
146
-
147
-		if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
148
-			$type = 4;
149
-		} else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
150
-			$type = 6;
151
-		} else {
152
-			return false;
153
-		}
154
-
155
-		$ip = inet_pton($ip);
156
-
157
-		foreach ($keys as $key) {
158
-			$cidr = $this->config->getAppValue('bruteForce', $key, null);
159
-
160
-			$cx = explode('/', $cidr);
161
-			$addr = $cx[0];
162
-			$mask = (int)$cx[1];
163
-
164
-			// Do not compare ipv4 to ipv6
165
-			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
166
-				($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
167
-				continue;
168
-			}
169
-
170
-			$addr = inet_pton($addr);
171
-
172
-			$valid = true;
173
-			for($i = 0; $i < $mask; $i++) {
174
-				$part = ord($addr[(int)($i/8)]);
175
-				$orig = ord($ip[(int)($i/8)]);
176
-
177
-				$part = $part & (15 << (1 - ($i % 2)));
178
-				$orig = $orig & (15 << (1 - ($i % 2)));
179
-
180
-				if ($part !== $orig) {
181
-					$valid = false;
182
-					break;
183
-				}
184
-			}
185
-
186
-			if ($valid === true) {
187
-				return true;
188
-			}
189
-		}
190
-
191
-		return false;
192
-
193
-	}
194
-
195
-	/**
196
-	 * Get the throttling delay (in milliseconds)
197
-	 *
198
-	 * @param string $ip
199
-	 * @param string $action optionally filter by action
200
-	 * @return int
201
-	 */
202
-	public function getDelay($ip, $action = '') {
203
-		$ipAddress = new IpAddress($ip);
204
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
205
-			return 0;
206
-		}
207
-
208
-		$cutoffTime = (new \DateTime())
209
-			->sub($this->getCutoff(43200))
210
-			->getTimestamp();
211
-
212
-		$qb = $this->db->getQueryBuilder();
213
-		$qb->select('*')
214
-			->from('bruteforce_attempts')
215
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
216
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
217
-
218
-		if ($action !== '') {
219
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
220
-		}
221
-
222
-		$attempts = count($qb->execute()->fetchAll());
223
-
224
-		if ($attempts === 0) {
225
-			return 0;
226
-		}
227
-
228
-		$maxDelay = 30;
229
-		$firstDelay = 0.1;
230
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
231
-			// Don't ever overflow. Just assume the maxDelay time:s
232
-			$firstDelay = $maxDelay;
233
-		} else {
234
-			$firstDelay *= pow(2, $attempts);
235
-			if ($firstDelay > $maxDelay) {
236
-				$firstDelay = $maxDelay;
237
-			}
238
-		}
239
-		return (int) \ceil($firstDelay * 1000);
240
-	}
241
-
242
-	/**
243
-	 * Will sleep for the defined amount of time
244
-	 *
245
-	 * @param string $ip
246
-	 * @param string $action optionally filter by action
247
-	 * @return int the time spent sleeping
248
-	 */
249
-	public function sleepDelay($ip, $action = '') {
250
-		$delay = $this->getDelay($ip, $action);
251
-		usleep($delay * 1000);
252
-		return $delay;
253
-	}
46
+    const LOGIN_ACTION = 'login';
47
+
48
+    /** @var IDBConnection */
49
+    private $db;
50
+    /** @var ITimeFactory */
51
+    private $timeFactory;
52
+    /** @var ILogger */
53
+    private $logger;
54
+    /** @var IConfig */
55
+    private $config;
56
+
57
+    /**
58
+     * @param IDBConnection $db
59
+     * @param ITimeFactory $timeFactory
60
+     * @param ILogger $logger
61
+     * @param IConfig $config
62
+     */
63
+    public function __construct(IDBConnection $db,
64
+                                ITimeFactory $timeFactory,
65
+                                ILogger $logger,
66
+                                IConfig $config) {
67
+        $this->db = $db;
68
+        $this->timeFactory = $timeFactory;
69
+        $this->logger = $logger;
70
+        $this->config = $config;
71
+    }
72
+
73
+    /**
74
+     * Convert a number of seconds into the appropriate DateInterval
75
+     *
76
+     * @param int $expire
77
+     * @return \DateInterval
78
+     */
79
+    private function getCutoff($expire) {
80
+        $d1 = new \DateTime();
81
+        $d2 = clone $d1;
82
+        $d2->sub(new \DateInterval('PT' . $expire . 'S'));
83
+        return $d2->diff($d1);
84
+    }
85
+
86
+    /**
87
+     * Register a failed attempt to bruteforce a security control
88
+     *
89
+     * @param string $action
90
+     * @param string $ip
91
+     * @param array $metadata Optional metadata logged to the database
92
+     * @suppress SqlInjectionChecker
93
+     */
94
+    public function registerAttempt($action,
95
+                                    $ip,
96
+                                    array $metadata = []) {
97
+        // No need to log if the bruteforce protection is disabled
98
+        if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
99
+            return;
100
+        }
101
+
102
+        $ipAddress = new IpAddress($ip);
103
+        $values = [
104
+            'action' => $action,
105
+            'occurred' => $this->timeFactory->getTime(),
106
+            'ip' => (string)$ipAddress,
107
+            'subnet' => $ipAddress->getSubnet(),
108
+            'metadata' => json_encode($metadata),
109
+        ];
110
+
111
+        $this->logger->notice(
112
+            sprintf(
113
+                'Bruteforce attempt from "%s" detected for action "%s".',
114
+                $ip,
115
+                $action
116
+            ),
117
+            [
118
+                'app' => 'core',
119
+            ]
120
+        );
121
+
122
+        $qb = $this->db->getQueryBuilder();
123
+        $qb->insert('bruteforce_attempts');
124
+        foreach($values as $column => $value) {
125
+            $qb->setValue($column, $qb->createNamedParameter($value));
126
+        }
127
+        $qb->execute();
128
+    }
129
+
130
+    /**
131
+     * Check if the IP is whitelisted
132
+     *
133
+     * @param string $ip
134
+     * @return bool
135
+     */
136
+    private function isIPWhitelisted($ip) {
137
+        if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
138
+            return true;
139
+        }
140
+
141
+        $keys = $this->config->getAppKeys('bruteForce');
142
+        $keys = array_filter($keys, function($key) {
143
+            $regex = '/^whitelist_/S';
144
+            return preg_match($regex, $key) === 1;
145
+        });
146
+
147
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
148
+            $type = 4;
149
+        } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
150
+            $type = 6;
151
+        } else {
152
+            return false;
153
+        }
154
+
155
+        $ip = inet_pton($ip);
156
+
157
+        foreach ($keys as $key) {
158
+            $cidr = $this->config->getAppValue('bruteForce', $key, null);
159
+
160
+            $cx = explode('/', $cidr);
161
+            $addr = $cx[0];
162
+            $mask = (int)$cx[1];
163
+
164
+            // Do not compare ipv4 to ipv6
165
+            if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
166
+                ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
167
+                continue;
168
+            }
169
+
170
+            $addr = inet_pton($addr);
171
+
172
+            $valid = true;
173
+            for($i = 0; $i < $mask; $i++) {
174
+                $part = ord($addr[(int)($i/8)]);
175
+                $orig = ord($ip[(int)($i/8)]);
176
+
177
+                $part = $part & (15 << (1 - ($i % 2)));
178
+                $orig = $orig & (15 << (1 - ($i % 2)));
179
+
180
+                if ($part !== $orig) {
181
+                    $valid = false;
182
+                    break;
183
+                }
184
+            }
185
+
186
+            if ($valid === true) {
187
+                return true;
188
+            }
189
+        }
190
+
191
+        return false;
192
+
193
+    }
194
+
195
+    /**
196
+     * Get the throttling delay (in milliseconds)
197
+     *
198
+     * @param string $ip
199
+     * @param string $action optionally filter by action
200
+     * @return int
201
+     */
202
+    public function getDelay($ip, $action = '') {
203
+        $ipAddress = new IpAddress($ip);
204
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
205
+            return 0;
206
+        }
207
+
208
+        $cutoffTime = (new \DateTime())
209
+            ->sub($this->getCutoff(43200))
210
+            ->getTimestamp();
211
+
212
+        $qb = $this->db->getQueryBuilder();
213
+        $qb->select('*')
214
+            ->from('bruteforce_attempts')
215
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
216
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
217
+
218
+        if ($action !== '') {
219
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
220
+        }
221
+
222
+        $attempts = count($qb->execute()->fetchAll());
223
+
224
+        if ($attempts === 0) {
225
+            return 0;
226
+        }
227
+
228
+        $maxDelay = 30;
229
+        $firstDelay = 0.1;
230
+        if ($attempts > (8 * PHP_INT_SIZE - 1))  {
231
+            // Don't ever overflow. Just assume the maxDelay time:s
232
+            $firstDelay = $maxDelay;
233
+        } else {
234
+            $firstDelay *= pow(2, $attempts);
235
+            if ($firstDelay > $maxDelay) {
236
+                $firstDelay = $maxDelay;
237
+            }
238
+        }
239
+        return (int) \ceil($firstDelay * 1000);
240
+    }
241
+
242
+    /**
243
+     * Will sleep for the defined amount of time
244
+     *
245
+     * @param string $ip
246
+     * @param string $action optionally filter by action
247
+     * @return int the time spent sleeping
248
+     */
249
+    public function sleepDelay($ip, $action = '') {
250
+        $delay = $this->getDelay($ip, $action);
251
+        usleep($delay * 1000);
252
+        return $delay;
253
+    }
254 254
 }
Please login to merge, or discard this patch.
lib/private/DB/Connection.php 1 patch
Indentation   +401 added lines, -401 removed lines patch added patch discarded remove patch
@@ -42,405 +42,405 @@
 block discarded – undo
42 42
 use OCP\PreConditionNotMetException;
43 43
 
44 44
 class Connection extends \Doctrine\DBAL\Connection implements IDBConnection {
45
-	/**
46
-	 * @var string $tablePrefix
47
-	 */
48
-	protected $tablePrefix;
49
-
50
-	/**
51
-	 * @var \OC\DB\Adapter $adapter
52
-	 */
53
-	protected $adapter;
54
-
55
-	protected $lockedTable = null;
56
-
57
-	public function connect() {
58
-		try {
59
-			return parent::connect();
60
-		} catch (DBALException $e) {
61
-			// throw a new exception to prevent leaking info from the stacktrace
62
-			throw new DBALException('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
63
-		}
64
-	}
65
-
66
-	/**
67
-	 * Returns a QueryBuilder for the connection.
68
-	 *
69
-	 * @return \OCP\DB\QueryBuilder\IQueryBuilder
70
-	 */
71
-	public function getQueryBuilder() {
72
-		return new QueryBuilder(
73
-			$this,
74
-			\OC::$server->getSystemConfig(),
75
-			\OC::$server->getLogger()
76
-		);
77
-	}
78
-
79
-	/**
80
-	 * Gets the QueryBuilder for the connection.
81
-	 *
82
-	 * @return \Doctrine\DBAL\Query\QueryBuilder
83
-	 * @deprecated please use $this->getQueryBuilder() instead
84
-	 */
85
-	public function createQueryBuilder() {
86
-		$backtrace = $this->getCallerBacktrace();
87
-		\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
88
-		return parent::createQueryBuilder();
89
-	}
90
-
91
-	/**
92
-	 * Gets the ExpressionBuilder for the connection.
93
-	 *
94
-	 * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
95
-	 * @deprecated please use $this->getQueryBuilder()->expr() instead
96
-	 */
97
-	public function getExpressionBuilder() {
98
-		$backtrace = $this->getCallerBacktrace();
99
-		\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
100
-		return parent::getExpressionBuilder();
101
-	}
102
-
103
-	/**
104
-	 * Get the file and line that called the method where `getCallerBacktrace()` was used
105
-	 *
106
-	 * @return string
107
-	 */
108
-	protected function getCallerBacktrace() {
109
-		$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
110
-
111
-		// 0 is the method where we use `getCallerBacktrace`
112
-		// 1 is the target method which uses the method we want to log
113
-		if (isset($traces[1])) {
114
-			return $traces[1]['file'] . ':' . $traces[1]['line'];
115
-		}
116
-
117
-		return '';
118
-	}
119
-
120
-	/**
121
-	 * @return string
122
-	 */
123
-	public function getPrefix() {
124
-		return $this->tablePrefix;
125
-	}
126
-
127
-	/**
128
-	 * Initializes a new instance of the Connection class.
129
-	 *
130
-	 * @param array $params  The connection parameters.
131
-	 * @param \Doctrine\DBAL\Driver $driver
132
-	 * @param \Doctrine\DBAL\Configuration $config
133
-	 * @param \Doctrine\Common\EventManager $eventManager
134
-	 * @throws \Exception
135
-	 */
136
-	public function __construct(array $params, Driver $driver, Configuration $config = null,
137
-		EventManager $eventManager = null)
138
-	{
139
-		if (!isset($params['adapter'])) {
140
-			throw new \Exception('adapter not set');
141
-		}
142
-		if (!isset($params['tablePrefix'])) {
143
-			throw new \Exception('tablePrefix not set');
144
-		}
145
-		parent::__construct($params, $driver, $config, $eventManager);
146
-		$this->adapter = new $params['adapter']($this);
147
-		$this->tablePrefix = $params['tablePrefix'];
148
-
149
-		parent::setTransactionIsolation(parent::TRANSACTION_READ_COMMITTED);
150
-	}
151
-
152
-	/**
153
-	 * Prepares an SQL statement.
154
-	 *
155
-	 * @param string $statement The SQL statement to prepare.
156
-	 * @param int $limit
157
-	 * @param int $offset
158
-	 * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
159
-	 */
160
-	public function prepare( $statement, $limit=null, $offset=null ) {
161
-		if ($limit === -1) {
162
-			$limit = null;
163
-		}
164
-		if (!is_null($limit)) {
165
-			$platform = $this->getDatabasePlatform();
166
-			$statement = $platform->modifyLimitQuery($statement, $limit, $offset);
167
-		}
168
-		$statement = $this->replaceTablePrefix($statement);
169
-		$statement = $this->adapter->fixupStatement($statement);
170
-
171
-		return parent::prepare($statement);
172
-	}
173
-
174
-	/**
175
-	 * Executes an, optionally parametrized, SQL query.
176
-	 *
177
-	 * If the query is parametrized, a prepared statement is used.
178
-	 * If an SQLLogger is configured, the execution is logged.
179
-	 *
180
-	 * @param string                                      $query  The SQL query to execute.
181
-	 * @param array                                       $params The parameters to bind to the query, if any.
182
-	 * @param array                                       $types  The types the previous parameters are in.
183
-	 * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
184
-	 *
185
-	 * @return \Doctrine\DBAL\Driver\Statement The executed statement.
186
-	 *
187
-	 * @throws \Doctrine\DBAL\DBALException
188
-	 */
189
-	public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null)
190
-	{
191
-		$query = $this->replaceTablePrefix($query);
192
-		$query = $this->adapter->fixupStatement($query);
193
-		return parent::executeQuery($query, $params, $types, $qcp);
194
-	}
195
-
196
-	/**
197
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
198
-	 * and returns the number of affected rows.
199
-	 *
200
-	 * This method supports PDO binding types as well as DBAL mapping types.
201
-	 *
202
-	 * @param string $query  The SQL query.
203
-	 * @param array  $params The query parameters.
204
-	 * @param array  $types  The parameter types.
205
-	 *
206
-	 * @return integer The number of affected rows.
207
-	 *
208
-	 * @throws \Doctrine\DBAL\DBALException
209
-	 */
210
-	public function executeUpdate($query, array $params = array(), array $types = array())
211
-	{
212
-		$query = $this->replaceTablePrefix($query);
213
-		$query = $this->adapter->fixupStatement($query);
214
-		return parent::executeUpdate($query, $params, $types);
215
-	}
216
-
217
-	/**
218
-	 * Returns the ID of the last inserted row, or the last value from a sequence object,
219
-	 * depending on the underlying driver.
220
-	 *
221
-	 * Note: This method may not return a meaningful or consistent result across different drivers,
222
-	 * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
223
-	 * columns or sequences.
224
-	 *
225
-	 * @param string $seqName Name of the sequence object from which the ID should be returned.
226
-	 * @return string A string representation of the last inserted ID.
227
-	 */
228
-	public function lastInsertId($seqName = null) {
229
-		if ($seqName) {
230
-			$seqName = $this->replaceTablePrefix($seqName);
231
-		}
232
-		return $this->adapter->lastInsertId($seqName);
233
-	}
234
-
235
-	// internal use
236
-	public function realLastInsertId($seqName = null) {
237
-		return parent::lastInsertId($seqName);
238
-	}
239
-
240
-	/**
241
-	 * Insert a row if the matching row does not exists.
242
-	 *
243
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
244
-	 * @param array $input data that should be inserted into the table  (column name => value)
245
-	 * @param array|null $compare List of values that should be checked for "if not exists"
246
-	 *				If this is null or an empty array, all keys of $input will be compared
247
-	 *				Please note: text fields (clob) must not be used in the compare array
248
-	 * @return int number of inserted rows
249
-	 * @throws \Doctrine\DBAL\DBALException
250
-	 */
251
-	public function insertIfNotExist($table, $input, array $compare = null) {
252
-		return $this->adapter->insertIfNotExist($table, $input, $compare);
253
-	}
254
-
255
-	private function getType($value) {
256
-		if (is_bool($value)) {
257
-			return IQueryBuilder::PARAM_BOOL;
258
-		} else if (is_int($value)) {
259
-			return IQueryBuilder::PARAM_INT;
260
-		} else {
261
-			return IQueryBuilder::PARAM_STR;
262
-		}
263
-	}
264
-
265
-	/**
266
-	 * Insert or update a row value
267
-	 *
268
-	 * @param string $table
269
-	 * @param array $keys (column name => value)
270
-	 * @param array $values (column name => value)
271
-	 * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
272
-	 * @return int number of new rows
273
-	 * @throws \Doctrine\DBAL\DBALException
274
-	 * @throws PreConditionNotMetException
275
-	 * @suppress SqlInjectionChecker
276
-	 */
277
-	public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
278
-		try {
279
-			$insertQb = $this->getQueryBuilder();
280
-			$insertQb->insert($table)
281
-				->values(
282
-					array_map(function($value) use ($insertQb) {
283
-						return $insertQb->createNamedParameter($value, $this->getType($value));
284
-					}, array_merge($keys, $values))
285
-				);
286
-			return $insertQb->execute();
287
-		} catch (ConstraintViolationException $e) {
288
-			// value already exists, try update
289
-			$updateQb = $this->getQueryBuilder();
290
-			$updateQb->update($table);
291
-			foreach ($values as $name => $value) {
292
-				$updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
293
-			}
294
-			$where = $updateQb->expr()->andX();
295
-			$whereValues = array_merge($keys, $updatePreconditionValues);
296
-			foreach ($whereValues as $name => $value) {
297
-				$where->add($updateQb->expr()->eq(
298
-					$name,
299
-					$updateQb->createNamedParameter($value, $this->getType($value)),
300
-					$this->getType($value)
301
-				));
302
-			}
303
-			$updateQb->where($where);
304
-			$affected = $updateQb->execute();
305
-
306
-			if ($affected === 0 && !empty($updatePreconditionValues)) {
307
-				throw new PreConditionNotMetException();
308
-			}
309
-
310
-			return 0;
311
-		}
312
-	}
313
-
314
-	/**
315
-	 * Create an exclusive read+write lock on a table
316
-	 *
317
-	 * @param string $tableName
318
-	 * @throws \BadMethodCallException When trying to acquire a second lock
319
-	 * @since 9.1.0
320
-	 */
321
-	public function lockTable($tableName) {
322
-		if ($this->lockedTable !== null) {
323
-			throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
324
-		}
325
-
326
-		$tableName = $this->tablePrefix . $tableName;
327
-		$this->lockedTable = $tableName;
328
-		$this->adapter->lockTable($tableName);
329
-	}
330
-
331
-	/**
332
-	 * Release a previous acquired lock again
333
-	 *
334
-	 * @since 9.1.0
335
-	 */
336
-	public function unlockTable() {
337
-		$this->adapter->unlockTable();
338
-		$this->lockedTable = null;
339
-	}
340
-
341
-	/**
342
-	 * returns the error code and message as a string for logging
343
-	 * works with DoctrineException
344
-	 * @return string
345
-	 */
346
-	public function getError() {
347
-		$msg = $this->errorCode() . ': ';
348
-		$errorInfo = $this->errorInfo();
349
-		if (is_array($errorInfo)) {
350
-			$msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
351
-			$msg .= 'Driver Code = '.$errorInfo[1] . ', ';
352
-			$msg .= 'Driver Message = '.$errorInfo[2];
353
-		}
354
-		return $msg;
355
-	}
356
-
357
-	/**
358
-	 * Drop a table from the database if it exists
359
-	 *
360
-	 * @param string $table table name without the prefix
361
-	 */
362
-	public function dropTable($table) {
363
-		$table = $this->tablePrefix . trim($table);
364
-		$schema = $this->getSchemaManager();
365
-		if($schema->tablesExist(array($table))) {
366
-			$schema->dropTable($table);
367
-		}
368
-	}
369
-
370
-	/**
371
-	 * Check if a table exists
372
-	 *
373
-	 * @param string $table table name without the prefix
374
-	 * @return bool
375
-	 */
376
-	public function tableExists($table){
377
-		$table = $this->tablePrefix . trim($table);
378
-		$schema = $this->getSchemaManager();
379
-		return $schema->tablesExist(array($table));
380
-	}
381
-
382
-	// internal use
383
-	/**
384
-	 * @param string $statement
385
-	 * @return string
386
-	 */
387
-	protected function replaceTablePrefix($statement) {
388
-		return str_replace( '*PREFIX*', $this->tablePrefix, $statement );
389
-	}
390
-
391
-	/**
392
-	 * Check if a transaction is active
393
-	 *
394
-	 * @return bool
395
-	 * @since 8.2.0
396
-	 */
397
-	public function inTransaction() {
398
-		return $this->getTransactionNestingLevel() > 0;
399
-	}
400
-
401
-	/**
402
-	 * Espace a parameter to be used in a LIKE query
403
-	 *
404
-	 * @param string $param
405
-	 * @return string
406
-	 */
407
-	public function escapeLikeParameter($param) {
408
-		return addcslashes($param, '\\_%');
409
-	}
410
-
411
-	/**
412
-	 * Check whether or not the current database support 4byte wide unicode
413
-	 *
414
-	 * @return bool
415
-	 * @since 11.0.0
416
-	 */
417
-	public function supports4ByteText() {
418
-		if (!$this->getDatabasePlatform() instanceof MySqlPlatform) {
419
-			return true;
420
-		}
421
-		return $this->getParams()['charset'] === 'utf8mb4';
422
-	}
423
-
424
-
425
-	/**
426
-	 * Create the schema of the connected database
427
-	 *
428
-	 * @return Schema
429
-	 */
430
-	public function createSchema() {
431
-		$schemaManager = new MDB2SchemaManager($this);
432
-		$migrator = $schemaManager->getMigrator();
433
-		return $migrator->createSchema();
434
-	}
435
-
436
-	/**
437
-	 * Migrate the database to the given schema
438
-	 *
439
-	 * @param Schema $toSchema
440
-	 */
441
-	public function migrateToSchema(Schema $toSchema) {
442
-		$schemaManager = new MDB2SchemaManager($this);
443
-		$migrator = $schemaManager->getMigrator();
444
-		$migrator->migrate($toSchema);
445
-	}
45
+    /**
46
+     * @var string $tablePrefix
47
+     */
48
+    protected $tablePrefix;
49
+
50
+    /**
51
+     * @var \OC\DB\Adapter $adapter
52
+     */
53
+    protected $adapter;
54
+
55
+    protected $lockedTable = null;
56
+
57
+    public function connect() {
58
+        try {
59
+            return parent::connect();
60
+        } catch (DBALException $e) {
61
+            // throw a new exception to prevent leaking info from the stacktrace
62
+            throw new DBALException('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
63
+        }
64
+    }
65
+
66
+    /**
67
+     * Returns a QueryBuilder for the connection.
68
+     *
69
+     * @return \OCP\DB\QueryBuilder\IQueryBuilder
70
+     */
71
+    public function getQueryBuilder() {
72
+        return new QueryBuilder(
73
+            $this,
74
+            \OC::$server->getSystemConfig(),
75
+            \OC::$server->getLogger()
76
+        );
77
+    }
78
+
79
+    /**
80
+     * Gets the QueryBuilder for the connection.
81
+     *
82
+     * @return \Doctrine\DBAL\Query\QueryBuilder
83
+     * @deprecated please use $this->getQueryBuilder() instead
84
+     */
85
+    public function createQueryBuilder() {
86
+        $backtrace = $this->getCallerBacktrace();
87
+        \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
88
+        return parent::createQueryBuilder();
89
+    }
90
+
91
+    /**
92
+     * Gets the ExpressionBuilder for the connection.
93
+     *
94
+     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
95
+     * @deprecated please use $this->getQueryBuilder()->expr() instead
96
+     */
97
+    public function getExpressionBuilder() {
98
+        $backtrace = $this->getCallerBacktrace();
99
+        \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
100
+        return parent::getExpressionBuilder();
101
+    }
102
+
103
+    /**
104
+     * Get the file and line that called the method where `getCallerBacktrace()` was used
105
+     *
106
+     * @return string
107
+     */
108
+    protected function getCallerBacktrace() {
109
+        $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
110
+
111
+        // 0 is the method where we use `getCallerBacktrace`
112
+        // 1 is the target method which uses the method we want to log
113
+        if (isset($traces[1])) {
114
+            return $traces[1]['file'] . ':' . $traces[1]['line'];
115
+        }
116
+
117
+        return '';
118
+    }
119
+
120
+    /**
121
+     * @return string
122
+     */
123
+    public function getPrefix() {
124
+        return $this->tablePrefix;
125
+    }
126
+
127
+    /**
128
+     * Initializes a new instance of the Connection class.
129
+     *
130
+     * @param array $params  The connection parameters.
131
+     * @param \Doctrine\DBAL\Driver $driver
132
+     * @param \Doctrine\DBAL\Configuration $config
133
+     * @param \Doctrine\Common\EventManager $eventManager
134
+     * @throws \Exception
135
+     */
136
+    public function __construct(array $params, Driver $driver, Configuration $config = null,
137
+        EventManager $eventManager = null)
138
+    {
139
+        if (!isset($params['adapter'])) {
140
+            throw new \Exception('adapter not set');
141
+        }
142
+        if (!isset($params['tablePrefix'])) {
143
+            throw new \Exception('tablePrefix not set');
144
+        }
145
+        parent::__construct($params, $driver, $config, $eventManager);
146
+        $this->adapter = new $params['adapter']($this);
147
+        $this->tablePrefix = $params['tablePrefix'];
148
+
149
+        parent::setTransactionIsolation(parent::TRANSACTION_READ_COMMITTED);
150
+    }
151
+
152
+    /**
153
+     * Prepares an SQL statement.
154
+     *
155
+     * @param string $statement The SQL statement to prepare.
156
+     * @param int $limit
157
+     * @param int $offset
158
+     * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
159
+     */
160
+    public function prepare( $statement, $limit=null, $offset=null ) {
161
+        if ($limit === -1) {
162
+            $limit = null;
163
+        }
164
+        if (!is_null($limit)) {
165
+            $platform = $this->getDatabasePlatform();
166
+            $statement = $platform->modifyLimitQuery($statement, $limit, $offset);
167
+        }
168
+        $statement = $this->replaceTablePrefix($statement);
169
+        $statement = $this->adapter->fixupStatement($statement);
170
+
171
+        return parent::prepare($statement);
172
+    }
173
+
174
+    /**
175
+     * Executes an, optionally parametrized, SQL query.
176
+     *
177
+     * If the query is parametrized, a prepared statement is used.
178
+     * If an SQLLogger is configured, the execution is logged.
179
+     *
180
+     * @param string                                      $query  The SQL query to execute.
181
+     * @param array                                       $params The parameters to bind to the query, if any.
182
+     * @param array                                       $types  The types the previous parameters are in.
183
+     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
184
+     *
185
+     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
186
+     *
187
+     * @throws \Doctrine\DBAL\DBALException
188
+     */
189
+    public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null)
190
+    {
191
+        $query = $this->replaceTablePrefix($query);
192
+        $query = $this->adapter->fixupStatement($query);
193
+        return parent::executeQuery($query, $params, $types, $qcp);
194
+    }
195
+
196
+    /**
197
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
198
+     * and returns the number of affected rows.
199
+     *
200
+     * This method supports PDO binding types as well as DBAL mapping types.
201
+     *
202
+     * @param string $query  The SQL query.
203
+     * @param array  $params The query parameters.
204
+     * @param array  $types  The parameter types.
205
+     *
206
+     * @return integer The number of affected rows.
207
+     *
208
+     * @throws \Doctrine\DBAL\DBALException
209
+     */
210
+    public function executeUpdate($query, array $params = array(), array $types = array())
211
+    {
212
+        $query = $this->replaceTablePrefix($query);
213
+        $query = $this->adapter->fixupStatement($query);
214
+        return parent::executeUpdate($query, $params, $types);
215
+    }
216
+
217
+    /**
218
+     * Returns the ID of the last inserted row, or the last value from a sequence object,
219
+     * depending on the underlying driver.
220
+     *
221
+     * Note: This method may not return a meaningful or consistent result across different drivers,
222
+     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
223
+     * columns or sequences.
224
+     *
225
+     * @param string $seqName Name of the sequence object from which the ID should be returned.
226
+     * @return string A string representation of the last inserted ID.
227
+     */
228
+    public function lastInsertId($seqName = null) {
229
+        if ($seqName) {
230
+            $seqName = $this->replaceTablePrefix($seqName);
231
+        }
232
+        return $this->adapter->lastInsertId($seqName);
233
+    }
234
+
235
+    // internal use
236
+    public function realLastInsertId($seqName = null) {
237
+        return parent::lastInsertId($seqName);
238
+    }
239
+
240
+    /**
241
+     * Insert a row if the matching row does not exists.
242
+     *
243
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
244
+     * @param array $input data that should be inserted into the table  (column name => value)
245
+     * @param array|null $compare List of values that should be checked for "if not exists"
246
+     *				If this is null or an empty array, all keys of $input will be compared
247
+     *				Please note: text fields (clob) must not be used in the compare array
248
+     * @return int number of inserted rows
249
+     * @throws \Doctrine\DBAL\DBALException
250
+     */
251
+    public function insertIfNotExist($table, $input, array $compare = null) {
252
+        return $this->adapter->insertIfNotExist($table, $input, $compare);
253
+    }
254
+
255
+    private function getType($value) {
256
+        if (is_bool($value)) {
257
+            return IQueryBuilder::PARAM_BOOL;
258
+        } else if (is_int($value)) {
259
+            return IQueryBuilder::PARAM_INT;
260
+        } else {
261
+            return IQueryBuilder::PARAM_STR;
262
+        }
263
+    }
264
+
265
+    /**
266
+     * Insert or update a row value
267
+     *
268
+     * @param string $table
269
+     * @param array $keys (column name => value)
270
+     * @param array $values (column name => value)
271
+     * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
272
+     * @return int number of new rows
273
+     * @throws \Doctrine\DBAL\DBALException
274
+     * @throws PreConditionNotMetException
275
+     * @suppress SqlInjectionChecker
276
+     */
277
+    public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
278
+        try {
279
+            $insertQb = $this->getQueryBuilder();
280
+            $insertQb->insert($table)
281
+                ->values(
282
+                    array_map(function($value) use ($insertQb) {
283
+                        return $insertQb->createNamedParameter($value, $this->getType($value));
284
+                    }, array_merge($keys, $values))
285
+                );
286
+            return $insertQb->execute();
287
+        } catch (ConstraintViolationException $e) {
288
+            // value already exists, try update
289
+            $updateQb = $this->getQueryBuilder();
290
+            $updateQb->update($table);
291
+            foreach ($values as $name => $value) {
292
+                $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
293
+            }
294
+            $where = $updateQb->expr()->andX();
295
+            $whereValues = array_merge($keys, $updatePreconditionValues);
296
+            foreach ($whereValues as $name => $value) {
297
+                $where->add($updateQb->expr()->eq(
298
+                    $name,
299
+                    $updateQb->createNamedParameter($value, $this->getType($value)),
300
+                    $this->getType($value)
301
+                ));
302
+            }
303
+            $updateQb->where($where);
304
+            $affected = $updateQb->execute();
305
+
306
+            if ($affected === 0 && !empty($updatePreconditionValues)) {
307
+                throw new PreConditionNotMetException();
308
+            }
309
+
310
+            return 0;
311
+        }
312
+    }
313
+
314
+    /**
315
+     * Create an exclusive read+write lock on a table
316
+     *
317
+     * @param string $tableName
318
+     * @throws \BadMethodCallException When trying to acquire a second lock
319
+     * @since 9.1.0
320
+     */
321
+    public function lockTable($tableName) {
322
+        if ($this->lockedTable !== null) {
323
+            throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
324
+        }
325
+
326
+        $tableName = $this->tablePrefix . $tableName;
327
+        $this->lockedTable = $tableName;
328
+        $this->adapter->lockTable($tableName);
329
+    }
330
+
331
+    /**
332
+     * Release a previous acquired lock again
333
+     *
334
+     * @since 9.1.0
335
+     */
336
+    public function unlockTable() {
337
+        $this->adapter->unlockTable();
338
+        $this->lockedTable = null;
339
+    }
340
+
341
+    /**
342
+     * returns the error code and message as a string for logging
343
+     * works with DoctrineException
344
+     * @return string
345
+     */
346
+    public function getError() {
347
+        $msg = $this->errorCode() . ': ';
348
+        $errorInfo = $this->errorInfo();
349
+        if (is_array($errorInfo)) {
350
+            $msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
351
+            $msg .= 'Driver Code = '.$errorInfo[1] . ', ';
352
+            $msg .= 'Driver Message = '.$errorInfo[2];
353
+        }
354
+        return $msg;
355
+    }
356
+
357
+    /**
358
+     * Drop a table from the database if it exists
359
+     *
360
+     * @param string $table table name without the prefix
361
+     */
362
+    public function dropTable($table) {
363
+        $table = $this->tablePrefix . trim($table);
364
+        $schema = $this->getSchemaManager();
365
+        if($schema->tablesExist(array($table))) {
366
+            $schema->dropTable($table);
367
+        }
368
+    }
369
+
370
+    /**
371
+     * Check if a table exists
372
+     *
373
+     * @param string $table table name without the prefix
374
+     * @return bool
375
+     */
376
+    public function tableExists($table){
377
+        $table = $this->tablePrefix . trim($table);
378
+        $schema = $this->getSchemaManager();
379
+        return $schema->tablesExist(array($table));
380
+    }
381
+
382
+    // internal use
383
+    /**
384
+     * @param string $statement
385
+     * @return string
386
+     */
387
+    protected function replaceTablePrefix($statement) {
388
+        return str_replace( '*PREFIX*', $this->tablePrefix, $statement );
389
+    }
390
+
391
+    /**
392
+     * Check if a transaction is active
393
+     *
394
+     * @return bool
395
+     * @since 8.2.0
396
+     */
397
+    public function inTransaction() {
398
+        return $this->getTransactionNestingLevel() > 0;
399
+    }
400
+
401
+    /**
402
+     * Espace a parameter to be used in a LIKE query
403
+     *
404
+     * @param string $param
405
+     * @return string
406
+     */
407
+    public function escapeLikeParameter($param) {
408
+        return addcslashes($param, '\\_%');
409
+    }
410
+
411
+    /**
412
+     * Check whether or not the current database support 4byte wide unicode
413
+     *
414
+     * @return bool
415
+     * @since 11.0.0
416
+     */
417
+    public function supports4ByteText() {
418
+        if (!$this->getDatabasePlatform() instanceof MySqlPlatform) {
419
+            return true;
420
+        }
421
+        return $this->getParams()['charset'] === 'utf8mb4';
422
+    }
423
+
424
+
425
+    /**
426
+     * Create the schema of the connected database
427
+     *
428
+     * @return Schema
429
+     */
430
+    public function createSchema() {
431
+        $schemaManager = new MDB2SchemaManager($this);
432
+        $migrator = $schemaManager->getMigrator();
433
+        return $migrator->createSchema();
434
+    }
435
+
436
+    /**
437
+     * Migrate the database to the given schema
438
+     *
439
+     * @param Schema $toSchema
440
+     */
441
+    public function migrateToSchema(Schema $toSchema) {
442
+        $schemaManager = new MDB2SchemaManager($this);
443
+        $migrator = $schemaManager->getMigrator();
444
+        $migrator->migrate($toSchema);
445
+    }
446 446
 }
Please login to merge, or discard this patch.
lib/private/Repair/OldGroupMembershipShares.php 1 patch
Indentation   +85 added lines, -85 removed lines patch added patch discarded remove patch
@@ -31,89 +31,89 @@
 block discarded – undo
31 31
 
32 32
 class OldGroupMembershipShares implements IRepairStep {
33 33
 
34
-	/** @var \OCP\IDBConnection */
35
-	protected $connection;
36
-
37
-	/** @var \OCP\IGroupManager */
38
-	protected $groupManager;
39
-
40
-	/**
41
-	 * @var array [gid => [uid => (bool)]]
42
-	 */
43
-	protected $memberships;
44
-
45
-	/**
46
-	 * @param IDBConnection $connection
47
-	 * @param IGroupManager $groupManager
48
-	 */
49
-	public function __construct(IDBConnection $connection, IGroupManager $groupManager) {
50
-		$this->connection = $connection;
51
-		$this->groupManager = $groupManager;
52
-	}
53
-
54
-	/**
55
-	 * Returns the step's name
56
-	 *
57
-	 * @return string
58
-	 */
59
-	public function getName() {
60
-		return 'Remove shares of old group memberships';
61
-	}
62
-
63
-	/**
64
-	 * Run repair step.
65
-	 * Must throw exception on error.
66
-	 *
67
-	 * @throws \Exception in case of failure
68
-	 * @suppress SqlInjectionChecker
69
-	 */
70
-	public function run(IOutput $output) {
71
-		$deletedEntries = 0;
72
-
73
-		$query = $this->connection->getQueryBuilder();
74
-		$query->select('s1.id')->selectAlias('s1.share_with', 'user')->selectAlias('s2.share_with', 'group')
75
-			->from('share', 's1')
76
-			->where($query->expr()->isNotNull('s1.parent'))
77
-				// \OC\Share\Constant::$shareTypeGroupUserUnique === 2
78
-				->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2)))
79
-				->andWhere($query->expr()->isNotNull('s2.id'))
80
-				->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(Share::SHARE_TYPE_GROUP)))
81
-			->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'));
82
-
83
-		$deleteQuery = $this->connection->getQueryBuilder();
84
-		$deleteQuery->delete('share')
85
-			->where($query->expr()->eq('id', $deleteQuery->createParameter('share')));
86
-
87
-		$result = $query->execute();
88
-		while ($row = $result->fetch()) {
89
-			if (!$this->isMember($row['group'], $row['user'])) {
90
-				$deletedEntries += $deleteQuery->setParameter('share', (int) $row['id'])
91
-					->execute();
92
-			}
93
-		}
94
-		$result->closeCursor();
95
-
96
-		if ($deletedEntries) {
97
-			$output->info('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore');
98
-		}
99
-	}
100
-
101
-	/**
102
-	 * @param string $gid
103
-	 * @param string $uid
104
-	 * @return bool
105
-	 */
106
-	protected function isMember($gid, $uid) {
107
-		if (isset($this->memberships[$gid][$uid])) {
108
-			return $this->memberships[$gid][$uid];
109
-		}
110
-
111
-		$isMember = $this->groupManager->isInGroup($uid, $gid);
112
-		if (!isset($this->memberships[$gid])) {
113
-			$this->memberships[$gid] = [];
114
-		}
115
-		$this->memberships[$gid][$uid] = $isMember;
116
-
117
-		return $isMember;
118
-	}
34
+    /** @var \OCP\IDBConnection */
35
+    protected $connection;
36
+
37
+    /** @var \OCP\IGroupManager */
38
+    protected $groupManager;
39
+
40
+    /**
41
+     * @var array [gid => [uid => (bool)]]
42
+     */
43
+    protected $memberships;
44
+
45
+    /**
46
+     * @param IDBConnection $connection
47
+     * @param IGroupManager $groupManager
48
+     */
49
+    public function __construct(IDBConnection $connection, IGroupManager $groupManager) {
50
+        $this->connection = $connection;
51
+        $this->groupManager = $groupManager;
52
+    }
53
+
54
+    /**
55
+     * Returns the step's name
56
+     *
57
+     * @return string
58
+     */
59
+    public function getName() {
60
+        return 'Remove shares of old group memberships';
61
+    }
62
+
63
+    /**
64
+     * Run repair step.
65
+     * Must throw exception on error.
66
+     *
67
+     * @throws \Exception in case of failure
68
+     * @suppress SqlInjectionChecker
69
+     */
70
+    public function run(IOutput $output) {
71
+        $deletedEntries = 0;
72
+
73
+        $query = $this->connection->getQueryBuilder();
74
+        $query->select('s1.id')->selectAlias('s1.share_with', 'user')->selectAlias('s2.share_with', 'group')
75
+            ->from('share', 's1')
76
+            ->where($query->expr()->isNotNull('s1.parent'))
77
+                // \OC\Share\Constant::$shareTypeGroupUserUnique === 2
78
+                ->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2)))
79
+                ->andWhere($query->expr()->isNotNull('s2.id'))
80
+                ->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(Share::SHARE_TYPE_GROUP)))
81
+            ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'));
82
+
83
+        $deleteQuery = $this->connection->getQueryBuilder();
84
+        $deleteQuery->delete('share')
85
+            ->where($query->expr()->eq('id', $deleteQuery->createParameter('share')));
86
+
87
+        $result = $query->execute();
88
+        while ($row = $result->fetch()) {
89
+            if (!$this->isMember($row['group'], $row['user'])) {
90
+                $deletedEntries += $deleteQuery->setParameter('share', (int) $row['id'])
91
+                    ->execute();
92
+            }
93
+        }
94
+        $result->closeCursor();
95
+
96
+        if ($deletedEntries) {
97
+            $output->info('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore');
98
+        }
99
+    }
100
+
101
+    /**
102
+     * @param string $gid
103
+     * @param string $uid
104
+     * @return bool
105
+     */
106
+    protected function isMember($gid, $uid) {
107
+        if (isset($this->memberships[$gid][$uid])) {
108
+            return $this->memberships[$gid][$uid];
109
+        }
110
+
111
+        $isMember = $this->groupManager->isInGroup($uid, $gid);
112
+        if (!isset($this->memberships[$gid])) {
113
+            $this->memberships[$gid] = [];
114
+        }
115
+        $this->memberships[$gid][$uid] = $isMember;
116
+
117
+        return $isMember;
118
+    }
119 119
 }
Please login to merge, or discard this patch.
lib/private/Repair/RepairInvalidShares.php 1 patch
Indentation   +86 added lines, -86 removed lines patch added patch discarded remove patch
@@ -33,90 +33,90 @@
 block discarded – undo
33 33
  */
34 34
 class RepairInvalidShares implements IRepairStep {
35 35
 
36
-	const CHUNK_SIZE = 200;
37
-
38
-	/** @var \OCP\IConfig */
39
-	protected $config;
40
-
41
-	/** @var \OCP\IDBConnection */
42
-	protected $connection;
43
-
44
-	/**
45
-	 * @param \OCP\IConfig $config
46
-	 * @param \OCP\IDBConnection $connection
47
-	 */
48
-	public function __construct($config, $connection) {
49
-		$this->connection = $connection;
50
-		$this->config = $config;
51
-	}
52
-
53
-	public function getName() {
54
-		return 'Repair invalid shares';
55
-	}
56
-
57
-	/**
58
-	 * Adjust file share permissions
59
-	 * @suppress SqlInjectionChecker
60
-	 */
61
-	private function adjustFileSharePermissions(IOutput $out) {
62
-		$mask = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_SHARE;
63
-		$builder = $this->connection->getQueryBuilder();
64
-
65
-		$permsFunc = $builder->expr()->bitwiseAnd('permissions', $mask);
66
-		$builder
67
-			->update('share')
68
-			->set('permissions', $permsFunc)
69
-			->where($builder->expr()->eq('item_type', $builder->expr()->literal('file')))
70
-			->andWhere($builder->expr()->neq('permissions', $permsFunc));
71
-
72
-		$updatedEntries = $builder->execute();
73
-		if ($updatedEntries > 0) {
74
-			$out->info('Fixed file share permissions for ' . $updatedEntries . ' shares');
75
-		}
76
-	}
77
-
78
-	/**
79
-	 * Remove shares where the parent share does not exist anymore
80
-	 */
81
-	private function removeSharesNonExistingParent(IOutput $out) {
82
-		$deletedEntries = 0;
83
-
84
-		$query = $this->connection->getQueryBuilder();
85
-		$query->select('s1.parent')
86
-			->from('share', 's1')
87
-			->where($query->expr()->isNotNull('s1.parent'))
88
-				->andWhere($query->expr()->isNull('s2.id'))
89
-			->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'))
90
-			->groupBy('s1.parent')
91
-			->setMaxResults(self::CHUNK_SIZE);
92
-
93
-		$deleteQuery = $this->connection->getQueryBuilder();
94
-		$deleteQuery->delete('share')
95
-			->where($deleteQuery->expr()->eq('parent', $deleteQuery->createParameter('parent')));
96
-
97
-		$deletedInLastChunk = self::CHUNK_SIZE;
98
-		while ($deletedInLastChunk === self::CHUNK_SIZE) {
99
-			$deletedInLastChunk = 0;
100
-			$result = $query->execute();
101
-			while ($row = $result->fetch()) {
102
-				$deletedInLastChunk++;
103
-				$deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
104
-					->execute();
105
-			}
106
-			$result->closeCursor();
107
-		}
108
-
109
-		if ($deletedEntries) {
110
-			$out->info('Removed ' . $deletedEntries . ' shares where the parent did not exist');
111
-		}
112
-	}
113
-
114
-	public function run(IOutput $out) {
115
-		$ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
116
-		if (version_compare($ocVersionFromBeforeUpdate, '12.0.0.11', '<')) {
117
-			$this->adjustFileSharePermissions($out);
118
-		}
119
-
120
-		$this->removeSharesNonExistingParent($out);
121
-	}
36
+    const CHUNK_SIZE = 200;
37
+
38
+    /** @var \OCP\IConfig */
39
+    protected $config;
40
+
41
+    /** @var \OCP\IDBConnection */
42
+    protected $connection;
43
+
44
+    /**
45
+     * @param \OCP\IConfig $config
46
+     * @param \OCP\IDBConnection $connection
47
+     */
48
+    public function __construct($config, $connection) {
49
+        $this->connection = $connection;
50
+        $this->config = $config;
51
+    }
52
+
53
+    public function getName() {
54
+        return 'Repair invalid shares';
55
+    }
56
+
57
+    /**
58
+     * Adjust file share permissions
59
+     * @suppress SqlInjectionChecker
60
+     */
61
+    private function adjustFileSharePermissions(IOutput $out) {
62
+        $mask = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_SHARE;
63
+        $builder = $this->connection->getQueryBuilder();
64
+
65
+        $permsFunc = $builder->expr()->bitwiseAnd('permissions', $mask);
66
+        $builder
67
+            ->update('share')
68
+            ->set('permissions', $permsFunc)
69
+            ->where($builder->expr()->eq('item_type', $builder->expr()->literal('file')))
70
+            ->andWhere($builder->expr()->neq('permissions', $permsFunc));
71
+
72
+        $updatedEntries = $builder->execute();
73
+        if ($updatedEntries > 0) {
74
+            $out->info('Fixed file share permissions for ' . $updatedEntries . ' shares');
75
+        }
76
+    }
77
+
78
+    /**
79
+     * Remove shares where the parent share does not exist anymore
80
+     */
81
+    private function removeSharesNonExistingParent(IOutput $out) {
82
+        $deletedEntries = 0;
83
+
84
+        $query = $this->connection->getQueryBuilder();
85
+        $query->select('s1.parent')
86
+            ->from('share', 's1')
87
+            ->where($query->expr()->isNotNull('s1.parent'))
88
+                ->andWhere($query->expr()->isNull('s2.id'))
89
+            ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'))
90
+            ->groupBy('s1.parent')
91
+            ->setMaxResults(self::CHUNK_SIZE);
92
+
93
+        $deleteQuery = $this->connection->getQueryBuilder();
94
+        $deleteQuery->delete('share')
95
+            ->where($deleteQuery->expr()->eq('parent', $deleteQuery->createParameter('parent')));
96
+
97
+        $deletedInLastChunk = self::CHUNK_SIZE;
98
+        while ($deletedInLastChunk === self::CHUNK_SIZE) {
99
+            $deletedInLastChunk = 0;
100
+            $result = $query->execute();
101
+            while ($row = $result->fetch()) {
102
+                $deletedInLastChunk++;
103
+                $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
104
+                    ->execute();
105
+            }
106
+            $result->closeCursor();
107
+        }
108
+
109
+        if ($deletedEntries) {
110
+            $out->info('Removed ' . $deletedEntries . ' shares where the parent did not exist');
111
+        }
112
+    }
113
+
114
+    public function run(IOutput $out) {
115
+        $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
116
+        if (version_compare($ocVersionFromBeforeUpdate, '12.0.0.11', '<')) {
117
+            $this->adjustFileSharePermissions($out);
118
+        }
119
+
120
+        $this->removeSharesNonExistingParent($out);
121
+    }
122 122
 }
Please login to merge, or discard this patch.
lib/private/Repair/CleanTags.php 1 patch
Indentation   +168 added lines, -168 removed lines patch added patch discarded remove patch
@@ -37,172 +37,172 @@
 block discarded – undo
37 37
  */
38 38
 class CleanTags implements IRepairStep {
39 39
 
40
-	/** @var IDBConnection */
41
-	protected $connection;
42
-
43
-	/** @var IUserManager */
44
-	protected $userManager;
45
-
46
-	protected $deletedTags = 0;
47
-
48
-	/**
49
-	 * @param IDBConnection $connection
50
-	 * @param IUserManager $userManager
51
-	 */
52
-	public function __construct(IDBConnection $connection, IUserManager $userManager) {
53
-		$this->connection = $connection;
54
-		$this->userManager = $userManager;
55
-	}
56
-
57
-	/**
58
-	 * @return string
59
-	 */
60
-	public function getName() {
61
-		return 'Clean tags and favorites';
62
-	}
63
-
64
-	/**
65
-	 * Updates the configuration after running an update
66
-	 */
67
-	public function run(IOutput $output) {
68
-		$this->deleteOrphanTags($output);
69
-		$this->deleteOrphanFileEntries($output);
70
-		$this->deleteOrphanTagEntries($output);
71
-		$this->deleteOrphanCategoryEntries($output);
72
-	}
73
-
74
-	/**
75
-	 * Delete tags for deleted users
76
-	 */
77
-	protected function deleteOrphanTags(IOutput $output) {
78
-		$offset = 0;
79
-		while ($this->checkTags($offset)) {
80
-			$offset += 50;
81
-		}
82
-
83
-		$output->info(sprintf('%d tags of deleted users have been removed.', $this->deletedTags));
84
-	}
85
-
86
-	protected function checkTags($offset) {
87
-		$query = $this->connection->getQueryBuilder();
88
-		$query->select('uid')
89
-			->from('vcategory')
90
-			->groupBy('uid')
91
-			->orderBy('uid')
92
-			->setMaxResults(50)
93
-			->setFirstResult($offset);
94
-		$result = $query->execute();
95
-
96
-		$users = [];
97
-		$hadResults = false;
98
-		while ($row = $result->fetch()) {
99
-			$hadResults = true;
100
-			if (!$this->userManager->userExists($row['uid'])) {
101
-				$users[] = $row['uid'];
102
-			}
103
-		}
104
-		$result->closeCursor();
105
-
106
-		if (!$hadResults) {
107
-			// No more tags, stop looping
108
-			return false;
109
-		}
110
-
111
-		if (!empty($users)) {
112
-			$query = $this->connection->getQueryBuilder();
113
-			$query->delete('vcategory')
114
-				->where($query->expr()->in('uid', $query->createNamedParameter($users, IQueryBuilder::PARAM_STR_ARRAY)));
115
-			$this->deletedTags += $query->execute();
116
-		}
117
-		return true;
118
-	}
119
-
120
-	/**
121
-	 * Delete tag entries for deleted files
122
-	 */
123
-	protected function deleteOrphanFileEntries(IOutput $output) {
124
-		$this->deleteOrphanEntries(
125
-			$output,
126
-			'%d tags for delete files have been removed.',
127
-			'vcategory_to_object', 'objid',
128
-			'filecache', 'fileid', 'path_hash'
129
-		);
130
-	}
131
-
132
-	/**
133
-	 * Delete tag entries for deleted tags
134
-	 */
135
-	protected function deleteOrphanTagEntries(IOutput $output) {
136
-		$this->deleteOrphanEntries(
137
-			$output,
138
-			'%d tag entries for deleted tags have been removed.',
139
-			'vcategory_to_object', 'categoryid',
140
-			'vcategory', 'id', 'uid'
141
-		);
142
-	}
143
-
144
-	/**
145
-	 * Delete tags that have no entries
146
-	 */
147
-	protected function deleteOrphanCategoryEntries(IOutput $output) {
148
-		$this->deleteOrphanEntries(
149
-			$output,
150
-			'%d tags with no entries have been removed.',
151
-			'vcategory', 'id',
152
-			'vcategory_to_object', 'categoryid', 'type'
153
-		);
154
-	}
155
-
156
-	/**
157
-	 * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable
158
-	 *
159
-	 * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks
160
-	 * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable
161
-	 * is being deleted.
162
-	 *
163
-	 * @param string $repairInfo
164
-	 * @param string $deleteTable
165
-	 * @param string $deleteId
166
-	 * @param string $sourceTable
167
-	 * @param string $sourceId
168
-	 * @param string $sourceNullColumn	If this column is null in the source table,
169
-	 * 								the entry is deleted in the $deleteTable
170
-	 * @suppress SqlInjectionChecker
171
-	 */
172
-	protected function deleteOrphanEntries(IOutput $output, $repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) {
173
-		$qb = $this->connection->getQueryBuilder();
174
-
175
-		$qb->select('d.' . $deleteId)
176
-			->from($deleteTable, 'd')
177
-			->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, 's.' . $sourceId))
178
-			->where(
179
-				$qb->expr()->eq('d.type', $qb->expr()->literal('files'))
180
-			)
181
-			->andWhere(
182
-				$qb->expr()->isNull('s.' . $sourceNullColumn)
183
-			);
184
-		$result = $qb->execute();
185
-
186
-		$orphanItems = array();
187
-		while ($row = $result->fetch()) {
188
-			$orphanItems[] = (int) $row[$deleteId];
189
-		}
190
-
191
-		if (!empty($orphanItems)) {
192
-			$orphanItemsBatch = array_chunk($orphanItems, 200);
193
-			foreach ($orphanItemsBatch as $items) {
194
-				$qb->delete($deleteTable)
195
-					->where(
196
-						$qb->expr()->eq('type', $qb->expr()->literal('files'))
197
-					)
198
-					->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids')));
199
-				$qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY);
200
-				$qb->execute();
201
-			}
202
-		}
203
-
204
-		if ($repairInfo) {
205
-			$output->info(sprintf($repairInfo, sizeof($orphanItems)));
206
-		}
207
-	}
40
+    /** @var IDBConnection */
41
+    protected $connection;
42
+
43
+    /** @var IUserManager */
44
+    protected $userManager;
45
+
46
+    protected $deletedTags = 0;
47
+
48
+    /**
49
+     * @param IDBConnection $connection
50
+     * @param IUserManager $userManager
51
+     */
52
+    public function __construct(IDBConnection $connection, IUserManager $userManager) {
53
+        $this->connection = $connection;
54
+        $this->userManager = $userManager;
55
+    }
56
+
57
+    /**
58
+     * @return string
59
+     */
60
+    public function getName() {
61
+        return 'Clean tags and favorites';
62
+    }
63
+
64
+    /**
65
+     * Updates the configuration after running an update
66
+     */
67
+    public function run(IOutput $output) {
68
+        $this->deleteOrphanTags($output);
69
+        $this->deleteOrphanFileEntries($output);
70
+        $this->deleteOrphanTagEntries($output);
71
+        $this->deleteOrphanCategoryEntries($output);
72
+    }
73
+
74
+    /**
75
+     * Delete tags for deleted users
76
+     */
77
+    protected function deleteOrphanTags(IOutput $output) {
78
+        $offset = 0;
79
+        while ($this->checkTags($offset)) {
80
+            $offset += 50;
81
+        }
82
+
83
+        $output->info(sprintf('%d tags of deleted users have been removed.', $this->deletedTags));
84
+    }
85
+
86
+    protected function checkTags($offset) {
87
+        $query = $this->connection->getQueryBuilder();
88
+        $query->select('uid')
89
+            ->from('vcategory')
90
+            ->groupBy('uid')
91
+            ->orderBy('uid')
92
+            ->setMaxResults(50)
93
+            ->setFirstResult($offset);
94
+        $result = $query->execute();
95
+
96
+        $users = [];
97
+        $hadResults = false;
98
+        while ($row = $result->fetch()) {
99
+            $hadResults = true;
100
+            if (!$this->userManager->userExists($row['uid'])) {
101
+                $users[] = $row['uid'];
102
+            }
103
+        }
104
+        $result->closeCursor();
105
+
106
+        if (!$hadResults) {
107
+            // No more tags, stop looping
108
+            return false;
109
+        }
110
+
111
+        if (!empty($users)) {
112
+            $query = $this->connection->getQueryBuilder();
113
+            $query->delete('vcategory')
114
+                ->where($query->expr()->in('uid', $query->createNamedParameter($users, IQueryBuilder::PARAM_STR_ARRAY)));
115
+            $this->deletedTags += $query->execute();
116
+        }
117
+        return true;
118
+    }
119
+
120
+    /**
121
+     * Delete tag entries for deleted files
122
+     */
123
+    protected function deleteOrphanFileEntries(IOutput $output) {
124
+        $this->deleteOrphanEntries(
125
+            $output,
126
+            '%d tags for delete files have been removed.',
127
+            'vcategory_to_object', 'objid',
128
+            'filecache', 'fileid', 'path_hash'
129
+        );
130
+    }
131
+
132
+    /**
133
+     * Delete tag entries for deleted tags
134
+     */
135
+    protected function deleteOrphanTagEntries(IOutput $output) {
136
+        $this->deleteOrphanEntries(
137
+            $output,
138
+            '%d tag entries for deleted tags have been removed.',
139
+            'vcategory_to_object', 'categoryid',
140
+            'vcategory', 'id', 'uid'
141
+        );
142
+    }
143
+
144
+    /**
145
+     * Delete tags that have no entries
146
+     */
147
+    protected function deleteOrphanCategoryEntries(IOutput $output) {
148
+        $this->deleteOrphanEntries(
149
+            $output,
150
+            '%d tags with no entries have been removed.',
151
+            'vcategory', 'id',
152
+            'vcategory_to_object', 'categoryid', 'type'
153
+        );
154
+    }
155
+
156
+    /**
157
+     * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable
158
+     *
159
+     * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks
160
+     * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable
161
+     * is being deleted.
162
+     *
163
+     * @param string $repairInfo
164
+     * @param string $deleteTable
165
+     * @param string $deleteId
166
+     * @param string $sourceTable
167
+     * @param string $sourceId
168
+     * @param string $sourceNullColumn	If this column is null in the source table,
169
+     * 								the entry is deleted in the $deleteTable
170
+     * @suppress SqlInjectionChecker
171
+     */
172
+    protected function deleteOrphanEntries(IOutput $output, $repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) {
173
+        $qb = $this->connection->getQueryBuilder();
174
+
175
+        $qb->select('d.' . $deleteId)
176
+            ->from($deleteTable, 'd')
177
+            ->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, 's.' . $sourceId))
178
+            ->where(
179
+                $qb->expr()->eq('d.type', $qb->expr()->literal('files'))
180
+            )
181
+            ->andWhere(
182
+                $qb->expr()->isNull('s.' . $sourceNullColumn)
183
+            );
184
+        $result = $qb->execute();
185
+
186
+        $orphanItems = array();
187
+        while ($row = $result->fetch()) {
188
+            $orphanItems[] = (int) $row[$deleteId];
189
+        }
190
+
191
+        if (!empty($orphanItems)) {
192
+            $orphanItemsBatch = array_chunk($orphanItems, 200);
193
+            foreach ($orphanItemsBatch as $items) {
194
+                $qb->delete($deleteTable)
195
+                    ->where(
196
+                        $qb->expr()->eq('type', $qb->expr()->literal('files'))
197
+                    )
198
+                    ->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids')));
199
+                $qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY);
200
+                $qb->execute();
201
+            }
202
+        }
203
+
204
+        if ($repairInfo) {
205
+            $output->info(sprintf($repairInfo, sizeof($orphanItems)));
206
+        }
207
+    }
208 208
 }
Please login to merge, or discard this patch.
lib/private/BackgroundJob/JobList.php 1 patch
Indentation   +294 added lines, -294 removed lines patch added patch discarded remove patch
@@ -36,298 +36,298 @@
 block discarded – undo
36 36
 
37 37
 class JobList implements IJobList {
38 38
 
39
-	/** @var IDBConnection */
40
-	protected $connection;
41
-
42
-	/**@var IConfig */
43
-	protected $config;
44
-
45
-	/**@var ITimeFactory */
46
-	protected $timeFactory;
47
-
48
-	/**
49
-	 * @param IDBConnection $connection
50
-	 * @param IConfig $config
51
-	 * @param ITimeFactory $timeFactory
52
-	 */
53
-	public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
54
-		$this->connection = $connection;
55
-		$this->config = $config;
56
-		$this->timeFactory = $timeFactory;
57
-	}
58
-
59
-	/**
60
-	 * @param IJob|string $job
61
-	 * @param mixed $argument
62
-	 */
63
-	public function add($job, $argument = null) {
64
-		if (!$this->has($job, $argument)) {
65
-			if ($job instanceof IJob) {
66
-				$class = get_class($job);
67
-			} else {
68
-				$class = $job;
69
-			}
70
-
71
-			$argument = json_encode($argument);
72
-			if (strlen($argument) > 4000) {
73
-				throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
74
-			}
75
-
76
-			$query = $this->connection->getQueryBuilder();
77
-			$query->insert('jobs')
78
-				->values([
79
-					'class' => $query->createNamedParameter($class),
80
-					'argument' => $query->createNamedParameter($argument),
81
-					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
82
-					'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
83
-				]);
84
-			$query->execute();
85
-		}
86
-	}
87
-
88
-	/**
89
-	 * @param IJob|string $job
90
-	 * @param mixed $argument
91
-	 */
92
-	public function remove($job, $argument = null) {
93
-		if ($job instanceof IJob) {
94
-			$class = get_class($job);
95
-		} else {
96
-			$class = $job;
97
-		}
98
-
99
-		$query = $this->connection->getQueryBuilder();
100
-		$query->delete('jobs')
101
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
102
-		if (!is_null($argument)) {
103
-			$argument = json_encode($argument);
104
-			$query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
105
-		}
106
-		$query->execute();
107
-	}
108
-
109
-	/**
110
-	 * @param int $id
111
-	 */
112
-	protected function removeById($id) {
113
-		$query = $this->connection->getQueryBuilder();
114
-		$query->delete('jobs')
115
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
116
-		$query->execute();
117
-	}
118
-
119
-	/**
120
-	 * check if a job is in the list
121
-	 *
122
-	 * @param IJob|string $job
123
-	 * @param mixed $argument
124
-	 * @return bool
125
-	 */
126
-	public function has($job, $argument) {
127
-		if ($job instanceof IJob) {
128
-			$class = get_class($job);
129
-		} else {
130
-			$class = $job;
131
-		}
132
-		$argument = json_encode($argument);
133
-
134
-		$query = $this->connection->getQueryBuilder();
135
-		$query->select('id')
136
-			->from('jobs')
137
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
138
-			->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
139
-			->setMaxResults(1);
140
-
141
-		$result = $query->execute();
142
-		$row = $result->fetch();
143
-		$result->closeCursor();
144
-
145
-		return (bool) $row;
146
-	}
147
-
148
-	/**
149
-	 * get all jobs in the list
150
-	 *
151
-	 * @return IJob[]
152
-	 * @deprecated 9.0.0 - This method is dangerous since it can cause load and
153
-	 * memory problems when creating too many instances.
154
-	 */
155
-	public function getAll() {
156
-		$query = $this->connection->getQueryBuilder();
157
-		$query->select('*')
158
-			->from('jobs');
159
-		$result = $query->execute();
160
-
161
-		$jobs = [];
162
-		while ($row = $result->fetch()) {
163
-			$job = $this->buildJob($row);
164
-			if ($job) {
165
-				$jobs[] = $job;
166
-			}
167
-		}
168
-		$result->closeCursor();
169
-
170
-		return $jobs;
171
-	}
172
-
173
-	/**
174
-	 * get the next job in the list
175
-	 *
176
-	 * @return IJob|null
177
-	 */
178
-	public function getNext() {
179
-		$query = $this->connection->getQueryBuilder();
180
-		$query->select('*')
181
-			->from('jobs')
182
-			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
183
-			->orderBy('last_checked', 'ASC')
184
-			->setMaxResults(1);
185
-
186
-		$update = $this->connection->getQueryBuilder();
187
-		$update->update('jobs')
188
-			->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
189
-			->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
190
-			->where($update->expr()->eq('id', $update->createParameter('jobid')))
191
-			->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
192
-			->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
193
-
194
-		$result = $query->execute();
195
-		$row = $result->fetch();
196
-		$result->closeCursor();
197
-
198
-		if ($row) {
199
-			$update->setParameter('jobid', $row['id']);
200
-			$update->setParameter('reserved_at', $row['reserved_at']);
201
-			$update->setParameter('last_checked', $row['last_checked']);
202
-			$count = $update->execute();
203
-
204
-			if ($count === 0) {
205
-				// Background job already executed elsewhere, try again.
206
-				return $this->getNext();
207
-			}
208
-			$job = $this->buildJob($row);
209
-
210
-			if ($job === null) {
211
-				// Background job from disabled app, try again.
212
-				return $this->getNext();
213
-			}
214
-
215
-			return $job;
216
-		} else {
217
-			return null;
218
-		}
219
-	}
220
-
221
-	/**
222
-	 * @param int $id
223
-	 * @return IJob|null
224
-	 */
225
-	public function getById($id) {
226
-		$query = $this->connection->getQueryBuilder();
227
-		$query->select('*')
228
-			->from('jobs')
229
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
230
-		$result = $query->execute();
231
-		$row = $result->fetch();
232
-		$result->closeCursor();
233
-
234
-		if ($row) {
235
-			return $this->buildJob($row);
236
-		} else {
237
-			return null;
238
-		}
239
-	}
240
-
241
-	/**
242
-	 * get the job object from a row in the db
243
-	 *
244
-	 * @param array $row
245
-	 * @return IJob|null
246
-	 */
247
-	private function buildJob($row) {
248
-		try {
249
-			try {
250
-				// Try to load the job as a service
251
-				/** @var IJob $job */
252
-				$job = \OC::$server->query($row['class']);
253
-			} catch (QueryException $e) {
254
-				if (class_exists($row['class'])) {
255
-					$class = $row['class'];
256
-					$job = new $class();
257
-				} else {
258
-					// job from disabled app or old version of an app, no need to do anything
259
-					return null;
260
-				}
261
-			}
262
-
263
-			$job->setId($row['id']);
264
-			$job->setLastRun($row['last_run']);
265
-			$job->setArgument(json_decode($row['argument'], true));
266
-			return $job;
267
-		} catch (AutoloadNotAllowedException $e) {
268
-			// job is from a disabled app, ignore
269
-			return null;
270
-		}
271
-	}
272
-
273
-	/**
274
-	 * set the job that was last ran
275
-	 *
276
-	 * @param IJob $job
277
-	 */
278
-	public function setLastJob(IJob $job) {
279
-		$this->unlockJob($job);
280
-		$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
281
-	}
282
-
283
-	/**
284
-	 * Remove the reservation for a job
285
-	 *
286
-	 * @param IJob $job
287
-	 * @suppress SqlInjectionChecker
288
-	 */
289
-	public function unlockJob(IJob $job) {
290
-		$query = $this->connection->getQueryBuilder();
291
-		$query->update('jobs')
292
-			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
293
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
294
-		$query->execute();
295
-	}
296
-
297
-	/**
298
-	 * get the id of the last ran job
299
-	 *
300
-	 * @return int
301
-	 * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
302
-	 *    only tells you which job finished last, but since we now allow multiple
303
-	 *    executors to run in parallel, it's not used to calculate the next job.
304
-	 */
305
-	public function getLastJob() {
306
-		return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
307
-	}
308
-
309
-	/**
310
-	 * set the lastRun of $job to now
311
-	 *
312
-	 * @param IJob $job
313
-	 */
314
-	public function setLastRun(IJob $job) {
315
-		$query = $this->connection->getQueryBuilder();
316
-		$query->update('jobs')
317
-			->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
318
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
319
-		$query->execute();
320
-	}
321
-
322
-	/**
323
-	 * @param IJob $job
324
-	 * @param $timeTaken
325
-	 */
326
-	public function setExecutionTime(IJob $job, $timeTaken) {
327
-		$query = $this->connection->getQueryBuilder();
328
-		$query->update('jobs')
329
-			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
330
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
331
-		$query->execute();
332
-	}
39
+    /** @var IDBConnection */
40
+    protected $connection;
41
+
42
+    /**@var IConfig */
43
+    protected $config;
44
+
45
+    /**@var ITimeFactory */
46
+    protected $timeFactory;
47
+
48
+    /**
49
+     * @param IDBConnection $connection
50
+     * @param IConfig $config
51
+     * @param ITimeFactory $timeFactory
52
+     */
53
+    public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
54
+        $this->connection = $connection;
55
+        $this->config = $config;
56
+        $this->timeFactory = $timeFactory;
57
+    }
58
+
59
+    /**
60
+     * @param IJob|string $job
61
+     * @param mixed $argument
62
+     */
63
+    public function add($job, $argument = null) {
64
+        if (!$this->has($job, $argument)) {
65
+            if ($job instanceof IJob) {
66
+                $class = get_class($job);
67
+            } else {
68
+                $class = $job;
69
+            }
70
+
71
+            $argument = json_encode($argument);
72
+            if (strlen($argument) > 4000) {
73
+                throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
74
+            }
75
+
76
+            $query = $this->connection->getQueryBuilder();
77
+            $query->insert('jobs')
78
+                ->values([
79
+                    'class' => $query->createNamedParameter($class),
80
+                    'argument' => $query->createNamedParameter($argument),
81
+                    'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
82
+                    'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
83
+                ]);
84
+            $query->execute();
85
+        }
86
+    }
87
+
88
+    /**
89
+     * @param IJob|string $job
90
+     * @param mixed $argument
91
+     */
92
+    public function remove($job, $argument = null) {
93
+        if ($job instanceof IJob) {
94
+            $class = get_class($job);
95
+        } else {
96
+            $class = $job;
97
+        }
98
+
99
+        $query = $this->connection->getQueryBuilder();
100
+        $query->delete('jobs')
101
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
102
+        if (!is_null($argument)) {
103
+            $argument = json_encode($argument);
104
+            $query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
105
+        }
106
+        $query->execute();
107
+    }
108
+
109
+    /**
110
+     * @param int $id
111
+     */
112
+    protected function removeById($id) {
113
+        $query = $this->connection->getQueryBuilder();
114
+        $query->delete('jobs')
115
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
116
+        $query->execute();
117
+    }
118
+
119
+    /**
120
+     * check if a job is in the list
121
+     *
122
+     * @param IJob|string $job
123
+     * @param mixed $argument
124
+     * @return bool
125
+     */
126
+    public function has($job, $argument) {
127
+        if ($job instanceof IJob) {
128
+            $class = get_class($job);
129
+        } else {
130
+            $class = $job;
131
+        }
132
+        $argument = json_encode($argument);
133
+
134
+        $query = $this->connection->getQueryBuilder();
135
+        $query->select('id')
136
+            ->from('jobs')
137
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
138
+            ->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
139
+            ->setMaxResults(1);
140
+
141
+        $result = $query->execute();
142
+        $row = $result->fetch();
143
+        $result->closeCursor();
144
+
145
+        return (bool) $row;
146
+    }
147
+
148
+    /**
149
+     * get all jobs in the list
150
+     *
151
+     * @return IJob[]
152
+     * @deprecated 9.0.0 - This method is dangerous since it can cause load and
153
+     * memory problems when creating too many instances.
154
+     */
155
+    public function getAll() {
156
+        $query = $this->connection->getQueryBuilder();
157
+        $query->select('*')
158
+            ->from('jobs');
159
+        $result = $query->execute();
160
+
161
+        $jobs = [];
162
+        while ($row = $result->fetch()) {
163
+            $job = $this->buildJob($row);
164
+            if ($job) {
165
+                $jobs[] = $job;
166
+            }
167
+        }
168
+        $result->closeCursor();
169
+
170
+        return $jobs;
171
+    }
172
+
173
+    /**
174
+     * get the next job in the list
175
+     *
176
+     * @return IJob|null
177
+     */
178
+    public function getNext() {
179
+        $query = $this->connection->getQueryBuilder();
180
+        $query->select('*')
181
+            ->from('jobs')
182
+            ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
183
+            ->orderBy('last_checked', 'ASC')
184
+            ->setMaxResults(1);
185
+
186
+        $update = $this->connection->getQueryBuilder();
187
+        $update->update('jobs')
188
+            ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
189
+            ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
190
+            ->where($update->expr()->eq('id', $update->createParameter('jobid')))
191
+            ->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
192
+            ->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
193
+
194
+        $result = $query->execute();
195
+        $row = $result->fetch();
196
+        $result->closeCursor();
197
+
198
+        if ($row) {
199
+            $update->setParameter('jobid', $row['id']);
200
+            $update->setParameter('reserved_at', $row['reserved_at']);
201
+            $update->setParameter('last_checked', $row['last_checked']);
202
+            $count = $update->execute();
203
+
204
+            if ($count === 0) {
205
+                // Background job already executed elsewhere, try again.
206
+                return $this->getNext();
207
+            }
208
+            $job = $this->buildJob($row);
209
+
210
+            if ($job === null) {
211
+                // Background job from disabled app, try again.
212
+                return $this->getNext();
213
+            }
214
+
215
+            return $job;
216
+        } else {
217
+            return null;
218
+        }
219
+    }
220
+
221
+    /**
222
+     * @param int $id
223
+     * @return IJob|null
224
+     */
225
+    public function getById($id) {
226
+        $query = $this->connection->getQueryBuilder();
227
+        $query->select('*')
228
+            ->from('jobs')
229
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
230
+        $result = $query->execute();
231
+        $row = $result->fetch();
232
+        $result->closeCursor();
233
+
234
+        if ($row) {
235
+            return $this->buildJob($row);
236
+        } else {
237
+            return null;
238
+        }
239
+    }
240
+
241
+    /**
242
+     * get the job object from a row in the db
243
+     *
244
+     * @param array $row
245
+     * @return IJob|null
246
+     */
247
+    private function buildJob($row) {
248
+        try {
249
+            try {
250
+                // Try to load the job as a service
251
+                /** @var IJob $job */
252
+                $job = \OC::$server->query($row['class']);
253
+            } catch (QueryException $e) {
254
+                if (class_exists($row['class'])) {
255
+                    $class = $row['class'];
256
+                    $job = new $class();
257
+                } else {
258
+                    // job from disabled app or old version of an app, no need to do anything
259
+                    return null;
260
+                }
261
+            }
262
+
263
+            $job->setId($row['id']);
264
+            $job->setLastRun($row['last_run']);
265
+            $job->setArgument(json_decode($row['argument'], true));
266
+            return $job;
267
+        } catch (AutoloadNotAllowedException $e) {
268
+            // job is from a disabled app, ignore
269
+            return null;
270
+        }
271
+    }
272
+
273
+    /**
274
+     * set the job that was last ran
275
+     *
276
+     * @param IJob $job
277
+     */
278
+    public function setLastJob(IJob $job) {
279
+        $this->unlockJob($job);
280
+        $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
281
+    }
282
+
283
+    /**
284
+     * Remove the reservation for a job
285
+     *
286
+     * @param IJob $job
287
+     * @suppress SqlInjectionChecker
288
+     */
289
+    public function unlockJob(IJob $job) {
290
+        $query = $this->connection->getQueryBuilder();
291
+        $query->update('jobs')
292
+            ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
293
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
294
+        $query->execute();
295
+    }
296
+
297
+    /**
298
+     * get the id of the last ran job
299
+     *
300
+     * @return int
301
+     * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
302
+     *    only tells you which job finished last, but since we now allow multiple
303
+     *    executors to run in parallel, it's not used to calculate the next job.
304
+     */
305
+    public function getLastJob() {
306
+        return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
307
+    }
308
+
309
+    /**
310
+     * set the lastRun of $job to now
311
+     *
312
+     * @param IJob $job
313
+     */
314
+    public function setLastRun(IJob $job) {
315
+        $query = $this->connection->getQueryBuilder();
316
+        $query->update('jobs')
317
+            ->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
318
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
319
+        $query->execute();
320
+    }
321
+
322
+    /**
323
+     * @param IJob $job
324
+     * @param $timeTaken
325
+     */
326
+    public function setExecutionTime(IJob $job, $timeTaken) {
327
+        $query = $this->connection->getQueryBuilder();
328
+        $query->update('jobs')
329
+            ->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
330
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
331
+        $query->execute();
332
+    }
333 333
 }
Please login to merge, or discard this patch.