Completed
Push — master ( b33fda...6a2e0f )
by
unknown
28:30
created
lib/public/Files/Cache/IWatcher.php 1 patch
Indentation   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -13,84 +13,84 @@
 block discarded – undo
13 13
  * @since 9.0.0
14 14
  */
15 15
 interface IWatcher {
16
-	/**
17
-	 * @since 9.0.0
18
-	 */
19
-	public const CHECK_NEVER = 0; // never check the underlying filesystem for updates
16
+    /**
17
+     * @since 9.0.0
18
+     */
19
+    public const CHECK_NEVER = 0; // never check the underlying filesystem for updates
20 20
 
21
-	/**
22
-	 * @since 9.0.0
23
-	 */
24
-	public const CHECK_ONCE = 1; // check the underlying filesystem for updates once every request for each file
21
+    /**
22
+     * @since 9.0.0
23
+     */
24
+    public const CHECK_ONCE = 1; // check the underlying filesystem for updates once every request for each file
25 25
 
26
-	/**
27
-	 * @since 9.0.0
28
-	 */
29
-	public const CHECK_ALWAYS = 2; // always check the underlying filesystem for updates
26
+    /**
27
+     * @since 9.0.0
28
+     */
29
+    public const CHECK_ALWAYS = 2; // always check the underlying filesystem for updates
30 30
 
31
-	/**
32
-	 * @param int $policy either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS
33
-	 * @since 9.0.0
34
-	 */
35
-	public function setPolicy($policy);
31
+    /**
32
+     * @param int $policy either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS
33
+     * @since 9.0.0
34
+     */
35
+    public function setPolicy($policy);
36 36
 
37
-	/**
38
-	 * Set a filter regex, only paths matching the regex will be checked for updates.
39
-	 *
40
-	 * When set to `null`, every path will be checked for updates
41
-	 *
42
-	 * @param ?string $filter
43
-	 * @return void
44
-	 * @since 33.0.0
45
-	 */
46
-	public function setCheckFilter(?string $filter): void;
37
+    /**
38
+     * Set a filter regex, only paths matching the regex will be checked for updates.
39
+     *
40
+     * When set to `null`, every path will be checked for updates
41
+     *
42
+     * @param ?string $filter
43
+     * @return void
44
+     * @since 33.0.0
45
+     */
46
+    public function setCheckFilter(?string $filter): void;
47 47
 
48
-	/**
49
-	 * @return int either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS
50
-	 * @since 9.0.0
51
-	 */
52
-	public function getPolicy();
48
+    /**
49
+     * @return int either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS
50
+     * @since 9.0.0
51
+     */
52
+    public function getPolicy();
53 53
 
54
-	/**
55
-	 * check $path for updates and update if needed
56
-	 *
57
-	 * @param string $path
58
-	 * @param ICacheEntry|null $cachedEntry
59
-	 * @return boolean true if path was updated
60
-	 * @since 9.0.0
61
-	 */
62
-	public function checkUpdate($path, $cachedEntry = null);
54
+    /**
55
+     * check $path for updates and update if needed
56
+     *
57
+     * @param string $path
58
+     * @param ICacheEntry|null $cachedEntry
59
+     * @return boolean true if path was updated
60
+     * @since 9.0.0
61
+     */
62
+    public function checkUpdate($path, $cachedEntry = null);
63 63
 
64
-	/**
65
-	 * Update the cache for changes to $path
66
-	 *
67
-	 * @param string $path
68
-	 * @param ICacheEntry $cachedData
69
-	 * @since 9.0.0
70
-	 */
71
-	public function update($path, $cachedData);
64
+    /**
65
+     * Update the cache for changes to $path
66
+     *
67
+     * @param string $path
68
+     * @param ICacheEntry $cachedData
69
+     * @since 9.0.0
70
+     */
71
+    public function update($path, $cachedData);
72 72
 
73
-	/**
74
-	 * Check if the cache for $path needs to be updated
75
-	 *
76
-	 * @param string $path
77
-	 * @param ICacheEntry $cachedData
78
-	 * @return bool
79
-	 * @since 9.0.0
80
-	 */
81
-	public function needsUpdate($path, $cachedData);
73
+    /**
74
+     * Check if the cache for $path needs to be updated
75
+     *
76
+     * @param string $path
77
+     * @param ICacheEntry $cachedData
78
+     * @return bool
79
+     * @since 9.0.0
80
+     */
81
+    public function needsUpdate($path, $cachedData);
82 82
 
83
-	/**
84
-	 * remove deleted files in $path from the cache
85
-	 *
86
-	 * @param string $path
87
-	 * @since 9.0.0
88
-	 */
89
-	public function cleanFolder($path);
83
+    /**
84
+     * remove deleted files in $path from the cache
85
+     *
86
+     * @param string $path
87
+     * @since 9.0.0
88
+     */
89
+    public function cleanFolder($path);
90 90
 
91
-	/**
92
-	 * register a callback to be called whenever the watcher triggers and update
93
-	 * @since 31.0.0
94
-	 */
95
-	public function onUpdate(callable $callback): void;
91
+    /**
92
+     * register a callback to be called whenever the watcher triggers and update
93
+     * @since 31.0.0
94
+     */
95
+    public function onUpdate(callable $callback): void;
96 96
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Watcher.php 1 patch
Indentation   +138 added lines, -138 removed lines patch added patch discarded remove patch
@@ -17,142 +17,142 @@
 block discarded – undo
17 17
  * check the storage backends for updates and change the cache accordingly
18 18
  */
19 19
 class Watcher implements IWatcher {
20
-	protected $watchPolicy = self::CHECK_ONCE;
21
-
22
-	protected $checkedPaths = [];
23
-
24
-	/**
25
-	 * @var IStorage $storage
26
-	 */
27
-	protected $storage;
28
-
29
-	/**
30
-	 * @var ICache $cache
31
-	 */
32
-	protected $cache;
33
-
34
-	/**
35
-	 * @var IScanner $scanner ;
36
-	 */
37
-	protected $scanner;
38
-
39
-	/** @var callable[] */
40
-	protected $onUpdate = [];
41
-
42
-	protected ?string $checkFilter = null;
43
-
44
-	public function __construct(IStorage $storage) {
45
-		$this->storage = $storage;
46
-		$this->cache = $storage->getCache();
47
-		$this->scanner = $storage->getScanner();
48
-	}
49
-
50
-	/**
51
-	 * @param int $policy either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS
52
-	 */
53
-	public function setPolicy($policy) {
54
-		$this->watchPolicy = $policy;
55
-	}
56
-
57
-	public function setCheckFilter(?string $filter): void {
58
-		$this->checkFilter = $filter;
59
-	}
60
-
61
-	/**
62
-	 * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS
63
-	 */
64
-	public function getPolicy() {
65
-		return $this->watchPolicy;
66
-	}
67
-
68
-	/**
69
-	 * check $path for updates and update if needed
70
-	 *
71
-	 * @param string $path
72
-	 * @param ICacheEntry|null $cachedEntry
73
-	 * @return boolean true if path was updated
74
-	 */
75
-	public function checkUpdate($path, $cachedEntry = null) {
76
-		if (is_null($cachedEntry)) {
77
-			$cachedEntry = $this->cache->get($path);
78
-		}
79
-		if ($cachedEntry === false || $this->needsUpdate($path, $cachedEntry)) {
80
-			$this->update($path, $cachedEntry);
81
-
82
-			if ($cachedEntry === false) {
83
-				return true;
84
-			} else {
85
-				// storage backends can sometimes return false positives, only return true if the scanner actually found a change
86
-				$newEntry = $this->cache->get($path);
87
-				return $newEntry->getStorageMTime() > $cachedEntry->getStorageMTime();
88
-			}
89
-		} else {
90
-			return false;
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * Update the cache for changes to $path
96
-	 *
97
-	 * @param string $path
98
-	 * @param ICacheEntry $cachedData
99
-	 */
100
-	public function update($path, $cachedData) {
101
-		if ($this->storage->is_dir($path)) {
102
-			$this->scanner->scan($path, Scanner::SCAN_SHALLOW);
103
-		} else {
104
-			$this->scanner->scanFile($path);
105
-		}
106
-		if (is_array($cachedData) && $cachedData['mimetype'] === 'httpd/unix-directory') {
107
-			$this->cleanFolder($path);
108
-		}
109
-		if ($this->cache instanceof Cache) {
110
-			$this->cache->correctFolderSize($path);
111
-		}
112
-		foreach ($this->onUpdate as $callback) {
113
-			$callback($path);
114
-		}
115
-	}
116
-
117
-	/**
118
-	 * Check if the cache for $path needs to be updated
119
-	 *
120
-	 * @param string $path
121
-	 * @param ICacheEntry $cachedData
122
-	 * @return bool
123
-	 */
124
-	public function needsUpdate($path, $cachedData) {
125
-		if ($this->checkFilter !== null) {
126
-			if (!preg_match($this->checkFilter, $path)) {
127
-				return false;
128
-			}
129
-		}
130
-
131
-		if ($this->watchPolicy === self::CHECK_ALWAYS || ($this->watchPolicy === self::CHECK_ONCE && !in_array($path, $this->checkedPaths))) {
132
-			$this->checkedPaths[] = $path;
133
-			return $cachedData['storage_mtime'] === null || $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
134
-		}
135
-		return false;
136
-	}
137
-
138
-	/**
139
-	 * remove deleted files in $path from the cache
140
-	 *
141
-	 * @param string $path
142
-	 */
143
-	public function cleanFolder($path) {
144
-		$cachedContent = $this->cache->getFolderContents($path);
145
-		foreach ($cachedContent as $entry) {
146
-			if (!$this->storage->file_exists($entry['path'])) {
147
-				$this->cache->remove($entry['path']);
148
-			}
149
-		}
150
-	}
151
-
152
-	/**
153
-	 * register a callback to be called whenever the watcher triggers and update
154
-	 */
155
-	public function onUpdate(callable $callback): void {
156
-		$this->onUpdate[] = $callback;
157
-	}
20
+    protected $watchPolicy = self::CHECK_ONCE;
21
+
22
+    protected $checkedPaths = [];
23
+
24
+    /**
25
+     * @var IStorage $storage
26
+     */
27
+    protected $storage;
28
+
29
+    /**
30
+     * @var ICache $cache
31
+     */
32
+    protected $cache;
33
+
34
+    /**
35
+     * @var IScanner $scanner ;
36
+     */
37
+    protected $scanner;
38
+
39
+    /** @var callable[] */
40
+    protected $onUpdate = [];
41
+
42
+    protected ?string $checkFilter = null;
43
+
44
+    public function __construct(IStorage $storage) {
45
+        $this->storage = $storage;
46
+        $this->cache = $storage->getCache();
47
+        $this->scanner = $storage->getScanner();
48
+    }
49
+
50
+    /**
51
+     * @param int $policy either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS
52
+     */
53
+    public function setPolicy($policy) {
54
+        $this->watchPolicy = $policy;
55
+    }
56
+
57
+    public function setCheckFilter(?string $filter): void {
58
+        $this->checkFilter = $filter;
59
+    }
60
+
61
+    /**
62
+     * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS
63
+     */
64
+    public function getPolicy() {
65
+        return $this->watchPolicy;
66
+    }
67
+
68
+    /**
69
+     * check $path for updates and update if needed
70
+     *
71
+     * @param string $path
72
+     * @param ICacheEntry|null $cachedEntry
73
+     * @return boolean true if path was updated
74
+     */
75
+    public function checkUpdate($path, $cachedEntry = null) {
76
+        if (is_null($cachedEntry)) {
77
+            $cachedEntry = $this->cache->get($path);
78
+        }
79
+        if ($cachedEntry === false || $this->needsUpdate($path, $cachedEntry)) {
80
+            $this->update($path, $cachedEntry);
81
+
82
+            if ($cachedEntry === false) {
83
+                return true;
84
+            } else {
85
+                // storage backends can sometimes return false positives, only return true if the scanner actually found a change
86
+                $newEntry = $this->cache->get($path);
87
+                return $newEntry->getStorageMTime() > $cachedEntry->getStorageMTime();
88
+            }
89
+        } else {
90
+            return false;
91
+        }
92
+    }
93
+
94
+    /**
95
+     * Update the cache for changes to $path
96
+     *
97
+     * @param string $path
98
+     * @param ICacheEntry $cachedData
99
+     */
100
+    public function update($path, $cachedData) {
101
+        if ($this->storage->is_dir($path)) {
102
+            $this->scanner->scan($path, Scanner::SCAN_SHALLOW);
103
+        } else {
104
+            $this->scanner->scanFile($path);
105
+        }
106
+        if (is_array($cachedData) && $cachedData['mimetype'] === 'httpd/unix-directory') {
107
+            $this->cleanFolder($path);
108
+        }
109
+        if ($this->cache instanceof Cache) {
110
+            $this->cache->correctFolderSize($path);
111
+        }
112
+        foreach ($this->onUpdate as $callback) {
113
+            $callback($path);
114
+        }
115
+    }
116
+
117
+    /**
118
+     * Check if the cache for $path needs to be updated
119
+     *
120
+     * @param string $path
121
+     * @param ICacheEntry $cachedData
122
+     * @return bool
123
+     */
124
+    public function needsUpdate($path, $cachedData) {
125
+        if ($this->checkFilter !== null) {
126
+            if (!preg_match($this->checkFilter, $path)) {
127
+                return false;
128
+            }
129
+        }
130
+
131
+        if ($this->watchPolicy === self::CHECK_ALWAYS || ($this->watchPolicy === self::CHECK_ONCE && !in_array($path, $this->checkedPaths))) {
132
+            $this->checkedPaths[] = $path;
133
+            return $cachedData['storage_mtime'] === null || $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
134
+        }
135
+        return false;
136
+    }
137
+
138
+    /**
139
+     * remove deleted files in $path from the cache
140
+     *
141
+     * @param string $path
142
+     */
143
+    public function cleanFolder($path) {
144
+        $cachedContent = $this->cache->getFolderContents($path);
145
+        foreach ($cachedContent as $entry) {
146
+            if (!$this->storage->file_exists($entry['path'])) {
147
+                $this->cache->remove($entry['path']);
148
+            }
149
+        }
150
+    }
151
+
152
+    /**
153
+     * register a callback to be called whenever the watcher triggers and update
154
+     */
155
+    public function onUpdate(callable $callback): void {
156
+        $this->onUpdate[] = $callback;
157
+    }
158 158
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/Common.php 1 patch
Indentation   +716 added lines, -716 removed lines patch added patch discarded remove patch
@@ -53,720 +53,720 @@
 block discarded – undo
53 53
  * in classes which extend it, e.g. $this->stat() .
54 54
  */
55 55
 abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage, IConstructableStorage {
56
-	use LocalTempFileTrait;
57
-
58
-	protected ?Cache $cache = null;
59
-	protected ?Scanner $scanner = null;
60
-	protected ?Watcher $watcher = null;
61
-	protected ?Propagator $propagator = null;
62
-	protected $storageCache;
63
-	protected ?Updater $updater = null;
64
-
65
-	protected array $mountOptions = [];
66
-	protected $owner = null;
67
-
68
-	private ?bool $shouldLogLocks = null;
69
-	private ?LoggerInterface $logger = null;
70
-	private ?IFilenameValidator $filenameValidator = null;
71
-
72
-	public function __construct(array $parameters) {
73
-	}
74
-
75
-	protected function remove(string $path): bool {
76
-		if ($this->file_exists($path)) {
77
-			if ($this->is_dir($path)) {
78
-				return $this->rmdir($path);
79
-			} elseif ($this->is_file($path)) {
80
-				return $this->unlink($path);
81
-			}
82
-		}
83
-		return false;
84
-	}
85
-
86
-	public function is_dir(string $path): bool {
87
-		return $this->filetype($path) === 'dir';
88
-	}
89
-
90
-	public function is_file(string $path): bool {
91
-		return $this->filetype($path) === 'file';
92
-	}
93
-
94
-	public function filesize(string $path): int|float|false {
95
-		if ($this->is_dir($path)) {
96
-			return 0; //by definition
97
-		} else {
98
-			$stat = $this->stat($path);
99
-			return isset($stat['size']) ? $stat['size'] : 0;
100
-		}
101
-	}
102
-
103
-	public function isReadable(string $path): bool {
104
-		// at least check whether it exists
105
-		// subclasses might want to implement this more thoroughly
106
-		return $this->file_exists($path);
107
-	}
108
-
109
-	public function isUpdatable(string $path): bool {
110
-		// at least check whether it exists
111
-		// subclasses might want to implement this more thoroughly
112
-		// a non-existing file/folder isn't updatable
113
-		return $this->file_exists($path);
114
-	}
115
-
116
-	public function isCreatable(string $path): bool {
117
-		if ($this->is_dir($path) && $this->isUpdatable($path)) {
118
-			return true;
119
-		}
120
-		return false;
121
-	}
122
-
123
-	public function isDeletable(string $path): bool {
124
-		if ($path === '' || $path === '/') {
125
-			return $this->isUpdatable($path);
126
-		}
127
-		$parent = dirname($path);
128
-		return $this->isUpdatable($parent) && $this->isUpdatable($path);
129
-	}
130
-
131
-	public function isSharable(string $path): bool {
132
-		return $this->isReadable($path);
133
-	}
134
-
135
-	public function getPermissions(string $path): int {
136
-		$permissions = 0;
137
-		if ($this->isCreatable($path)) {
138
-			$permissions |= \OCP\Constants::PERMISSION_CREATE;
139
-		}
140
-		if ($this->isReadable($path)) {
141
-			$permissions |= \OCP\Constants::PERMISSION_READ;
142
-		}
143
-		if ($this->isUpdatable($path)) {
144
-			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
145
-		}
146
-		if ($this->isDeletable($path)) {
147
-			$permissions |= \OCP\Constants::PERMISSION_DELETE;
148
-		}
149
-		if ($this->isSharable($path)) {
150
-			$permissions |= \OCP\Constants::PERMISSION_SHARE;
151
-		}
152
-		return $permissions;
153
-	}
154
-
155
-	public function filemtime(string $path): int|false {
156
-		$stat = $this->stat($path);
157
-		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
158
-			return $stat['mtime'];
159
-		} else {
160
-			return 0;
161
-		}
162
-	}
163
-
164
-	public function file_get_contents(string $path): string|false {
165
-		$handle = $this->fopen($path, 'r');
166
-		if (!$handle) {
167
-			return false;
168
-		}
169
-		$data = stream_get_contents($handle);
170
-		fclose($handle);
171
-		return $data;
172
-	}
173
-
174
-	public function file_put_contents(string $path, mixed $data): int|float|false {
175
-		$handle = $this->fopen($path, 'w');
176
-		if (!$handle) {
177
-			return false;
178
-		}
179
-		$this->removeCachedFile($path);
180
-		$count = fwrite($handle, $data);
181
-		fclose($handle);
182
-		return $count;
183
-	}
184
-
185
-	public function rename(string $source, string $target): bool {
186
-		$this->remove($target);
187
-
188
-		$this->removeCachedFile($source);
189
-		return $this->copy($source, $target) && $this->remove($source);
190
-	}
191
-
192
-	public function copy(string $source, string $target): bool {
193
-		if ($this->is_dir($source)) {
194
-			$this->remove($target);
195
-			$dir = $this->opendir($source);
196
-			$this->mkdir($target);
197
-			while (($file = readdir($dir)) !== false) {
198
-				if (!Filesystem::isIgnoredDir($file)) {
199
-					if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
200
-						closedir($dir);
201
-						return false;
202
-					}
203
-				}
204
-			}
205
-			closedir($dir);
206
-			return true;
207
-		} else {
208
-			$sourceStream = $this->fopen($source, 'r');
209
-			$targetStream = $this->fopen($target, 'w');
210
-			[, $result] = Files::streamCopy($sourceStream, $targetStream, true);
211
-			if (!$result) {
212
-				Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
213
-			}
214
-			$this->removeCachedFile($target);
215
-			return $result;
216
-		}
217
-	}
218
-
219
-	public function getMimeType(string $path): string|false {
220
-		if ($this->is_dir($path)) {
221
-			return 'httpd/unix-directory';
222
-		} elseif ($this->file_exists($path)) {
223
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
224
-		} else {
225
-			return false;
226
-		}
227
-	}
228
-
229
-	public function hash(string $type, string $path, bool $raw = false): string|false {
230
-		$fh = $this->fopen($path, 'rb');
231
-		if (!$fh) {
232
-			return false;
233
-		}
234
-		$ctx = hash_init($type);
235
-		hash_update_stream($ctx, $fh);
236
-		fclose($fh);
237
-		return hash_final($ctx, $raw);
238
-	}
239
-
240
-	public function getLocalFile(string $path): string|false {
241
-		return $this->getCachedFile($path);
242
-	}
243
-
244
-	private function addLocalFolder(string $path, string $target): void {
245
-		$dh = $this->opendir($path);
246
-		if (is_resource($dh)) {
247
-			while (($file = readdir($dh)) !== false) {
248
-				if (!Filesystem::isIgnoredDir($file)) {
249
-					if ($this->is_dir($path . '/' . $file)) {
250
-						mkdir($target . '/' . $file);
251
-						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
252
-					} else {
253
-						$tmp = $this->toTmpFile($path . '/' . $file);
254
-						rename($tmp, $target . '/' . $file);
255
-					}
256
-				}
257
-			}
258
-		}
259
-	}
260
-
261
-	protected function searchInDir(string $query, string $dir = ''): array {
262
-		$files = [];
263
-		$dh = $this->opendir($dir);
264
-		if (is_resource($dh)) {
265
-			while (($item = readdir($dh)) !== false) {
266
-				if (Filesystem::isIgnoredDir($item)) {
267
-					continue;
268
-				}
269
-				if (strstr(strtolower($item), strtolower($query)) !== false) {
270
-					$files[] = $dir . '/' . $item;
271
-				}
272
-				if ($this->is_dir($dir . '/' . $item)) {
273
-					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
274
-				}
275
-			}
276
-		}
277
-		closedir($dh);
278
-		return $files;
279
-	}
280
-
281
-	/**
282
-	 * @inheritDoc
283
-	 * Check if a file or folder has been updated since $time
284
-	 *
285
-	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
286
-	 * the mtime should always return false here. As a result storage implementations that always return false expect
287
-	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
288
-	 * Nextcloud filesystem.
289
-	 */
290
-	public function hasUpdated(string $path, int $time): bool {
291
-		return $this->filemtime($path) > $time;
292
-	}
293
-
294
-	protected function getCacheDependencies(): CacheDependencies {
295
-		static $dependencies = null;
296
-		if (!$dependencies) {
297
-			$dependencies = Server::get(CacheDependencies::class);
298
-		}
299
-		return $dependencies;
300
-	}
301
-
302
-	public function getCache(string $path = '', ?IStorage $storage = null): ICache {
303
-		if (!$storage) {
304
-			$storage = $this;
305
-		}
306
-		/** @var self $storage */
307
-		if (!isset($storage->cache)) {
308
-			$storage->cache = new Cache($storage, $this->getCacheDependencies());
309
-		}
310
-		return $storage->cache;
311
-	}
312
-
313
-	public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
314
-		if (!$storage) {
315
-			$storage = $this;
316
-		}
317
-		if (!$storage->instanceOfStorage(self::class)) {
318
-			throw new \InvalidArgumentException('Storage is not of the correct class');
319
-		}
320
-		if (!isset($storage->scanner)) {
321
-			$storage->scanner = new Scanner($storage);
322
-		}
323
-		return $storage->scanner;
324
-	}
325
-
326
-	public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
327
-		if (!$storage) {
328
-			$storage = $this;
329
-		}
330
-		if (!isset($this->watcher)) {
331
-			$this->watcher = new Watcher($storage);
332
-			$globalPolicy = Server::get(IConfig::class)->getSystemValueInt('filesystem_check_changes', Watcher::CHECK_NEVER);
333
-			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
334
-			$this->watcher->setCheckFilter($this->getMountOption('filesystem_check_filter'));
335
-		}
336
-		return $this->watcher;
337
-	}
338
-
339
-	public function getPropagator(?IStorage $storage = null): IPropagator {
340
-		if (!$storage) {
341
-			$storage = $this;
342
-		}
343
-		if (!$storage->instanceOfStorage(self::class)) {
344
-			throw new \InvalidArgumentException('Storage is not of the correct class');
345
-		}
346
-		/** @var self $storage */
347
-		if (!isset($storage->propagator)) {
348
-			$config = Server::get(IConfig::class);
349
-			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getSystemValueString('instanceid')]);
350
-		}
351
-		return $storage->propagator;
352
-	}
353
-
354
-	public function getUpdater(?IStorage $storage = null): IUpdater {
355
-		if (!$storage) {
356
-			$storage = $this;
357
-		}
358
-		if (!$storage->instanceOfStorage(self::class)) {
359
-			throw new \InvalidArgumentException('Storage is not of the correct class');
360
-		}
361
-		/** @var self $storage */
362
-		if (!isset($storage->updater)) {
363
-			$storage->updater = new Updater($storage);
364
-		}
365
-		return $storage->updater;
366
-	}
367
-
368
-	public function getStorageCache(?IStorage $storage = null): \OC\Files\Cache\Storage {
369
-		/** @var Cache $cache */
370
-		$cache = $this->getCache(storage: $storage);
371
-		return $cache->getStorageCache();
372
-	}
373
-
374
-	public function getOwner(string $path): string|false {
375
-		if ($this->owner === null) {
376
-			$this->owner = \OC_User::getUser();
377
-		}
378
-
379
-		return $this->owner;
380
-	}
381
-
382
-	public function getETag(string $path): string|false {
383
-		return uniqid();
384
-	}
385
-
386
-	/**
387
-	 * clean a path, i.e. remove all redundant '.' and '..'
388
-	 * making sure that it can't point to higher than '/'
389
-	 *
390
-	 * @param string $path The path to clean
391
-	 * @return string cleaned path
392
-	 */
393
-	public function cleanPath(string $path): string {
394
-		if (strlen($path) == 0 || $path[0] != '/') {
395
-			$path = '/' . $path;
396
-		}
397
-
398
-		$output = [];
399
-		foreach (explode('/', $path) as $chunk) {
400
-			if ($chunk == '..') {
401
-				array_pop($output);
402
-			} elseif ($chunk == '.') {
403
-			} else {
404
-				$output[] = $chunk;
405
-			}
406
-		}
407
-		return implode('/', $output);
408
-	}
409
-
410
-	/**
411
-	 * Test a storage for availability
412
-	 */
413
-	public function test(): bool {
414
-		try {
415
-			if ($this->stat('')) {
416
-				return true;
417
-			}
418
-			Server::get(LoggerInterface::class)->info('External storage not available: stat() failed');
419
-			return false;
420
-		} catch (\Exception $e) {
421
-			Server::get(LoggerInterface::class)->warning(
422
-				'External storage not available: ' . $e->getMessage(),
423
-				['exception' => $e]
424
-			);
425
-			return false;
426
-		}
427
-	}
428
-
429
-	public function free_space(string $path): int|float|false {
430
-		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
431
-	}
432
-
433
-	public function isLocal(): bool {
434
-		// the common implementation returns a temporary file by
435
-		// default, which is not local
436
-		return false;
437
-	}
438
-
439
-	/**
440
-	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
441
-	 */
442
-	public function instanceOfStorage(string $class): bool {
443
-		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
444
-			// FIXME Temporary fix to keep existing checks working
445
-			$class = '\OCA\Files_Sharing\SharedStorage';
446
-		}
447
-		return is_a($this, $class);
448
-	}
449
-
450
-	#[Override]
451
-	public function getDirectDownload(string $path): array|false {
452
-		return false;
453
-	}
454
-
455
-	#[Override]
456
-	public function getDirectDownloadById(string $fileId): array|false {
457
-		return false;
458
-	}
459
-
460
-	public function verifyPath(string $path, string $fileName): void {
461
-		$this->getFilenameValidator()
462
-			->validateFilename($fileName);
463
-
464
-		// verify also the path is valid
465
-		if ($path && $path !== '/' && $path !== '.') {
466
-			try {
467
-				$this->verifyPath(dirname($path), basename($path));
468
-			} catch (InvalidPathException $e) {
469
-				// Ignore invalid file type exceptions on directories
470
-				if ($e->getCode() !== FilenameValidator::INVALID_FILE_TYPE) {
471
-					$l = \OCP\Util::getL10N('lib');
472
-					throw new InvalidPathException($l->t('Invalid parent path'), previous: $e);
473
-				}
474
-			}
475
-		}
476
-	}
477
-
478
-	/**
479
-	 * Get the filename validator
480
-	 * (cached for performance)
481
-	 */
482
-	protected function getFilenameValidator(): IFilenameValidator {
483
-		if ($this->filenameValidator === null) {
484
-			$this->filenameValidator = Server::get(IFilenameValidator::class);
485
-		}
486
-		return $this->filenameValidator;
487
-	}
488
-
489
-	public function setMountOptions(array $options): void {
490
-		$this->mountOptions = $options;
491
-	}
492
-
493
-	public function getMountOption(string $name, mixed $default = null): mixed {
494
-		return $this->mountOptions[$name] ?? $default;
495
-	}
496
-
497
-	public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): bool {
498
-		if ($sourceStorage === $this) {
499
-			return $this->copy($sourceInternalPath, $targetInternalPath);
500
-		}
501
-
502
-		if ($sourceStorage->is_dir($sourceInternalPath)) {
503
-			$dh = $sourceStorage->opendir($sourceInternalPath);
504
-			$result = $this->mkdir($targetInternalPath);
505
-			if (is_resource($dh)) {
506
-				$result = true;
507
-				while ($result && ($file = readdir($dh)) !== false) {
508
-					if (!Filesystem::isIgnoredDir($file)) {
509
-						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
510
-					}
511
-				}
512
-			}
513
-		} else {
514
-			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
515
-			$result = false;
516
-			if ($source) {
517
-				try {
518
-					$this->writeStream($targetInternalPath, $source);
519
-					$result = true;
520
-				} catch (\Exception $e) {
521
-					Server::get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
522
-				}
523
-			}
524
-
525
-			if ($result && $preserveMtime) {
526
-				$mtime = $sourceStorage->filemtime($sourceInternalPath);
527
-				$this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
528
-			}
529
-
530
-			if (!$result) {
531
-				// delete partially written target file
532
-				$this->unlink($targetInternalPath);
533
-				// delete cache entry that was created by fopen
534
-				$this->getCache()->remove($targetInternalPath);
535
-			}
536
-		}
537
-		return (bool)$result;
538
-	}
539
-
540
-	/**
541
-	 * Check if a storage is the same as the current one, including wrapped storages
542
-	 */
543
-	private function isSameStorage(IStorage $storage): bool {
544
-		while ($storage->instanceOfStorage(Wrapper::class)) {
545
-			/**
546
-			 * @var Wrapper $storage
547
-			 */
548
-			$storage = $storage->getWrapperStorage();
549
-		}
550
-
551
-		return $storage === $this;
552
-	}
553
-
554
-	public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
555
-		if (
556
-			!$sourceStorage->instanceOfStorage(Encryption::class)
557
-			&& $this->isSameStorage($sourceStorage)
558
-		) {
559
-			// resolve any jailed paths
560
-			while ($sourceStorage->instanceOfStorage(Jail::class)) {
561
-				/**
562
-				 * @var Jail $sourceStorage
563
-				 */
564
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
565
-				$sourceStorage = $sourceStorage->getUnjailedStorage();
566
-			}
567
-
568
-			return $this->rename($sourceInternalPath, $targetInternalPath);
569
-		}
570
-
571
-		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
572
-			return false;
573
-		}
574
-
575
-		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
576
-		if ($result) {
577
-			if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
578
-				/** @var ObjectStoreStorage $sourceStorage */
579
-				$sourceStorage->setPreserveCacheOnDelete(true);
580
-			}
581
-			try {
582
-				if ($sourceStorage->is_dir($sourceInternalPath)) {
583
-					$result = $sourceStorage->rmdir($sourceInternalPath);
584
-				} else {
585
-					$result = $sourceStorage->unlink($sourceInternalPath);
586
-				}
587
-			} finally {
588
-				if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
589
-					/** @var ObjectStoreStorage $sourceStorage */
590
-					$sourceStorage->setPreserveCacheOnDelete(false);
591
-				}
592
-			}
593
-		}
594
-		return $result;
595
-	}
596
-
597
-	public function getMetaData(string $path): ?array {
598
-		if (Filesystem::isFileBlacklisted($path)) {
599
-			throw new ForbiddenException('Invalid path: ' . $path, false);
600
-		}
601
-
602
-		$permissions = $this->getPermissions($path);
603
-		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
604
-			//can't read, nothing we can do
605
-			return null;
606
-		}
607
-
608
-		$data = [];
609
-		$data['mimetype'] = $this->getMimeType($path);
610
-		$data['mtime'] = $this->filemtime($path);
611
-		if ($data['mtime'] === false) {
612
-			$data['mtime'] = time();
613
-		}
614
-		if ($data['mimetype'] == 'httpd/unix-directory') {
615
-			$data['size'] = -1; //unknown
616
-		} else {
617
-			$data['size'] = $this->filesize($path);
618
-		}
619
-		$data['etag'] = $this->getETag($path);
620
-		$data['storage_mtime'] = $data['mtime'];
621
-		$data['permissions'] = $permissions;
622
-		$data['name'] = basename($path);
623
-
624
-		return $data;
625
-	}
626
-
627
-	public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
628
-		$logger = $this->getLockLogger();
629
-		if ($logger) {
630
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
631
-			$logger->info(
632
-				sprintf(
633
-					'acquire %s lock on "%s" on storage "%s"',
634
-					$typeString,
635
-					$path,
636
-					$this->getId()
637
-				),
638
-				[
639
-					'app' => 'locking',
640
-				]
641
-			);
642
-		}
643
-		try {
644
-			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
645
-		} catch (LockedException $e) {
646
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
647
-			if ($logger) {
648
-				$logger->info($e->getMessage(), ['exception' => $e]);
649
-			}
650
-			throw $e;
651
-		}
652
-	}
653
-
654
-	public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
655
-		$logger = $this->getLockLogger();
656
-		if ($logger) {
657
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
658
-			$logger->info(
659
-				sprintf(
660
-					'release %s lock on "%s" on storage "%s"',
661
-					$typeString,
662
-					$path,
663
-					$this->getId()
664
-				),
665
-				[
666
-					'app' => 'locking',
667
-				]
668
-			);
669
-		}
670
-		try {
671
-			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
672
-		} catch (LockedException $e) {
673
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
674
-			if ($logger) {
675
-				$logger->info($e->getMessage(), ['exception' => $e]);
676
-			}
677
-			throw $e;
678
-		}
679
-	}
680
-
681
-	public function changeLock(string $path, int $type, ILockingProvider $provider): void {
682
-		$logger = $this->getLockLogger();
683
-		if ($logger) {
684
-			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
685
-			$logger->info(
686
-				sprintf(
687
-					'change lock on "%s" to %s on storage "%s"',
688
-					$path,
689
-					$typeString,
690
-					$this->getId()
691
-				),
692
-				[
693
-					'app' => 'locking',
694
-				]
695
-			);
696
-		}
697
-		try {
698
-			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
699
-		} catch (LockedException $e) {
700
-			$e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
701
-			if ($logger) {
702
-				$logger->info($e->getMessage(), ['exception' => $e]);
703
-			}
704
-			throw $e;
705
-		}
706
-	}
707
-
708
-	private function getLockLogger(): ?LoggerInterface {
709
-		if (is_null($this->shouldLogLocks)) {
710
-			$this->shouldLogLocks = Server::get(IConfig::class)->getSystemValueBool('filelocking.debug', false);
711
-			$this->logger = $this->shouldLogLocks ? Server::get(LoggerInterface::class) : null;
712
-		}
713
-		return $this->logger;
714
-	}
715
-
716
-	/**
717
-	 * @return array{available: bool, last_checked: int}
718
-	 */
719
-	public function getAvailability(): array {
720
-		return $this->getStorageCache()->getAvailability();
721
-	}
722
-
723
-	public function setAvailability(bool $isAvailable): void {
724
-		$this->getStorageCache()->setAvailability($isAvailable);
725
-	}
726
-
727
-	public function setOwner(?string $user): void {
728
-		$this->owner = $user;
729
-	}
730
-
731
-	public function needsPartFile(): bool {
732
-		return true;
733
-	}
734
-
735
-	public function writeStream(string $path, $stream, ?int $size = null): int {
736
-		$target = $this->fopen($path, 'w');
737
-		if (!$target) {
738
-			throw new GenericFileException("Failed to open $path for writing");
739
-		}
740
-		try {
741
-			[$count, $result] = Files::streamCopy($stream, $target, true);
742
-			if (!$result) {
743
-				throw new GenericFileException('Failed to copy stream');
744
-			}
745
-		} finally {
746
-			fclose($target);
747
-			fclose($stream);
748
-		}
749
-		return $count;
750
-	}
751
-
752
-	public function getDirectoryContent(string $directory): \Traversable {
753
-		$dh = $this->opendir($directory);
754
-
755
-		if ($dh === false) {
756
-			throw new StorageNotAvailableException('Directory listing failed');
757
-		}
758
-
759
-		if (is_resource($dh)) {
760
-			$basePath = rtrim($directory, '/');
761
-			while (($file = readdir($dh)) !== false) {
762
-				if (!Filesystem::isIgnoredDir($file)) {
763
-					$childPath = $basePath . '/' . trim($file, '/');
764
-					$metadata = $this->getMetaData($childPath);
765
-					if ($metadata !== null) {
766
-						yield $metadata;
767
-					}
768
-				}
769
-			}
770
-		}
771
-	}
56
+    use LocalTempFileTrait;
57
+
58
+    protected ?Cache $cache = null;
59
+    protected ?Scanner $scanner = null;
60
+    protected ?Watcher $watcher = null;
61
+    protected ?Propagator $propagator = null;
62
+    protected $storageCache;
63
+    protected ?Updater $updater = null;
64
+
65
+    protected array $mountOptions = [];
66
+    protected $owner = null;
67
+
68
+    private ?bool $shouldLogLocks = null;
69
+    private ?LoggerInterface $logger = null;
70
+    private ?IFilenameValidator $filenameValidator = null;
71
+
72
+    public function __construct(array $parameters) {
73
+    }
74
+
75
+    protected function remove(string $path): bool {
76
+        if ($this->file_exists($path)) {
77
+            if ($this->is_dir($path)) {
78
+                return $this->rmdir($path);
79
+            } elseif ($this->is_file($path)) {
80
+                return $this->unlink($path);
81
+            }
82
+        }
83
+        return false;
84
+    }
85
+
86
+    public function is_dir(string $path): bool {
87
+        return $this->filetype($path) === 'dir';
88
+    }
89
+
90
+    public function is_file(string $path): bool {
91
+        return $this->filetype($path) === 'file';
92
+    }
93
+
94
+    public function filesize(string $path): int|float|false {
95
+        if ($this->is_dir($path)) {
96
+            return 0; //by definition
97
+        } else {
98
+            $stat = $this->stat($path);
99
+            return isset($stat['size']) ? $stat['size'] : 0;
100
+        }
101
+    }
102
+
103
+    public function isReadable(string $path): bool {
104
+        // at least check whether it exists
105
+        // subclasses might want to implement this more thoroughly
106
+        return $this->file_exists($path);
107
+    }
108
+
109
+    public function isUpdatable(string $path): bool {
110
+        // at least check whether it exists
111
+        // subclasses might want to implement this more thoroughly
112
+        // a non-existing file/folder isn't updatable
113
+        return $this->file_exists($path);
114
+    }
115
+
116
+    public function isCreatable(string $path): bool {
117
+        if ($this->is_dir($path) && $this->isUpdatable($path)) {
118
+            return true;
119
+        }
120
+        return false;
121
+    }
122
+
123
+    public function isDeletable(string $path): bool {
124
+        if ($path === '' || $path === '/') {
125
+            return $this->isUpdatable($path);
126
+        }
127
+        $parent = dirname($path);
128
+        return $this->isUpdatable($parent) && $this->isUpdatable($path);
129
+    }
130
+
131
+    public function isSharable(string $path): bool {
132
+        return $this->isReadable($path);
133
+    }
134
+
135
+    public function getPermissions(string $path): int {
136
+        $permissions = 0;
137
+        if ($this->isCreatable($path)) {
138
+            $permissions |= \OCP\Constants::PERMISSION_CREATE;
139
+        }
140
+        if ($this->isReadable($path)) {
141
+            $permissions |= \OCP\Constants::PERMISSION_READ;
142
+        }
143
+        if ($this->isUpdatable($path)) {
144
+            $permissions |= \OCP\Constants::PERMISSION_UPDATE;
145
+        }
146
+        if ($this->isDeletable($path)) {
147
+            $permissions |= \OCP\Constants::PERMISSION_DELETE;
148
+        }
149
+        if ($this->isSharable($path)) {
150
+            $permissions |= \OCP\Constants::PERMISSION_SHARE;
151
+        }
152
+        return $permissions;
153
+    }
154
+
155
+    public function filemtime(string $path): int|false {
156
+        $stat = $this->stat($path);
157
+        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
158
+            return $stat['mtime'];
159
+        } else {
160
+            return 0;
161
+        }
162
+    }
163
+
164
+    public function file_get_contents(string $path): string|false {
165
+        $handle = $this->fopen($path, 'r');
166
+        if (!$handle) {
167
+            return false;
168
+        }
169
+        $data = stream_get_contents($handle);
170
+        fclose($handle);
171
+        return $data;
172
+    }
173
+
174
+    public function file_put_contents(string $path, mixed $data): int|float|false {
175
+        $handle = $this->fopen($path, 'w');
176
+        if (!$handle) {
177
+            return false;
178
+        }
179
+        $this->removeCachedFile($path);
180
+        $count = fwrite($handle, $data);
181
+        fclose($handle);
182
+        return $count;
183
+    }
184
+
185
+    public function rename(string $source, string $target): bool {
186
+        $this->remove($target);
187
+
188
+        $this->removeCachedFile($source);
189
+        return $this->copy($source, $target) && $this->remove($source);
190
+    }
191
+
192
+    public function copy(string $source, string $target): bool {
193
+        if ($this->is_dir($source)) {
194
+            $this->remove($target);
195
+            $dir = $this->opendir($source);
196
+            $this->mkdir($target);
197
+            while (($file = readdir($dir)) !== false) {
198
+                if (!Filesystem::isIgnoredDir($file)) {
199
+                    if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
200
+                        closedir($dir);
201
+                        return false;
202
+                    }
203
+                }
204
+            }
205
+            closedir($dir);
206
+            return true;
207
+        } else {
208
+            $sourceStream = $this->fopen($source, 'r');
209
+            $targetStream = $this->fopen($target, 'w');
210
+            [, $result] = Files::streamCopy($sourceStream, $targetStream, true);
211
+            if (!$result) {
212
+                Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
213
+            }
214
+            $this->removeCachedFile($target);
215
+            return $result;
216
+        }
217
+    }
218
+
219
+    public function getMimeType(string $path): string|false {
220
+        if ($this->is_dir($path)) {
221
+            return 'httpd/unix-directory';
222
+        } elseif ($this->file_exists($path)) {
223
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
224
+        } else {
225
+            return false;
226
+        }
227
+    }
228
+
229
+    public function hash(string $type, string $path, bool $raw = false): string|false {
230
+        $fh = $this->fopen($path, 'rb');
231
+        if (!$fh) {
232
+            return false;
233
+        }
234
+        $ctx = hash_init($type);
235
+        hash_update_stream($ctx, $fh);
236
+        fclose($fh);
237
+        return hash_final($ctx, $raw);
238
+    }
239
+
240
+    public function getLocalFile(string $path): string|false {
241
+        return $this->getCachedFile($path);
242
+    }
243
+
244
+    private function addLocalFolder(string $path, string $target): void {
245
+        $dh = $this->opendir($path);
246
+        if (is_resource($dh)) {
247
+            while (($file = readdir($dh)) !== false) {
248
+                if (!Filesystem::isIgnoredDir($file)) {
249
+                    if ($this->is_dir($path . '/' . $file)) {
250
+                        mkdir($target . '/' . $file);
251
+                        $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
252
+                    } else {
253
+                        $tmp = $this->toTmpFile($path . '/' . $file);
254
+                        rename($tmp, $target . '/' . $file);
255
+                    }
256
+                }
257
+            }
258
+        }
259
+    }
260
+
261
+    protected function searchInDir(string $query, string $dir = ''): array {
262
+        $files = [];
263
+        $dh = $this->opendir($dir);
264
+        if (is_resource($dh)) {
265
+            while (($item = readdir($dh)) !== false) {
266
+                if (Filesystem::isIgnoredDir($item)) {
267
+                    continue;
268
+                }
269
+                if (strstr(strtolower($item), strtolower($query)) !== false) {
270
+                    $files[] = $dir . '/' . $item;
271
+                }
272
+                if ($this->is_dir($dir . '/' . $item)) {
273
+                    $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
274
+                }
275
+            }
276
+        }
277
+        closedir($dh);
278
+        return $files;
279
+    }
280
+
281
+    /**
282
+     * @inheritDoc
283
+     * Check if a file or folder has been updated since $time
284
+     *
285
+     * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
286
+     * the mtime should always return false here. As a result storage implementations that always return false expect
287
+     * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
288
+     * Nextcloud filesystem.
289
+     */
290
+    public function hasUpdated(string $path, int $time): bool {
291
+        return $this->filemtime($path) > $time;
292
+    }
293
+
294
+    protected function getCacheDependencies(): CacheDependencies {
295
+        static $dependencies = null;
296
+        if (!$dependencies) {
297
+            $dependencies = Server::get(CacheDependencies::class);
298
+        }
299
+        return $dependencies;
300
+    }
301
+
302
+    public function getCache(string $path = '', ?IStorage $storage = null): ICache {
303
+        if (!$storage) {
304
+            $storage = $this;
305
+        }
306
+        /** @var self $storage */
307
+        if (!isset($storage->cache)) {
308
+            $storage->cache = new Cache($storage, $this->getCacheDependencies());
309
+        }
310
+        return $storage->cache;
311
+    }
312
+
313
+    public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
314
+        if (!$storage) {
315
+            $storage = $this;
316
+        }
317
+        if (!$storage->instanceOfStorage(self::class)) {
318
+            throw new \InvalidArgumentException('Storage is not of the correct class');
319
+        }
320
+        if (!isset($storage->scanner)) {
321
+            $storage->scanner = new Scanner($storage);
322
+        }
323
+        return $storage->scanner;
324
+    }
325
+
326
+    public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
327
+        if (!$storage) {
328
+            $storage = $this;
329
+        }
330
+        if (!isset($this->watcher)) {
331
+            $this->watcher = new Watcher($storage);
332
+            $globalPolicy = Server::get(IConfig::class)->getSystemValueInt('filesystem_check_changes', Watcher::CHECK_NEVER);
333
+            $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
334
+            $this->watcher->setCheckFilter($this->getMountOption('filesystem_check_filter'));
335
+        }
336
+        return $this->watcher;
337
+    }
338
+
339
+    public function getPropagator(?IStorage $storage = null): IPropagator {
340
+        if (!$storage) {
341
+            $storage = $this;
342
+        }
343
+        if (!$storage->instanceOfStorage(self::class)) {
344
+            throw new \InvalidArgumentException('Storage is not of the correct class');
345
+        }
346
+        /** @var self $storage */
347
+        if (!isset($storage->propagator)) {
348
+            $config = Server::get(IConfig::class);
349
+            $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getSystemValueString('instanceid')]);
350
+        }
351
+        return $storage->propagator;
352
+    }
353
+
354
+    public function getUpdater(?IStorage $storage = null): IUpdater {
355
+        if (!$storage) {
356
+            $storage = $this;
357
+        }
358
+        if (!$storage->instanceOfStorage(self::class)) {
359
+            throw new \InvalidArgumentException('Storage is not of the correct class');
360
+        }
361
+        /** @var self $storage */
362
+        if (!isset($storage->updater)) {
363
+            $storage->updater = new Updater($storage);
364
+        }
365
+        return $storage->updater;
366
+    }
367
+
368
+    public function getStorageCache(?IStorage $storage = null): \OC\Files\Cache\Storage {
369
+        /** @var Cache $cache */
370
+        $cache = $this->getCache(storage: $storage);
371
+        return $cache->getStorageCache();
372
+    }
373
+
374
+    public function getOwner(string $path): string|false {
375
+        if ($this->owner === null) {
376
+            $this->owner = \OC_User::getUser();
377
+        }
378
+
379
+        return $this->owner;
380
+    }
381
+
382
+    public function getETag(string $path): string|false {
383
+        return uniqid();
384
+    }
385
+
386
+    /**
387
+     * clean a path, i.e. remove all redundant '.' and '..'
388
+     * making sure that it can't point to higher than '/'
389
+     *
390
+     * @param string $path The path to clean
391
+     * @return string cleaned path
392
+     */
393
+    public function cleanPath(string $path): string {
394
+        if (strlen($path) == 0 || $path[0] != '/') {
395
+            $path = '/' . $path;
396
+        }
397
+
398
+        $output = [];
399
+        foreach (explode('/', $path) as $chunk) {
400
+            if ($chunk == '..') {
401
+                array_pop($output);
402
+            } elseif ($chunk == '.') {
403
+            } else {
404
+                $output[] = $chunk;
405
+            }
406
+        }
407
+        return implode('/', $output);
408
+    }
409
+
410
+    /**
411
+     * Test a storage for availability
412
+     */
413
+    public function test(): bool {
414
+        try {
415
+            if ($this->stat('')) {
416
+                return true;
417
+            }
418
+            Server::get(LoggerInterface::class)->info('External storage not available: stat() failed');
419
+            return false;
420
+        } catch (\Exception $e) {
421
+            Server::get(LoggerInterface::class)->warning(
422
+                'External storage not available: ' . $e->getMessage(),
423
+                ['exception' => $e]
424
+            );
425
+            return false;
426
+        }
427
+    }
428
+
429
+    public function free_space(string $path): int|float|false {
430
+        return \OCP\Files\FileInfo::SPACE_UNKNOWN;
431
+    }
432
+
433
+    public function isLocal(): bool {
434
+        // the common implementation returns a temporary file by
435
+        // default, which is not local
436
+        return false;
437
+    }
438
+
439
+    /**
440
+     * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
441
+     */
442
+    public function instanceOfStorage(string $class): bool {
443
+        if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
444
+            // FIXME Temporary fix to keep existing checks working
445
+            $class = '\OCA\Files_Sharing\SharedStorage';
446
+        }
447
+        return is_a($this, $class);
448
+    }
449
+
450
+    #[Override]
451
+    public function getDirectDownload(string $path): array|false {
452
+        return false;
453
+    }
454
+
455
+    #[Override]
456
+    public function getDirectDownloadById(string $fileId): array|false {
457
+        return false;
458
+    }
459
+
460
+    public function verifyPath(string $path, string $fileName): void {
461
+        $this->getFilenameValidator()
462
+            ->validateFilename($fileName);
463
+
464
+        // verify also the path is valid
465
+        if ($path && $path !== '/' && $path !== '.') {
466
+            try {
467
+                $this->verifyPath(dirname($path), basename($path));
468
+            } catch (InvalidPathException $e) {
469
+                // Ignore invalid file type exceptions on directories
470
+                if ($e->getCode() !== FilenameValidator::INVALID_FILE_TYPE) {
471
+                    $l = \OCP\Util::getL10N('lib');
472
+                    throw new InvalidPathException($l->t('Invalid parent path'), previous: $e);
473
+                }
474
+            }
475
+        }
476
+    }
477
+
478
+    /**
479
+     * Get the filename validator
480
+     * (cached for performance)
481
+     */
482
+    protected function getFilenameValidator(): IFilenameValidator {
483
+        if ($this->filenameValidator === null) {
484
+            $this->filenameValidator = Server::get(IFilenameValidator::class);
485
+        }
486
+        return $this->filenameValidator;
487
+    }
488
+
489
+    public function setMountOptions(array $options): void {
490
+        $this->mountOptions = $options;
491
+    }
492
+
493
+    public function getMountOption(string $name, mixed $default = null): mixed {
494
+        return $this->mountOptions[$name] ?? $default;
495
+    }
496
+
497
+    public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): bool {
498
+        if ($sourceStorage === $this) {
499
+            return $this->copy($sourceInternalPath, $targetInternalPath);
500
+        }
501
+
502
+        if ($sourceStorage->is_dir($sourceInternalPath)) {
503
+            $dh = $sourceStorage->opendir($sourceInternalPath);
504
+            $result = $this->mkdir($targetInternalPath);
505
+            if (is_resource($dh)) {
506
+                $result = true;
507
+                while ($result && ($file = readdir($dh)) !== false) {
508
+                    if (!Filesystem::isIgnoredDir($file)) {
509
+                        $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
510
+                    }
511
+                }
512
+            }
513
+        } else {
514
+            $source = $sourceStorage->fopen($sourceInternalPath, 'r');
515
+            $result = false;
516
+            if ($source) {
517
+                try {
518
+                    $this->writeStream($targetInternalPath, $source);
519
+                    $result = true;
520
+                } catch (\Exception $e) {
521
+                    Server::get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
522
+                }
523
+            }
524
+
525
+            if ($result && $preserveMtime) {
526
+                $mtime = $sourceStorage->filemtime($sourceInternalPath);
527
+                $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
528
+            }
529
+
530
+            if (!$result) {
531
+                // delete partially written target file
532
+                $this->unlink($targetInternalPath);
533
+                // delete cache entry that was created by fopen
534
+                $this->getCache()->remove($targetInternalPath);
535
+            }
536
+        }
537
+        return (bool)$result;
538
+    }
539
+
540
+    /**
541
+     * Check if a storage is the same as the current one, including wrapped storages
542
+     */
543
+    private function isSameStorage(IStorage $storage): bool {
544
+        while ($storage->instanceOfStorage(Wrapper::class)) {
545
+            /**
546
+             * @var Wrapper $storage
547
+             */
548
+            $storage = $storage->getWrapperStorage();
549
+        }
550
+
551
+        return $storage === $this;
552
+    }
553
+
554
+    public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
555
+        if (
556
+            !$sourceStorage->instanceOfStorage(Encryption::class)
557
+            && $this->isSameStorage($sourceStorage)
558
+        ) {
559
+            // resolve any jailed paths
560
+            while ($sourceStorage->instanceOfStorage(Jail::class)) {
561
+                /**
562
+                 * @var Jail $sourceStorage
563
+                 */
564
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
565
+                $sourceStorage = $sourceStorage->getUnjailedStorage();
566
+            }
567
+
568
+            return $this->rename($sourceInternalPath, $targetInternalPath);
569
+        }
570
+
571
+        if (!$sourceStorage->isDeletable($sourceInternalPath)) {
572
+            return false;
573
+        }
574
+
575
+        $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
576
+        if ($result) {
577
+            if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
578
+                /** @var ObjectStoreStorage $sourceStorage */
579
+                $sourceStorage->setPreserveCacheOnDelete(true);
580
+            }
581
+            try {
582
+                if ($sourceStorage->is_dir($sourceInternalPath)) {
583
+                    $result = $sourceStorage->rmdir($sourceInternalPath);
584
+                } else {
585
+                    $result = $sourceStorage->unlink($sourceInternalPath);
586
+                }
587
+            } finally {
588
+                if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
589
+                    /** @var ObjectStoreStorage $sourceStorage */
590
+                    $sourceStorage->setPreserveCacheOnDelete(false);
591
+                }
592
+            }
593
+        }
594
+        return $result;
595
+    }
596
+
597
+    public function getMetaData(string $path): ?array {
598
+        if (Filesystem::isFileBlacklisted($path)) {
599
+            throw new ForbiddenException('Invalid path: ' . $path, false);
600
+        }
601
+
602
+        $permissions = $this->getPermissions($path);
603
+        if (!$permissions & \OCP\Constants::PERMISSION_READ) {
604
+            //can't read, nothing we can do
605
+            return null;
606
+        }
607
+
608
+        $data = [];
609
+        $data['mimetype'] = $this->getMimeType($path);
610
+        $data['mtime'] = $this->filemtime($path);
611
+        if ($data['mtime'] === false) {
612
+            $data['mtime'] = time();
613
+        }
614
+        if ($data['mimetype'] == 'httpd/unix-directory') {
615
+            $data['size'] = -1; //unknown
616
+        } else {
617
+            $data['size'] = $this->filesize($path);
618
+        }
619
+        $data['etag'] = $this->getETag($path);
620
+        $data['storage_mtime'] = $data['mtime'];
621
+        $data['permissions'] = $permissions;
622
+        $data['name'] = basename($path);
623
+
624
+        return $data;
625
+    }
626
+
627
+    public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
628
+        $logger = $this->getLockLogger();
629
+        if ($logger) {
630
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
631
+            $logger->info(
632
+                sprintf(
633
+                    'acquire %s lock on "%s" on storage "%s"',
634
+                    $typeString,
635
+                    $path,
636
+                    $this->getId()
637
+                ),
638
+                [
639
+                    'app' => 'locking',
640
+                ]
641
+            );
642
+        }
643
+        try {
644
+            $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
645
+        } catch (LockedException $e) {
646
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
647
+            if ($logger) {
648
+                $logger->info($e->getMessage(), ['exception' => $e]);
649
+            }
650
+            throw $e;
651
+        }
652
+    }
653
+
654
+    public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
655
+        $logger = $this->getLockLogger();
656
+        if ($logger) {
657
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
658
+            $logger->info(
659
+                sprintf(
660
+                    'release %s lock on "%s" on storage "%s"',
661
+                    $typeString,
662
+                    $path,
663
+                    $this->getId()
664
+                ),
665
+                [
666
+                    'app' => 'locking',
667
+                ]
668
+            );
669
+        }
670
+        try {
671
+            $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
672
+        } catch (LockedException $e) {
673
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
674
+            if ($logger) {
675
+                $logger->info($e->getMessage(), ['exception' => $e]);
676
+            }
677
+            throw $e;
678
+        }
679
+    }
680
+
681
+    public function changeLock(string $path, int $type, ILockingProvider $provider): void {
682
+        $logger = $this->getLockLogger();
683
+        if ($logger) {
684
+            $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
685
+            $logger->info(
686
+                sprintf(
687
+                    'change lock on "%s" to %s on storage "%s"',
688
+                    $path,
689
+                    $typeString,
690
+                    $this->getId()
691
+                ),
692
+                [
693
+                    'app' => 'locking',
694
+                ]
695
+            );
696
+        }
697
+        try {
698
+            $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
699
+        } catch (LockedException $e) {
700
+            $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path);
701
+            if ($logger) {
702
+                $logger->info($e->getMessage(), ['exception' => $e]);
703
+            }
704
+            throw $e;
705
+        }
706
+    }
707
+
708
+    private function getLockLogger(): ?LoggerInterface {
709
+        if (is_null($this->shouldLogLocks)) {
710
+            $this->shouldLogLocks = Server::get(IConfig::class)->getSystemValueBool('filelocking.debug', false);
711
+            $this->logger = $this->shouldLogLocks ? Server::get(LoggerInterface::class) : null;
712
+        }
713
+        return $this->logger;
714
+    }
715
+
716
+    /**
717
+     * @return array{available: bool, last_checked: int}
718
+     */
719
+    public function getAvailability(): array {
720
+        return $this->getStorageCache()->getAvailability();
721
+    }
722
+
723
+    public function setAvailability(bool $isAvailable): void {
724
+        $this->getStorageCache()->setAvailability($isAvailable);
725
+    }
726
+
727
+    public function setOwner(?string $user): void {
728
+        $this->owner = $user;
729
+    }
730
+
731
+    public function needsPartFile(): bool {
732
+        return true;
733
+    }
734
+
735
+    public function writeStream(string $path, $stream, ?int $size = null): int {
736
+        $target = $this->fopen($path, 'w');
737
+        if (!$target) {
738
+            throw new GenericFileException("Failed to open $path for writing");
739
+        }
740
+        try {
741
+            [$count, $result] = Files::streamCopy($stream, $target, true);
742
+            if (!$result) {
743
+                throw new GenericFileException('Failed to copy stream');
744
+            }
745
+        } finally {
746
+            fclose($target);
747
+            fclose($stream);
748
+        }
749
+        return $count;
750
+    }
751
+
752
+    public function getDirectoryContent(string $directory): \Traversable {
753
+        $dh = $this->opendir($directory);
754
+
755
+        if ($dh === false) {
756
+            throw new StorageNotAvailableException('Directory listing failed');
757
+        }
758
+
759
+        if (is_resource($dh)) {
760
+            $basePath = rtrim($directory, '/');
761
+            while (($file = readdir($dh)) !== false) {
762
+                if (!Filesystem::isIgnoredDir($file)) {
763
+                    $childPath = $basePath . '/' . trim($file, '/');
764
+                    $metadata = $this->getMetaData($childPath);
765
+                    if ($metadata !== null) {
766
+                        yield $metadata;
767
+                    }
768
+                }
769
+            }
770
+        }
771
+    }
772 772
 }
