Passed
Push — master ( 51f739...732353 )
by Pauli
02:31
created

LibrarySettings::setExcludedPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2019, 2020
11
 */
12
13
namespace OCA\Music\Utility;
14
15
use OCP\Files\Folder;
16
use OCP\Files\IRootFolder;
17
use OCP\IConfig;
18
19
use OCA\Music\AppFramework\Core\Logger;
20
21
/**
22
 * Manage the user-specific music folder setting
23
 */
24
class LibrarySettings {
25
	private $appName;
26
	private $configManager;
27
	private $rootFolder;
28
	private $logger;
29
30
	public function __construct(
31
			string $appName,
32
			IConfig $configManager,
33
			IRootFolder $rootFolder,
34
			Logger $logger) {
35
		$this->appName = $appName;
36
		$this->configManager = $configManager;
37
		$this->rootFolder = $rootFolder;
38
		$this->logger = $logger;
39
	}
40
41
	public function setScanMetadataEnabled(string $userId, bool $enabled) : void {
42
		$this->configManager->setUserValue($userId, $this->appName, 'scan_metadata', $enabled ? 1 : 0);
43
	}
44
45
	public function getScanMetadataEnabled(string $userId) : bool {
46
		$value = $this->configManager->getUserValue($userId, $this->appName, 'scan_metadata', 1);
47
		return ($value > 0);
48
	}
49
50
	public function setPath(string $userId, string $path) : bool {
51
		$success = false;
52
53
		$userHome = $this->rootFolder->getUserFolder($userId);
54
		$element = $userHome->get($path);
55
		if ($element instanceof \OCP\Files\Folder) {
56
			if ($path[0] !== '/') {
57
				$path = '/' . $path;
58
			}
59
			if ($path[\strlen($path)-1] !== '/') {
60
				$path .= '/';
61
			}
62
			$this->configManager->setUserValue($userId, $this->appName, 'path', $path);
63
			$success = true;
64
		}
65
66
		return $success;
67
	}
68
69
	public function getPath(string $userId) : string {
70
		$path = $this->configManager->getUserValue($userId, $this->appName, 'path');
71
		return $path ?: '/';
72
	}
73
74
	/**
75
	 * @param string[] $paths
76
	 */
77
	public function setExcludedPaths(string $userId, array $paths) : bool {
78
		$this->configManager->setUserValue($userId, $this->appName, 'excluded_paths', \json_encode($paths));
79
		return true;
80
	}
81
82
	/**
83
	 * @return string[]
84
	 */
85
	public function getExcludedPaths(string $userId) : array {
86
		$paths = $this->configManager->getUserValue($userId, $this->appName, 'excluded_paths');
87
		if (empty($paths)) {
88
			return [];
89
		} else {
90
			return \json_decode($paths);
91
		}
92
	}
93
94
	public function getFolder(string $userId) : Folder {
95
		$userHome = $this->rootFolder->getUserFolder($userId);
96
		$path = $this->getPath($userId);
97
		return Util::getFolderFromRelativePath($userHome, $path);
98
	}
99
100
	public function pathBelongsToMusicLibrary(string $filePath, string $userId) : bool {
101
		$filePath = self::normalizePath($filePath);
102
		$musicPath = self::normalizePath($this->getFolder($userId)->getPath());
103
104
		return Util::startsWith($filePath, $musicPath)
105
				&& !$this->pathIsExcluded($filePath, $musicPath, $userId);
106
	}
107
108
	private function pathIsExcluded(string $filePath, string $musicPath, string $userId) : bool {
109
		$userRootPath = $this->rootFolder->getUserFolder($userId)->getPath();
110
		$excludedPaths = $this->getExcludedPaths($userId);
111
112
		foreach ($excludedPaths as $excludedPath) {
113
			if (Util::startsWith($excludedPath, '/')) {
114
				$excludedPath = $userRootPath . $excludedPath;
115
			} else {
116
				$excludedPath = $musicPath . '/' . $excludedPath;
117
			}
118
			if (self::pathMatchesPattern($filePath, $excludedPath)) {
119
				return true;
120
			}
121
		}
122
123
		return false;
124
	}
125
126
	private static function pathMatchesPattern(string $path, string $pattern) : bool {
127
		// normalize the pattern so that there is no trailing '/'
128
		$pattern = \rtrim($pattern, '/');
129
130
		if (\strpos($pattern, '*') === false && \strpos($pattern, '?') === false) {
131
			// no wildcards, begininning of the path should match the pattern exactly
132
			// and the next character after the matching part (if any) should be '/'
133
			$patternLen = \strlen($pattern);
134
			return Util::startsWith($path, $pattern)
135
				&& (\strlen($path) === $patternLen || $path[$patternLen] === '/');
136
		} else {
137
			// some wildcard characters in the pattern, convert the pattern into regex:
138
			// - '?' matches exactly one arbitrary character except the directory separator '/'
139
			// - '*' matches zero or more arbitrary characters except the directory separator '/'
140
			// - '**' matches zero or more arbitrary characters including directory separator '/'
141
			$pattern = \preg_quote($pattern, '/');				// escape regex meta characters
142
			$pattern = \str_replace('\*\*', '.*', $pattern);	// convert ** to its regex equivaleant
143
			$pattern = \str_replace('\*', '[^\/]*', $pattern);	// convert * to its regex equivaleant
144
			$pattern = \str_replace('\?', '[^\/]', $pattern);	// convert ? to its regex equivaleant
145
			$pattern = $pattern . '(\/.*)?$';					// after given pattern, there should be '/' or nothing
146
			$pattern = '/' . $pattern . '/';
147
148
			return (\preg_match($pattern, $path) === 1);
149
		}
150
	}
151
152
	private static function normalizePath(string $path) : string {
153
		// The file system may create paths where there are two consecutive
154
		// path seprator characters (/). This was seen with an external local
155
		// folder on NC13, but may apply to other cases, too. Normalize such paths.
156
		return \str_replace('//', '/', $path);
157
	}
158
}
159