Completed
Pull Request — master (#31988)
by Jörn Friedrich
16:09 queued 04:45
created

Propagator::propagateChange()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 3
dl 0
loc 44
rs 8.9048
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Robin Appelman <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\Files\Cache;
23
24
use OCP\DB\QueryBuilder\IQueryBuilder;
25
use OCP\Files\Cache\IPropagator;
26
use OCP\IDBConnection;
27
28
/**
29
 * Propagate etags and mtimes within the storage
30
 */
31
class Propagator implements IPropagator {
32
	private $inBatch = false;
33
34
	private $batch = [];
35
36
	/**
37
	 * @var \OC\Files\Storage\Storage
38
	 */
39
	protected $storage;
40
41
	/**
42
	 * @var IDBConnection
43
	 */
44
	private $connection;
45
46
	/**
47
	 * @param \OC\Files\Storage\Storage $storage
48
	 * @param IDBConnection $connection
49
	 */
50
	public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection) {
51
		$this->storage = $storage;
52
		$this->connection = $connection;
53
	}
54
55
	/**
56
	 * @param string $internalPath
57
	 * @param int $time
58
	 * @param int $sizeDifference number of bytes the file has grown
59
	 */
60
	public function propagateChange($internalPath, $time, $sizeDifference = 0) {
61
		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
62
63
		$parents = $this->getParents($internalPath);
64
65
		if (\count($parents) === 0) {
66
			return;
67
		}
68
69
		if ($this->inBatch) {
70
			foreach ($parents as $parent) {
71
				$this->addToBatch($parent, $time, $sizeDifference);
72
			}
73
			return;
74
		}
75
76
		$parentHashes = \array_map('md5', $parents);
77
		$etag = \uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
78
79
		$builder = $this->connection->getQueryBuilder();
80
		$hashParams = \array_map(function ($hash) use ($builder) {
81
			return $builder->expr()->literal($hash);
82
		}, $parentHashes);
83
84
		$builder->update('filecache')
85
			->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT) . ')'))
86
			->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR))
87
			->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
88
			->andWhere($builder->expr()->in('path_hash', $hashParams));
89
90
		$builder->execute();
91
92
		if ($sizeDifference !== 0) {
93
			// we need to do size separably so we can ignore entries with uncalculated size
94
			$builder = $this->connection->getQueryBuilder();
95
			$builder->update('filecache')
96
				->set('size', $builder->createFunction('`size` + ' . $builder->createNamedParameter($sizeDifference)))
97
				->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
98
				->andWhere($builder->expr()->in('path_hash', $hashParams))
99
				->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
100
101
			$builder->execute();
102
		}
103
	}
104
105
	protected function getParents($path) {
106
		if ($path === '') {
107
			return [];
108
		}
109
		$parts = \explode('/', $path);
110
		$parent = '';
111
		$parents = [];
112
		foreach ($parts as $part) {
113
			$parents[] = $parent;
114
			$parent = \trim($parent . '/' . $part, '/');
115
		}
116
		return $parents;
117
	}
118
119
	/**
120
	 * Mark the beginning of a propagation batch
121
	 *
122
	 * Note that not all cache setups support propagation in which case this will be a noop
123
	 *
124
	 * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent
125
	 * before the batch is committed.
126
	 */
127
	public function beginBatch() {
128
		$this->inBatch = true;
129
	}
130
131
	private function addToBatch($internalPath, $time, $sizeDifference) {
132
		if (!isset($this->batch[$internalPath])) {
133
			$this->batch[$internalPath] = [
134
				'hash' => \md5($internalPath),
135
				'time' => $time,
136
				'size' => $sizeDifference
137
			];
138
		} else {
139
			$this->batch[$internalPath]['size'] += $sizeDifference;
140
			if ($time > $this->batch[$internalPath]['time']) {
141
				$this->batch[$internalPath]['time'] = $time;
142
			}
143
		}
144
	}
145
146
	/**
147
	 * Commit the active propagation batch
148
	 */
149
	public function commitBatch() {
150
		if (!$this->inBatch) {
151
			throw new \BadMethodCallException('Not in batch');
152
		}
153
		$this->inBatch = false;
154
155
		$this->connection->beginTransaction();
156
157
		$query = $this->connection->getQueryBuilder();
158
		$storageId = (int)$this->storage->getStorageCache()->getNumericId();
159
160
		$query->update('filecache')
161
			->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')'))
162
			->set('etag', $query->expr()->literal(\uniqid()))
163
			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
164
			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
165
166
		$sizeQuery = $this->connection->getQueryBuilder();
167
		$sizeQuery->update('filecache')
168
			->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size')))
169
			->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
170
			->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')))
171
			->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
172
173
		foreach ($this->batch as $item) {
174
			$query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
175
			$query->setParameter('hash', $item['hash']);
176
177
			$query->execute();
178
179
			if ($item['size']) {
180
				$sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
181
				$sizeQuery->setParameter('hash', $item['hash']);
182
183
				$sizeQuery->execute();
184
			}
185
		}
186
187
		$this->batch = [];
188
189
		$this->connection->commit();
190
	}
191
}
192