Completed
Push — master ( ed6bb5...9b547f )
by
unknown
44:30
created
lib/private/Files/Cache/Propagator.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -22,183 +22,183 @@
 block discarded – undo
22 22
 use Psr\Log\LoggerInterface;
23 23
 
24 24
 class Propagator implements IPropagator {
25
-	public const MAX_RETRIES = 3;
26
-
27
-	private bool $inBatch = false;
28
-	private array $batch = [];
29
-	private ClockInterface $clock;
30
-
31
-	public function __construct(
32
-		protected readonly IStorage $storage,
33
-		private readonly IDBConnection $connection,
34
-		private readonly array $ignore = [],
35
-	) {
36
-		$this->clock = Server::get(ClockInterface::class);
37
-	}
38
-
39
-	#[Override]
40
-	public function propagateChange(string $internalPath, int $time, int $sizeDifference = 0): void {
41
-		// Do not propagate changes in ignored paths
42
-		foreach ($this->ignore as $ignore) {
43
-			if (str_starts_with($internalPath, $ignore)) {
44
-				return;
45
-			}
46
-		}
47
-
48
-		$time = min($time, $this->clock->now()->getTimestamp());
49
-
50
-		$storageId = $this->storage->getCache()->getNumericStorageId();
51
-
52
-		$parents = $this->getParents($internalPath);
53
-
54
-		if ($this->inBatch) {
55
-			foreach ($parents as $parent) {
56
-				$this->addToBatch($parent, $time, $sizeDifference);
57
-			}
58
-			return;
59
-		}
60
-
61
-		$parentHashes = array_map('md5', $parents);
62
-		$etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
63
-
64
-		$builder = $this->connection->getQueryBuilder();
65
-		$hashParams = array_map(function ($hash) use ($builder) {
66
-			return $builder->expr()->literal($hash);
67
-		}, $parentHashes);
68
-
69
-		$builder->update('filecache')
70
-			->set('mtime', $builder->func()->greatest('mtime', $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT)))
71
-			->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
72
-			->andWhere($builder->expr()->in('path_hash', $hashParams));
73
-		if (!$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
74
-			$builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
75
-		}
76
-
77
-		if ($sizeDifference !== 0) {
78
-			$hasCalculatedSize = $builder->expr()->gt('size', $builder->expr()->literal(-1, IQUeryBuilder::PARAM_INT));
79
-			$sizeColumn = $builder->getColumnName('size');
80
-			$newSize = $builder->func()->greatest(
81
-				$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
82
-				$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
83
-			);
84
-
85
-			// Only update if row had a previously calculated size
86
-			$builder->set('size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newSize ELSE $sizeColumn END"));
87
-
88
-			if ($this->storage->instanceOfStorage(Encryption::class)) {
89
-				// in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size
90
-				$hasUnencryptedSize = $builder->expr()->neq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
91
-				$sizeColumn = $builder->getColumnName('size');
92
-				$unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
93
-				$newUnencryptedSize = $builder->func()->greatest(
94
-					$builder->func()->add(
95
-						$builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
96
-						$builder->createNamedParameter($sizeDifference)
97
-					),
98
-					$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
99
-				);
100
-
101
-				// Only update if row had a previously calculated size
102
-				$builder->set('unencrypted_size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newUnencryptedSize ELSE $unencryptedSizeColumn END"));
103
-			}
104
-		}
105
-
106
-		for ($i = 0; $i < self::MAX_RETRIES; $i++) {
107
-			try {
108
-				$builder->executeStatement();
109
-				break;
110
-			} catch (DbalException $e) {
111
-				if (!$e->isRetryable()) {
112
-					throw $e;
113
-				}
114
-
115
-				/** @var LoggerInterface $loggerInterface */
116
-				$loggerInterface = \OCP\Server::get(LoggerInterface::class);
117
-				$loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]);
118
-			}
119
-		}
120
-	}
121
-
122
-	/**
123
-	 * @return string[]
124
-	 */
125
-	protected function getParents(string $path): array {
126
-		$parts = explode('/', $path);
127
-		$parent = '';
128
-		$parents = [];
129
-		foreach ($parts as $part) {
130
-			$parents[] = $parent;
131
-			$parent = trim($parent . '/' . $part, '/');
132
-		}
133
-		return $parents;
134
-	}
135
-
136
-	#[Override]
137
-	public function beginBatch(): void {
138
-		$this->inBatch = true;
139
-	}
140
-
141
-	private function addToBatch(string $internalPath, int $time, int $sizeDifference): void {
142
-		if (!isset($this->batch[$internalPath])) {
143
-			$this->batch[$internalPath] = [
144
-				'hash' => md5($internalPath),
145
-				'time' => $time,
146
-				'size' => $sizeDifference,
147
-			];
148
-		} else {
149
-			$this->batch[$internalPath]['size'] += $sizeDifference;
150
-			if ($time > $this->batch[$internalPath]['time']) {
151
-				$this->batch[$internalPath]['time'] = $time;
152
-			}
153
-		}
154
-	}
155
-
156
-	#[Override]
157
-	public function commitBatch(): void {
158
-		if (!$this->inBatch) {
159
-			throw new \BadMethodCallException('Not in batch');
160
-		}
161
-		$this->inBatch = false;
162
-
163
-		try {
164
-			$this->connection->beginTransaction();
165
-
166
-			$query = $this->connection->getQueryBuilder();
167
-			$storageId = $this->storage->getCache()->getNumericStorageId();
168
-
169
-			$query->update('filecache')
170
-				->set('mtime', $query->func()->greatest('mtime', $query->createParameter('time')))
171
-				->set('etag', $query->expr()->literal(uniqid()))
172
-				->where($query->expr()->eq('storage', $query->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
173
-				->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
174
-
175
-			$sizeQuery = $this->connection->getQueryBuilder();
176
-			$sizeQuery->update('filecache')
177
-				->set('size', $sizeQuery->func()->add('size', $sizeQuery->createParameter('size')))
178
-				->where($query->expr()->eq('storage', $sizeQuery->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
179
-				->andWhere($query->expr()->eq('path_hash', $sizeQuery->createParameter('hash')))
180
-				->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->createNamedParameter(-1, IQueryBuilder::PARAM_INT)));
181
-
182
-			foreach ($this->batch as $item) {
183
-				$query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
184
-				$query->setParameter('hash', $item['hash']);
185
-
186
-				$query->executeStatement();
187
-
188
-				if ($item['size']) {
189
-					$sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
190
-					$sizeQuery->setParameter('hash', $item['hash']);
191
-
192
-					$sizeQuery->executeStatement();
193
-				}
194
-			}
195
-
196
-			$this->batch = [];
197
-
198
-			$this->connection->commit();
199
-		} catch (\Exception $e) {
200
-			$this->connection->rollback();
201
-			throw $e;
202
-		}
203
-	}
25
+    public const MAX_RETRIES = 3;
26
+
27
+    private bool $inBatch = false;
28
+    private array $batch = [];
29
+    private ClockInterface $clock;
30
+
31
+    public function __construct(
32
+        protected readonly IStorage $storage,
33
+        private readonly IDBConnection $connection,
34
+        private readonly array $ignore = [],
35
+    ) {
36
+        $this->clock = Server::get(ClockInterface::class);
37
+    }
38
+
39
+    #[Override]
40
+    public function propagateChange(string $internalPath, int $time, int $sizeDifference = 0): void {
41
+        // Do not propagate changes in ignored paths
42
+        foreach ($this->ignore as $ignore) {
43
+            if (str_starts_with($internalPath, $ignore)) {
44
+                return;
45
+            }
46
+        }
47
+
48
+        $time = min($time, $this->clock->now()->getTimestamp());
49
+
50
+        $storageId = $this->storage->getCache()->getNumericStorageId();
51
+
52
+        $parents = $this->getParents($internalPath);
53
+
54
+        if ($this->inBatch) {
55
+            foreach ($parents as $parent) {
56
+                $this->addToBatch($parent, $time, $sizeDifference);
57
+            }
58
+            return;
59
+        }
60
+
61
+        $parentHashes = array_map('md5', $parents);
62
+        $etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag
63
+
64
+        $builder = $this->connection->getQueryBuilder();
65
+        $hashParams = array_map(function ($hash) use ($builder) {
66
+            return $builder->expr()->literal($hash);
67
+        }, $parentHashes);
68
+
69
+        $builder->update('filecache')
70
+            ->set('mtime', $builder->func()->greatest('mtime', $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT)))
71
+            ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
72
+            ->andWhere($builder->expr()->in('path_hash', $hashParams));
73
+        if (!$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
74
+            $builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
75
+        }
76
+
77
+        if ($sizeDifference !== 0) {
78
+            $hasCalculatedSize = $builder->expr()->gt('size', $builder->expr()->literal(-1, IQUeryBuilder::PARAM_INT));
79
+            $sizeColumn = $builder->getColumnName('size');
80
+            $newSize = $builder->func()->greatest(
81
+                $builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
82
+                $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
83
+            );
84
+
85
+            // Only update if row had a previously calculated size
86
+            $builder->set('size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newSize ELSE $sizeColumn END"));
87
+
88
+            if ($this->storage->instanceOfStorage(Encryption::class)) {
89
+                // in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size
90
+                $hasUnencryptedSize = $builder->expr()->neq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
91
+                $sizeColumn = $builder->getColumnName('size');
92
+                $unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
93
+                $newUnencryptedSize = $builder->func()->greatest(
94
+                    $builder->func()->add(
95
+                        $builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
96
+                        $builder->createNamedParameter($sizeDifference)
97
+                    ),
98
+                    $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
99
+                );
100
+
101
+                // Only update if row had a previously calculated size
102
+                $builder->set('unencrypted_size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newUnencryptedSize ELSE $unencryptedSizeColumn END"));
103
+            }
104
+        }
105
+
106
+        for ($i = 0; $i < self::MAX_RETRIES; $i++) {
107
+            try {
108
+                $builder->executeStatement();
109
+                break;
110
+            } catch (DbalException $e) {
111
+                if (!$e->isRetryable()) {
112
+                    throw $e;
113
+                }
114
+
115
+                /** @var LoggerInterface $loggerInterface */
116
+                $loggerInterface = \OCP\Server::get(LoggerInterface::class);
117
+                $loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]);
118
+            }
119
+        }
120
+    }
121
+
122
+    /**
123
+     * @return string[]
124
+     */
125
+    protected function getParents(string $path): array {
126
+        $parts = explode('/', $path);
127
+        $parent = '';
128
+        $parents = [];
129
+        foreach ($parts as $part) {
130
+            $parents[] = $parent;
131
+            $parent = trim($parent . '/' . $part, '/');
132
+        }
133
+        return $parents;
134
+    }
135
+
136
+    #[Override]
137
+    public function beginBatch(): void {
138
+        $this->inBatch = true;
139
+    }
140
+
141
+    private function addToBatch(string $internalPath, int $time, int $sizeDifference): void {
142
+        if (!isset($this->batch[$internalPath])) {
143
+            $this->batch[$internalPath] = [
144
+                'hash' => md5($internalPath),
145
+                'time' => $time,
146
+                'size' => $sizeDifference,
147
+            ];
148
+        } else {
149
+            $this->batch[$internalPath]['size'] += $sizeDifference;
150
+            if ($time > $this->batch[$internalPath]['time']) {
151
+                $this->batch[$internalPath]['time'] = $time;
152
+            }
153
+        }
154
+    }
155
+
156
+    #[Override]
157
+    public function commitBatch(): void {
158
+        if (!$this->inBatch) {
159
+            throw new \BadMethodCallException('Not in batch');
160
+        }
161
+        $this->inBatch = false;
162
+
163
+        try {
164
+            $this->connection->beginTransaction();
165
+
166
+            $query = $this->connection->getQueryBuilder();
167
+            $storageId = $this->storage->getCache()->getNumericStorageId();
168
+
169
+            $query->update('filecache')
170
+                ->set('mtime', $query->func()->greatest('mtime', $query->createParameter('time')))
171
+                ->set('etag', $query->expr()->literal(uniqid()))
172
+                ->where($query->expr()->eq('storage', $query->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
173
+                ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
174
+
175
+            $sizeQuery = $this->connection->getQueryBuilder();
176
+            $sizeQuery->update('filecache')
177
+                ->set('size', $sizeQuery->func()->add('size', $sizeQuery->createParameter('size')))
178
+                ->where($query->expr()->eq('storage', $sizeQuery->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
179
+                ->andWhere($query->expr()->eq('path_hash', $sizeQuery->createParameter('hash')))
180
+                ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->createNamedParameter(-1, IQueryBuilder::PARAM_INT)));
181
+
182
+            foreach ($this->batch as $item) {
183
+                $query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
184
+                $query->setParameter('hash', $item['hash']);
185
+
186
+                $query->executeStatement();
187
+
188
+                if ($item['size']) {
189
+                    $sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
190
+                    $sizeQuery->setParameter('hash', $item['hash']);
191
+
192
+                    $sizeQuery->executeStatement();
193
+                }
194
+            }
195
+
196
+            $this->batch = [];
197
+
198
+            $this->connection->commit();
199
+        } catch (\Exception $e) {
200
+            $this->connection->rollback();
201
+            throw $e;
202
+        }
203
+    }
204 204
 }
Please login to merge, or discard this patch.