Please login to merge, or discard this patch.
tests/lib/Files/Cache/WatcherTest.php 1 patch
Indentation   +196 added lines, -196 removed lines patch added patch discarded remove patch
@@ -25,214 +25,214 @@
 block discarded – undo
25 25
 #[\PHPUnit\Framework\Attributes\Medium]
26 26
 #[\PHPUnit\Framework\Attributes\Group('DB')]
27 27
 class WatcherTest extends \Test\TestCase {
28
-	/**
29
-	 * @var Storage[] $storages
30
-	 */
31
-	private $storages = [];
28
+    /**
29
+     * @var Storage[] $storages
30
+     */
31
+    private $storages = [];
32 32
 
33
-	protected function setUp(): void {
34
-		parent::setUp();
33
+    protected function setUp(): void {
34
+        parent::setUp();
35 35
 
36
-		$this->loginAsUser();
37
-	}
36
+        $this->loginAsUser();
37
+    }
38 38
 
39
-	protected function tearDown(): void {
40
-		foreach ($this->storages as $storage) {
41
-			$cache = $storage->getCache();
42
-			$ids = $cache->getAll();
43
-			$cache->clear();
44
-		}
39
+    protected function tearDown(): void {
40
+        foreach ($this->storages as $storage) {
41
+            $cache = $storage->getCache();
42
+            $ids = $cache->getAll();
43
+            $cache->clear();
44
+        }
45 45
 
46
-		$this->logout();
47
-		parent::tearDown();
48
-	}
46
+        $this->logout();
47
+        parent::tearDown();
48
+    }
49 49
 
50
-	public function testWatcher(): void {
51
-		$storage = $this->getTestStorage();
52
-		$cache = $storage->getCache();
53
-		$updater = $storage->getWatcher();
54
-		$updater->setPolicy(Watcher::CHECK_ONCE);
50
+    public function testWatcher(): void {
51
+        $storage = $this->getTestStorage();
52
+        $cache = $storage->getCache();
53
+        $updater = $storage->getWatcher();
54
+        $updater->setPolicy(Watcher::CHECK_ONCE);
55 55
 
56
-		//set the mtime to the past so it can detect an mtime change
57
-		$cache->put('', ['storage_mtime' => 10]);
56
+        //set the mtime to the past so it can detect an mtime change
57
+        $cache->put('', ['storage_mtime' => 10]);
58 58
 
59
-		$this->assertTrue($cache->inCache('folder/bar.txt'));
60
-		$this->assertTrue($cache->inCache('folder/bar2.txt'));
59
+        $this->assertTrue($cache->inCache('folder/bar.txt'));
60
+        $this->assertTrue($cache->inCache('folder/bar2.txt'));
61 61
 
62
-		$this->assertFalse($cache->inCache('bar.test'));
63
-		$storage->file_put_contents('bar.test', 'foo');
64
-		$updater->checkUpdate('');
65
-		$this->assertTrue($cache->inCache('bar.test'));
66
-		$cachedData = $cache->get('bar.test');
67
-		$this->assertEquals(3, $cachedData['size']);
62
+        $this->assertFalse($cache->inCache('bar.test'));
63
+        $storage->file_put_contents('bar.test', 'foo');
64
+        $updater->checkUpdate('');
65
+        $this->assertTrue($cache->inCache('bar.test'));
66
+        $cachedData = $cache->get('bar.test');
67
+        $this->assertEquals(3, $cachedData['size']);
68 68
 
69
-		$cache->put('bar.test', ['storage_mtime' => 10]);
70
-		$storage->file_put_contents('bar.test', 'test data');
69
+        $cache->put('bar.test', ['storage_mtime' => 10]);
70
+        $storage->file_put_contents('bar.test', 'test data');
71 71
 
72
-		// make sure that PHP can read the new size correctly
73
-		clearstatcache();
72
+        // make sure that PHP can read the new size correctly
73
+        clearstatcache();
74 74
 
75
-		$updater->checkUpdate('bar.test');
76
-		$cachedData = $cache->get('bar.test');
77
-		$this->assertEquals(9, $cachedData['size']);
75
+        $updater->checkUpdate('bar.test');
76
+        $cachedData = $cache->get('bar.test');
77
+        $this->assertEquals(9, $cachedData['size']);
78 78
 
79
-		$cache->put('folder', ['storage_mtime' => 10]);
79
+        $cache->put('folder', ['storage_mtime' => 10]);
80 80
 
81
-		$storage->unlink('folder/bar2.txt');
82
-		$updater->checkUpdate('folder');
81
+        $storage->unlink('folder/bar2.txt');
82
+        $updater->checkUpdate('folder');
83 83
 
84
-		$this->assertTrue($cache->inCache('folder/bar.txt'));
85
-		$this->assertFalse($cache->inCache('folder/bar2.txt'));
86
-	}
87
-
88
-
89
-	public function testFileToFolder(): void {
90
-		$storage = $this->getTestStorage();
91
-		$cache = $storage->getCache();
92
-		$updater = $storage->getWatcher();
93
-		$updater->setPolicy(Watcher::CHECK_ONCE);
94
-
95
-		//set the mtime to the past so it can detect an mtime change
96
-		$cache->put('', ['storage_mtime' => 10]);
97
-
98
-		$storage->unlink('foo.txt');
99
-		$storage->rename('folder', 'foo.txt');
100
-		$updater->checkUpdate('');
101
-
102
-		$entry = $cache->get('foo.txt');
103
-		$this->assertEquals('httpd/unix-directory', $entry['mimetype']);
104
-		$this->assertFalse($cache->inCache('folder'));
105
-		$this->assertFalse($cache->inCache('folder/bar.txt'));
106
-
107
-		$storage = $this->getTestStorage();
108
-		$cache = $storage->getCache();
109
-		$updater = $storage->getWatcher();
110
-		$updater->setPolicy(Watcher::CHECK_ONCE);
111
-
112
-		//set the mtime to the past so it can detect an mtime change
113
-		$cache->put('foo.txt', ['storage_mtime' => 10]);
114
-
115
-		$storage->unlink('foo.txt');
116
-		$storage->rename('folder', 'foo.txt');
117
-		$updater->checkUpdate('foo.txt');
118
-
119
-		$entry = $cache->get('foo.txt');
120
-		$this->assertEquals('httpd/unix-directory', $entry['mimetype']);
121
-		$this->assertTrue($cache->inCache('foo.txt/bar.txt'));
122
-	}
123
-
124
-	public function testPolicyNever(): void {
125
-		$storage = $this->getTestStorage();
126
-		$cache = $storage->getCache();
127
-		$updater = $storage->getWatcher();
128
-
129
-		//set the mtime to the past so it can detect an mtime change
130
-		$cache->put('foo.txt', ['storage_mtime' => 10]);
131
-
132
-		$updater->setPolicy(Watcher::CHECK_NEVER);
133
-
134
-		$storage->file_put_contents('foo.txt', 'q');
135
-		$this->assertFalse($updater->checkUpdate('foo.txt'));
136
-
137
-		$cache->put('foo.txt', ['storage_mtime' => 20]);
138
-		$storage->file_put_contents('foo.txt', 'w');
139
-		$this->assertFalse($updater->checkUpdate('foo.txt'));
140
-	}
141
-
142
-	public function testPolicyOnce(): void {
143
-		$storage = $this->getTestStorage();
144
-		$cache = $storage->getCache();
145
-		$updater = $storage->getWatcher();
146
-
147
-		//set the mtime to the past so it can detect an mtime change
148
-		$cache->put('foo.txt', ['storage_mtime' => 10]);
149
-
150
-		$updater->setPolicy(Watcher::CHECK_ONCE);
151
-
152
-		$storage->file_put_contents('foo.txt', 'q');
153
-		$this->assertTrue($updater->checkUpdate('foo.txt'));
154
-
155
-		$cache->put('foo.txt', ['storage_mtime' => 20]);
156
-		$storage->file_put_contents('foo.txt', 'w');
157
-		$this->assertFalse($updater->checkUpdate('foo.txt'));
158
-	}
159
-
160
-	public function testPolicyAlways(): void {
161
-		$storage = $this->getTestStorage();
162
-		$cache = $storage->getCache();
163
-		$updater = $storage->getWatcher();
164
-
165
-		//set the mtime to the past so it can detect an mtime change
166
-		$cache->put('foo.txt', ['storage_mtime' => 10]);
167
-
168
-		$updater->setPolicy(Watcher::CHECK_ALWAYS);
169
-
170
-		$storage->file_put_contents('foo.txt', 'q');
171
-		$this->assertTrue($updater->checkUpdate('foo.txt'));
172
-
173
-		$cache->put('foo.txt', ['storage_mtime' => 20]);
174
-		$storage->file_put_contents('foo.txt', 'w');
175
-		$this->assertTrue($updater->checkUpdate('foo.txt'));
176
-	}
177
-
178
-	/**
179
-	 * @param bool $scan
180
-	 * @return Storage
181
-	 */
182
-	private function getTestStorage($scan = true) {
183
-		$storage = new Temporary([]);
184
-		$textData = "dummy file data\n";
185
-		$imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
186
-		$storage->mkdir('folder');
187
-		$storage->file_put_contents('foo.txt', $textData);
188
-		$storage->file_put_contents('foo.png', $imgData);
189
-		$storage->file_put_contents('folder/bar.txt', $textData);
190
-		$storage->file_put_contents('folder/bar2.txt', $textData);
191
-
192
-		if ($scan) {
193
-			$scanner = $storage->getScanner();
194
-			$scanner->scan('');
195
-		}
196
-		$this->storages[] = $storage;
197
-		return $storage;
198
-	}
199
-
200
-	public static function checkFilterProvider(): array {
201
-		return [
202
-			[null, [
203
-				'' => true,
204
-				'foo' => true,
205
-				'foo.txt' => true,
206
-			]],
207
-			['/^.+$/', [
208
-				'' => false,
209
-				'foo' => true,
210
-				'foo.txt' => true,
211
-			]],
212
-			['/^.+\..+$/', [
213
-				'' => false,
214
-				'foo' => false,
215
-				'foo.txt' => true,
216
-			]]
217
-		];
218
-	}
219
-
220
-	#[DataProvider('checkFilterProvider')]
221
-	public function testCheckFilter($filter, $paths) {
222
-		$storage = $this->createMock(IStorage::class);
223
-		$storage->method('hasUpdated')
224
-			->willReturn(true);
225
-		$watcher = new Watcher($storage);
226
-		$watcher->setPolicy(IWatcher::CHECK_ALWAYS);
227
-
228
-		$watcher->setCheckFilter($filter);
229
-
230
-		$entry = new CacheEntry([
231
-			'storage_mtime' => 0,
232
-		]);
233
-
234
-		foreach ($paths as $patch => $shouldUpdate) {
235
-			$this->assertEquals($shouldUpdate, $watcher->needsUpdate($patch, $entry));
236
-		}
237
-	}
84
+        $this->assertTrue($cache->inCache('folder/bar.txt'));
85
+        $this->assertFalse($cache->inCache('folder/bar2.txt'));
86
+    }
87
+
88
+
89
+    public function testFileToFolder(): void {
90
+        $storage = $this->getTestStorage();
91
+        $cache = $storage->getCache();
92
+        $updater = $storage->getWatcher();
93
+        $updater->setPolicy(Watcher::CHECK_ONCE);
94
+
95
+        //set the mtime to the past so it can detect an mtime change
96
+        $cache->put('', ['storage_mtime' => 10]);
97
+
98
+        $storage->unlink('foo.txt');
99
+        $storage->rename('folder', 'foo.txt');
100
+        $updater->checkUpdate('');
101
+
102
+        $entry = $cache->get('foo.txt');
103
+        $this->assertEquals('httpd/unix-directory', $entry['mimetype']);
104
+        $this->assertFalse($cache->inCache('folder'));
105
+        $this->assertFalse($cache->inCache('folder/bar.txt'));
106
+
107
+        $storage = $this->getTestStorage();
108
+        $cache = $storage->getCache();
109
+        $updater = $storage->getWatcher();
110
+        $updater->setPolicy(Watcher::CHECK_ONCE);
111
+
112
+        //set the mtime to the past so it can detect an mtime change
113
+        $cache->put('foo.txt', ['storage_mtime' => 10]);
114
+
115
+        $storage->unlink('foo.txt');
116
+        $storage->rename('folder', 'foo.txt');
117
+        $updater->checkUpdate('foo.txt');
118
+
119
+        $entry = $cache->get('foo.txt');
120
+        $this->assertEquals('httpd/unix-directory', $entry['mimetype']);
121
+        $this->assertTrue($cache->inCache('foo.txt/bar.txt'));
122
+    }
123
+
124
+    public function testPolicyNever(): void {
125
+        $storage = $this->getTestStorage();
126
+        $cache = $storage->getCache();
127
+        $updater = $storage->getWatcher();
128
+
129
+        //set the mtime to the past so it can detect an mtime change
130
+        $cache->put('foo.txt', ['storage_mtime' => 10]);
131
+
132
+        $updater->setPolicy(Watcher::CHECK_NEVER);
133
+
134
+        $storage->file_put_contents('foo.txt', 'q');
135
+        $this->assertFalse($updater->checkUpdate('foo.txt'));
136
+
137
+        $cache->put('foo.txt', ['storage_mtime' => 20]);
138
+        $storage->file_put_contents('foo.txt', 'w');
139
+        $this->assertFalse($updater->checkUpdate('foo.txt'));
140
+    }
141
+
142
+    public function testPolicyOnce(): void {
143
+        $storage = $this->getTestStorage();
144
+        $cache = $storage->getCache();
145
+        $updater = $storage->getWatcher();
146
+
147
+        //set the mtime to the past so it can detect an mtime change
148
+        $cache->put('foo.txt', ['storage_mtime' => 10]);
149
+
150
+        $updater->setPolicy(Watcher::CHECK_ONCE);
151
+
152
+        $storage->file_put_contents('foo.txt', 'q');
153
+        $this->assertTrue($updater->checkUpdate('foo.txt'));
154
+
155
+        $cache->put('foo.txt', ['storage_mtime' => 20]);
156
+        $storage->file_put_contents('foo.txt', 'w');
157
+        $this->assertFalse($updater->checkUpdate('foo.txt'));
158
+    }
159
+
160
+    public function testPolicyAlways(): void {
161
+        $storage = $this->getTestStorage();
162
+        $cache = $storage->getCache();
163
+        $updater = $storage->getWatcher();
164
+
165
+        //set the mtime to the past so it can detect an mtime change
166
+        $cache->put('foo.txt', ['storage_mtime' => 10]);
167
+
168
+        $updater->setPolicy(Watcher::CHECK_ALWAYS);
169
+
170
+        $storage->file_put_contents('foo.txt', 'q');
171
+        $this->assertTrue($updater->checkUpdate('foo.txt'));
172
+
173
+        $cache->put('foo.txt', ['storage_mtime' => 20]);
174
+        $storage->file_put_contents('foo.txt', 'w');
175
+        $this->assertTrue($updater->checkUpdate('foo.txt'));
176
+    }
177
+
178
+    /**
179
+     * @param bool $scan
180
+     * @return Storage
181
+     */
182
+    private function getTestStorage($scan = true) {
183
+        $storage = new Temporary([]);
184
+        $textData = "dummy file data\n";
185
+        $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
186
+        $storage->mkdir('folder');
187
+        $storage->file_put_contents('foo.txt', $textData);
188
+        $storage->file_put_contents('foo.png', $imgData);
189
+        $storage->file_put_contents('folder/bar.txt', $textData);
190
+        $storage->file_put_contents('folder/bar2.txt', $textData);
191
+
192
+        if ($scan) {
193
+            $scanner = $storage->getScanner();
194
+            $scanner->scan('');
195
+        }
196
+        $this->storages[] = $storage;
197
+        return $storage;
198
+    }
199
+
200
+    public static function checkFilterProvider(): array {
201
+        return [
202
+            [null, [
203
+                '' => true,
204
+                'foo' => true,
205
+                'foo.txt' => true,
206
+            ]],
207
+            ['/^.+$/', [
208
+                '' => false,
209
+                'foo' => true,
210
+                'foo.txt' => true,
211
+            ]],
212
+            ['/^.+\..+$/', [
213
+                '' => false,
214
+                'foo' => false,
215
+                'foo.txt' => true,
216
+            ]]
217
+        ];
218
+    }
219
+
220
+    #[DataProvider('checkFilterProvider')]
221
+    public function testCheckFilter($filter, $paths) {
222
+        $storage = $this->createMock(IStorage::class);
223
+        $storage->method('hasUpdated')
224
+            ->willReturn(true);
225
+        $watcher = new Watcher($storage);
226
+        $watcher->setPolicy(IWatcher::CHECK_ALWAYS);
227
+
228
+        $watcher->setCheckFilter($filter);
229
+
230
+        $entry = new CacheEntry([
231
+            'storage_mtime' => 0,
232
+        ]);
233
+
234
+        foreach ($paths as $patch => $shouldUpdate) {
235
+            $this->assertEquals($shouldUpdate, $watcher->needsUpdate($patch, $entry));
236
+        }
237
+    }
238 238
 }
Please login to merge, or discard this patch.