Completed
Push — master ( 453450...6f0537 )
by
unknown
37:16
created
apps/files_versions/lib/Db/VersionsMapper.php 1 patch
Indentation   +84 added lines, -84 removed lines patch added patch discarded remove patch
@@ -17,88 +17,88 @@
 block discarded – undo
17 17
  * @extends QBMapper<VersionEntity>
18 18
  */
19 19
 class VersionsMapper extends QBMapper {
20
-	public function __construct(IDBConnection $db) {
21
-		parent::__construct($db, 'files_versions', VersionEntity::class);
22
-	}
23
-
24
-	/**
25
-	 * @return VersionEntity[]
26
-	 */
27
-	public function findAllVersionsForFileId(int $fileId): array {
28
-		$qb = $this->db->getQueryBuilder();
29
-
30
-		$qb->select('*')
31
-			->from($this->getTableName())
32
-			->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)));
33
-
34
-		return $this->findEntities($qb);
35
-	}
36
-
37
-	/**
38
-	 * @return VersionEntity
39
-	 */
40
-	public function findCurrentVersionForFileId(int $fileId): VersionEntity {
41
-		$qb = $this->db->getQueryBuilder();
42
-
43
-		$qb->select('*')
44
-			->from($this->getTableName())
45
-			->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
46
-			->orderBy('timestamp', 'DESC')
47
-			->setMaxResults(1);
48
-
49
-		return $this->findEntity($qb);
50
-	}
51
-
52
-	public function findVersionForFileId(int $fileId, int $timestamp): VersionEntity {
53
-		$qb = $this->db->getQueryBuilder();
54
-
55
-		$qb->select('*')
56
-			->from($this->getTableName())
57
-			->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
58
-			->andWhere($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp)));
59
-
60
-		return $this->findEntity($qb);
61
-	}
62
-
63
-	public function deleteAllVersionsForFileId(int $fileId): int {
64
-		$qb = $this->db->getQueryBuilder();
65
-
66
-		return $qb->delete($this->getTableName())
67
-			->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
68
-			->executeStatement();
69
-	}
70
-
71
-	public function deleteAllVersionsForUser(int $storageId, ?string $path = null): void {
72
-		$fileIdsGenerator = $this->getFileIdsGenerator($storageId, $path);
73
-
74
-		$versionEntitiesDeleteQuery = $this->db->getQueryBuilder();
75
-		$versionEntitiesDeleteQuery->delete($this->getTableName())
76
-			->where($versionEntitiesDeleteQuery->expr()->in('file_id', $versionEntitiesDeleteQuery->createParameter('file_ids')));
77
-
78
-		foreach ($fileIdsGenerator as $fileIds) {
79
-			$versionEntitiesDeleteQuery->setParameter('file_ids', $fileIds, IQueryBuilder::PARAM_INT_ARRAY);
80
-			$versionEntitiesDeleteQuery->executeStatement();
81
-		}
82
-	}
83
-
84
-	private function getFileIdsGenerator(int $storageId, ?string $path): \Generator {
85
-		$offset = 0;
86
-		do {
87
-			$filesIdsSelect = $this->db->getQueryBuilder();
88
-			$filesIdsSelect->select('fileid')
89
-				->from('filecache')
90
-				->where($filesIdsSelect->expr()->eq('storage', $filesIdsSelect->createNamedParameter($storageId, IQueryBuilder::PARAM_STR)))
91
-				->andWhere($filesIdsSelect->expr()->like('path', $filesIdsSelect->createNamedParameter('files' . ($path ? '/' . $this->db->escapeLikeParameter($path) : '') . '/%', IQueryBuilder::PARAM_STR)))
92
-				->andWhere($filesIdsSelect->expr()->gt('fileid', $filesIdsSelect->createParameter('offset')))
93
-				->setMaxResults(1000)
94
-				->orderBy('fileid', 'ASC');
95
-
96
-			$filesIdsSelect->setParameter('offset', $offset, IQueryBuilder::PARAM_INT);
97
-			$result = $filesIdsSelect->executeQuery();
98
-			$fileIds = $result->fetchFirstColumn();
99
-			$offset = end($fileIds);
100
-
101
-			yield $fileIds;
102
-		} while (!empty($fileIds));
103
-	}
20
+    public function __construct(IDBConnection $db) {
21
+        parent::__construct($db, 'files_versions', VersionEntity::class);
22
+    }
23
+
24
+    /**
25
+     * @return VersionEntity[]
26
+     */
27
+    public function findAllVersionsForFileId(int $fileId): array {
28
+        $qb = $this->db->getQueryBuilder();
29
+
30
+        $qb->select('*')
31
+            ->from($this->getTableName())
32
+            ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)));
33
+
34
+        return $this->findEntities($qb);
35
+    }
36
+
37
+    /**
38
+     * @return VersionEntity
39
+     */
40
+    public function findCurrentVersionForFileId(int $fileId): VersionEntity {
41
+        $qb = $this->db->getQueryBuilder();
42
+
43
+        $qb->select('*')
44
+            ->from($this->getTableName())
45
+            ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
46
+            ->orderBy('timestamp', 'DESC')
47
+            ->setMaxResults(1);
48
+
49
+        return $this->findEntity($qb);
50
+    }
51
+
52
+    public function findVersionForFileId(int $fileId, int $timestamp): VersionEntity {
53
+        $qb = $this->db->getQueryBuilder();
54
+
55
+        $qb->select('*')
56
+            ->from($this->getTableName())
57
+            ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
58
+            ->andWhere($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp)));
59
+
60
+        return $this->findEntity($qb);
61
+    }
62
+
63
+    public function deleteAllVersionsForFileId(int $fileId): int {
64
+        $qb = $this->db->getQueryBuilder();
65
+
66
+        return $qb->delete($this->getTableName())
67
+            ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId)))
68
+            ->executeStatement();
69
+    }
70
+
71
+    public function deleteAllVersionsForUser(int $storageId, ?string $path = null): void {
72
+        $fileIdsGenerator = $this->getFileIdsGenerator($storageId, $path);
73
+
74
+        $versionEntitiesDeleteQuery = $this->db->getQueryBuilder();
75
+        $versionEntitiesDeleteQuery->delete($this->getTableName())
76
+            ->where($versionEntitiesDeleteQuery->expr()->in('file_id', $versionEntitiesDeleteQuery->createParameter('file_ids')));
77
+
78
+        foreach ($fileIdsGenerator as $fileIds) {
79
+            $versionEntitiesDeleteQuery->setParameter('file_ids', $fileIds, IQueryBuilder::PARAM_INT_ARRAY);
80
+            $versionEntitiesDeleteQuery->executeStatement();
81
+        }
82
+    }
83
+
84
+    private function getFileIdsGenerator(int $storageId, ?string $path): \Generator {
85
+        $offset = 0;
86
+        do {
87
+            $filesIdsSelect = $this->db->getQueryBuilder();
88
+            $filesIdsSelect->select('fileid')
89
+                ->from('filecache')
90
+                ->where($filesIdsSelect->expr()->eq('storage', $filesIdsSelect->createNamedParameter($storageId, IQueryBuilder::PARAM_STR)))
91
+                ->andWhere($filesIdsSelect->expr()->like('path', $filesIdsSelect->createNamedParameter('files' . ($path ? '/' . $this->db->escapeLikeParameter($path) : '') . '/%', IQueryBuilder::PARAM_STR)))
92
+                ->andWhere($filesIdsSelect->expr()->gt('fileid', $filesIdsSelect->createParameter('offset')))
93
+                ->setMaxResults(1000)
94
+                ->orderBy('fileid', 'ASC');
95
+
96
+            $filesIdsSelect->setParameter('offset', $offset, IQueryBuilder::PARAM_INT);
97
+            $result = $filesIdsSelect->executeQuery();
98
+            $fileIds = $result->fetchFirstColumn();
99
+            $offset = end($fileIds);
100
+
101
+            yield $fileIds;
102
+        } while (!empty($fileIds));
103
+    }
104 104
 }
Please login to merge, or discard this patch.
apps/oauth2/lib/Migration/Version011601Date20230522143227.php 1 patch
Indentation   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -18,48 +18,48 @@
 block discarded – undo
18 18
 
19 19
 class Version011601Date20230522143227 extends SimpleMigrationStep {
20 20
 
21
-	public function __construct(
22
-		private IDBConnection $connection,
23
-		private ICrypto $crypto,
24
-	) {
25
-	}
21
+    public function __construct(
22
+        private IDBConnection $connection,
23
+        private ICrypto $crypto,
24
+    ) {
25
+    }
26 26
 
27
-	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
28
-		/** @var ISchemaWrapper $schema */
29
-		$schema = $schemaClosure();
27
+    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
28
+        /** @var ISchemaWrapper $schema */
29
+        $schema = $schemaClosure();
30 30
 
31
-		if ($schema->hasTable('oauth2_clients')) {
32
-			$table = $schema->getTable('oauth2_clients');
33
-			if ($table->hasColumn('secret')) {
34
-				$column = $table->getColumn('secret');
35
-				$column->setLength(512);
36
-				return $schema;
37
-			}
38
-		}
31
+        if ($schema->hasTable('oauth2_clients')) {
32
+            $table = $schema->getTable('oauth2_clients');
33
+            if ($table->hasColumn('secret')) {
34
+                $column = $table->getColumn('secret');
35
+                $column->setLength(512);
36
+                return $schema;
37
+            }
38
+        }
39 39
 
40
-		return null;
41
-	}
40
+        return null;
41
+    }
42 42
 
43
-	public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
44
-		$qbUpdate = $this->connection->getQueryBuilder();
45
-		$qbUpdate->update('oauth2_clients')
46
-			->set('secret', $qbUpdate->createParameter('updateSecret'))
47
-			->where(
48
-				$qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
49
-			);
43
+    public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
44
+        $qbUpdate = $this->connection->getQueryBuilder();
45
+        $qbUpdate->update('oauth2_clients')
46
+            ->set('secret', $qbUpdate->createParameter('updateSecret'))
47
+            ->where(
48
+                $qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
49
+            );
50 50
 
51
-		$qbSelect = $this->connection->getQueryBuilder();
52
-		$qbSelect->select('id', 'secret')
53
-			->from('oauth2_clients');
54
-		$req = $qbSelect->executeQuery();
55
-		while ($row = $req->fetchAssociative()) {
56
-			$id = $row['id'];
57
-			$secret = $row['secret'];
58
-			$encryptedSecret = $this->crypto->encrypt($secret);
59
-			$qbUpdate->setParameter('updateSecret', $encryptedSecret, IQueryBuilder::PARAM_STR);
60
-			$qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
61
-			$qbUpdate->executeStatement();
62
-		}
63
-		$req->closeCursor();
64
-	}
51
+        $qbSelect = $this->connection->getQueryBuilder();
52
+        $qbSelect->select('id', 'secret')
53
+            ->from('oauth2_clients');
54
+        $req = $qbSelect->executeQuery();
55
+        while ($row = $req->fetchAssociative()) {
56
+            $id = $row['id'];
57
+            $secret = $row['secret'];
58
+            $encryptedSecret = $this->crypto->encrypt($secret);
59
+            $qbUpdate->setParameter('updateSecret', $encryptedSecret, IQueryBuilder::PARAM_STR);
60
+            $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
61
+            $qbUpdate->executeStatement();
62
+        }
63
+        $req->closeCursor();
64
+    }
65 65
 }
Please login to merge, or discard this patch.
apps/oauth2/lib/Migration/SetTokenExpiration.php 1 patch
Indentation   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -18,34 +18,34 @@
 block discarded – undo
18 18
 
19 19
 class SetTokenExpiration implements IRepairStep {
20 20
 
21
-	public function __construct(
22
-		private IDBConnection $connection,
23
-		private ITimeFactory $time,
24
-		private TokenProvider $tokenProvider,
25
-	) {
26
-	}
27
-
28
-	public function getName(): string {
29
-		return 'Update OAuth token expiration times';
30
-	}
31
-
32
-	public function run(IOutput $output) {
33
-		$qb = $this->connection->getQueryBuilder();
34
-		$qb->select('*')
35
-			->from('oauth2_access_tokens');
36
-
37
-		$cursor = $qb->executeQuery();
38
-
39
-		while ($row = $cursor->fetchAssociative()) {
40
-			$token = AccessToken::fromRow($row);
41
-			try {
42
-				$appToken = $this->tokenProvider->getTokenById($token->getTokenId());
43
-				$appToken->setExpires($this->time->getTime() + 3600);
44
-				$this->tokenProvider->updateToken($appToken);
45
-			} catch (InvalidTokenException $e) {
46
-				//Skip this token
47
-			}
48
-		}
49
-		$cursor->closeCursor();
50
-	}
21
+    public function __construct(
22
+        private IDBConnection $connection,
23
+        private ITimeFactory $time,
24
+        private TokenProvider $tokenProvider,
25
+    ) {
26
+    }
27
+
28
+    public function getName(): string {
29
+        return 'Update OAuth token expiration times';
30
+    }
31
+
32
+    public function run(IOutput $output) {
33
+        $qb = $this->connection->getQueryBuilder();
34
+        $qb->select('*')
35
+            ->from('oauth2_access_tokens');
36
+
37
+        $cursor = $qb->executeQuery();
38
+
39
+        while ($row = $cursor->fetchAssociative()) {
40
+            $token = AccessToken::fromRow($row);
41
+            try {
42
+                $appToken = $this->tokenProvider->getTokenById($token->getTokenId());
43
+                $appToken->setExpires($this->time->getTime() + 3600);
44
+                $this->tokenProvider->updateToken($appToken);
45
+            } catch (InvalidTokenException $e) {
46
+                //Skip this token
47
+            }
48
+        }
49
+        $cursor->closeCursor();
50
+    }
51 51
 }
Please login to merge, or discard this patch.
apps/oauth2/lib/Migration/Version011901Date20240829164356.php 1 patch
Indentation   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -17,33 +17,33 @@
 block discarded – undo
17 17
 
18 18
 class Version011901Date20240829164356 extends SimpleMigrationStep {
19 19
 
20
-	public function __construct(
21
-		private IDBConnection $connection,
22
-		private ICrypto $crypto,
23
-	) {
24
-	}
20
+    public function __construct(
21
+        private IDBConnection $connection,
22
+        private ICrypto $crypto,
23
+    ) {
24
+    }
25 25
 
26
-	public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
27
-		$qbUpdate = $this->connection->getQueryBuilder();
28
-		$qbUpdate->update('oauth2_clients')
29
-			->set('secret', $qbUpdate->createParameter('updateSecret'))
30
-			->where(
31
-				$qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
32
-			);
26
+    public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
27
+        $qbUpdate = $this->connection->getQueryBuilder();
28
+        $qbUpdate->update('oauth2_clients')
29
+            ->set('secret', $qbUpdate->createParameter('updateSecret'))
30
+            ->where(
31
+                $qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
32
+            );
33 33
 
34
-		$qbSelect = $this->connection->getQueryBuilder();
35
-		$qbSelect->select('id', 'secret')
36
-			->from('oauth2_clients');
37
-		$req = $qbSelect->executeQuery();
38
-		while ($row = $req->fetchAssociative()) {
39
-			$id = $row['id'];
40
-			$storedEncryptedSecret = $row['secret'];
41
-			$secret = $this->crypto->decrypt($storedEncryptedSecret);
42
-			$hashedSecret = bin2hex($this->crypto->calculateHMAC($secret));
43
-			$qbUpdate->setParameter('updateSecret', $hashedSecret, IQueryBuilder::PARAM_STR);
44
-			$qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
45
-			$qbUpdate->executeStatement();
46
-		}
47
-		$req->closeCursor();
48
-	}
34
+        $qbSelect = $this->connection->getQueryBuilder();
35
+        $qbSelect->select('id', 'secret')
36
+            ->from('oauth2_clients');
37
+        $req = $qbSelect->executeQuery();
38
+        while ($row = $req->fetchAssociative()) {
39
+            $id = $row['id'];
40
+            $storedEncryptedSecret = $row['secret'];
41
+            $secret = $this->crypto->decrypt($storedEncryptedSecret);
42
+            $hashedSecret = bin2hex($this->crypto->calculateHMAC($secret));
43
+            $qbUpdate->setParameter('updateSecret', $hashedSecret, IQueryBuilder::PARAM_STR);
44
+            $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
45
+            $qbUpdate->executeStatement();
46
+        }
47
+        $req->closeCursor();
48
+    }
49 49
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Service/DBConfigService.php 1 patch
Indentation   +504 added lines, -504 removed lines patch added patch discarded remove patch
@@ -17,508 +17,508 @@
 block discarded – undo
17 17
  * Stores the mount config in the database
18 18
  */
19 19
 class DBConfigService {
20
-	public const MOUNT_TYPE_ADMIN = 1;
21
-	public const MOUNT_TYPE_PERSONAL = 2;
22
-	/** @deprecated use MOUNT_TYPE_PERSONAL (full uppercase) instead */
23
-	public const MOUNT_TYPE_PERSONAl = 2;
24
-
25
-	public const APPLICABLE_TYPE_GLOBAL = 1;
26
-	public const APPLICABLE_TYPE_GROUP = 2;
27
-	public const APPLICABLE_TYPE_USER = 3;
28
-
29
-	public function __construct(
30
-		private IDBConnection $connection,
31
-		private ICrypto $crypto,
32
-	) {
33
-	}
34
-
35
-	public function getMountById(int $mountId): ?array {
36
-		$builder = $this->connection->getQueryBuilder();
37
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
38
-			->from('external_mounts', 'm')
39
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
40
-		$mounts = $this->getMountsFromQuery($query);
41
-		if (count($mounts) > 0) {
42
-			return $mounts[0];
43
-		} else {
44
-			return null;
45
-		}
46
-	}
47
-
48
-	/**
49
-	 * Get all configured mounts
50
-	 *
51
-	 * @return array
52
-	 */
53
-	public function getAllMounts() {
54
-		$builder = $this->connection->getQueryBuilder();
55
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
56
-			->from('external_mounts');
57
-		return $this->getMountsFromQuery($query);
58
-	}
59
-
60
-	public function getMountsForUser($userId, $groupIds) {
61
-		$builder = $this->connection->getQueryBuilder();
62
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
63
-			->from('external_mounts', 'm')
64
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
65
-			->where($builder->expr()->orX(
66
-				$builder->expr()->andX( // global mounts
67
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
68
-					$builder->expr()->isNull('a.value'),
69
-				),
70
-				$builder->expr()->andX( // mounts for user
71
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
72
-					$builder->expr()->eq('a.value', $builder->createNamedParameter($userId)),
73
-				),
74
-				$builder->expr()->andX( // mounts for group
75
-					$builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
76
-					$builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)),
77
-				),
78
-			));
79
-
80
-		return $this->getMountsFromQuery($query);
81
-	}
82
-
83
-	public function modifyMountsOnUserDelete(string $uid): void {
84
-		$this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
85
-	}
86
-
87
-	public function modifyMountsOnGroupDelete(string $gid): void {
88
-		$this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
89
-	}
90
-
91
-	protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
92
-		$builder = $this->connection->getQueryBuilder();
93
-		$query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
94
-			->from('external_applicable', 'a')
95
-			->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
96
-			->where($builder->expr()->andX(
97
-				$builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
98
-				$builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId)),
99
-			),
100
-			)
101
-			->groupBy(['a.mount_id']);
102
-		$stmt = $query->executeQuery();
103
-		$result = $stmt->fetchAllAssociative();
104
-		$stmt->closeCursor();
105
-
106
-		foreach ($result as $row) {
107
-			if ((int)$row['count'] > 1) {
108
-				$this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
109
-			} else {
110
-				$this->removeMount($row['mount_id']);
111
-			}
112
-		}
113
-	}
114
-
115
-	/**
116
-	 * Get admin defined mounts
117
-	 *
118
-	 * @return array
119
-	 */
120
-	public function getAdminMounts() {
121
-		$builder = $this->connection->getQueryBuilder();
122
-		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
123
-			->from('external_mounts')
124
-			->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
125
-		return $this->getMountsFromQuery($query);
126
-	}
127
-
128
-	protected function getForQuery(IQueryBuilder $builder, $type, $value) {
129
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
130
-			->from('external_mounts', 'm')
131
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
132
-			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
133
-
134
-		if (is_null($value)) {
135
-			$query = $query->andWhere($builder->expr()->isNull('a.value'));
136
-		} else {
137
-			$query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
138
-		}
139
-
140
-		return $query;
141
-	}
142
-
143
-	/**
144
-	 * Get mounts by applicable
145
-	 *
146
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
147
-	 * @param string|null $value user_id, group_id or null for global mounts
148
-	 * @return array
149
-	 */
150
-	public function getMountsFor($type, $value) {
151
-		$builder = $this->connection->getQueryBuilder();
152
-		$query = $this->getForQuery($builder, $type, $value);
153
-
154
-		return $this->getMountsFromQuery($query);
155
-	}
156
-
157
-	/**
158
-	 * Get admin defined mounts by applicable
159
-	 *
160
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
161
-	 * @param string|null $value user_id, group_id or null for global mounts
162
-	 * @return array
163
-	 */
164
-	public function getAdminMountsFor($type, $value) {
165
-		$builder = $this->connection->getQueryBuilder();
166
-		$query = $this->getForQuery($builder, $type, $value);
167
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
168
-
169
-		return $this->getMountsFromQuery($query);
170
-	}
171
-
172
-	/**
173
-	 * Get admin defined mounts for multiple applicable
174
-	 *
175
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
176
-	 * @param string[] $values user_ids or group_ids
177
-	 * @return array
178
-	 */
179
-	public function getAdminMountsForMultiple($type, array $values) {
180
-		$builder = $this->connection->getQueryBuilder();
181
-		$params = array_map(function ($value) use ($builder) {
182
-			return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
183
-		}, $values);
184
-
185
-		$query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
186
-			->from('external_mounts', 'm')
187
-			->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
188
-			->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
189
-			->andWhere($builder->expr()->in('a.value', $params));
190
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
191
-
192
-		return $this->getMountsFromQuery($query);
193
-	}
194
-
195
-	/**
196
-	 * Get user defined mounts by applicable
197
-	 *
198
-	 * @param int $type any of the self::APPLICABLE_TYPE_ constants
199
-	 * @param string|null $value user_id, group_id or null for global mounts
200
-	 * @return array
201
-	 */
202
-	public function getUserMountsFor($type, $value) {
203
-		$builder = $this->connection->getQueryBuilder();
204
-		$query = $this->getForQuery($builder, $type, $value);
205
-		$query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAL, IQueryBuilder::PARAM_INT)));
206
-
207
-		return $this->getMountsFromQuery($query);
208
-	}
209
-
210
-	/**
211
-	 * Add a mount to the database
212
-	 *
213
-	 * @param string $mountPoint
214
-	 * @param string $storageBackend
215
-	 * @param string $authBackend
216
-	 * @param int $priority
217
-	 * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
218
-	 * @return int the id of the new mount
219
-	 */
220
-	public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
221
-		if (!$priority) {
222
-			$priority = 100;
223
-		}
224
-		$builder = $this->connection->getQueryBuilder();
225
-		$query = $builder->insert('external_mounts')
226
-			->values([
227
-				'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
228
-				'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
229
-				'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
230
-				'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
231
-				'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT),
232
-			]);
233
-		$query->executeStatement();
234
-		return $query->getLastInsertId();
235
-	}
236
-
237
-	/**
238
-	 * Remove a mount from the database
239
-	 *
240
-	 * @param int $mountId
241
-	 */
242
-	public function removeMount($mountId) {
243
-		$builder = $this->connection->getQueryBuilder();
244
-		$query = $builder->delete('external_mounts')
245
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
246
-		$query->executeStatement();
247
-
248
-		$builder = $this->connection->getQueryBuilder();
249
-		$query = $builder->delete('external_applicable')
250
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
251
-		$query->executeStatement();
252
-
253
-		$builder = $this->connection->getQueryBuilder();
254
-		$query = $builder->delete('external_config')
255
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
256
-		$query->executeStatement();
257
-
258
-		$builder = $this->connection->getQueryBuilder();
259
-		$query = $builder->delete('external_options')
260
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
261
-		$query->executeStatement();
262
-	}
263
-
264
-	/**
265
-	 * @param int $mountId
266
-	 * @param string $newMountPoint
267
-	 */
268
-	public function setMountPoint($mountId, $newMountPoint) {
269
-		$builder = $this->connection->getQueryBuilder();
270
-
271
-		$query = $builder->update('external_mounts')
272
-			->set('mount_point', $builder->createNamedParameter($newMountPoint))
273
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
274
-
275
-		$query->executeStatement();
276
-	}
277
-
278
-	/**
279
-	 * @param int $mountId
280
-	 * @param string $newAuthBackend
281
-	 */
282
-	public function setAuthBackend($mountId, $newAuthBackend) {
283
-		$builder = $this->connection->getQueryBuilder();
284
-
285
-		$query = $builder->update('external_mounts')
286
-			->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
287
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
288
-
289
-		$query->executeStatement();
290
-	}
291
-
292
-	/**
293
-	 * @param int $mountId
294
-	 * @param string $key
295
-	 * @param string $value
296
-	 */
297
-	public function setConfig($mountId, $key, $value) {
298
-		if ($key === 'password') {
299
-			$value = $this->encryptValue($value);
300
-		}
301
-
302
-		try {
303
-			$builder = $this->connection->getQueryBuilder();
304
-			$builder->insert('external_config')
305
-				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
306
-				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
307
-				->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
308
-				->executeStatement();
309
-		} catch (Exception $e) {
310
-			if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
311
-				throw $e;
312
-			}
313
-			$builder = $this->connection->getQueryBuilder();
314
-			$query = $builder->update('external_config')
315
-				->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
316
-				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
317
-				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
318
-			$query->executeStatement();
319
-		}
320
-	}
321
-
322
-	/**
323
-	 * @param int $mountId
324
-	 * @param string $key
325
-	 * @param string $value
326
-	 */
327
-	public function setOption($mountId, $key, $value) {
328
-		try {
329
-			$builder = $this->connection->getQueryBuilder();
330
-			$builder->insert('external_options')
331
-				->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
332
-				->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
333
-				->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
334
-				->executeStatement();
335
-		} catch (Exception $e) {
336
-			if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
337
-				throw $e;
338
-			}
339
-			$builder = $this->connection->getQueryBuilder();
340
-			$query = $builder->update('external_options')
341
-				->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
342
-				->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
343
-				->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
344
-			$query->executeStatement();
345
-		}
346
-	}
347
-
348
-	public function addApplicable($mountId, $type, $value) {
349
-		try {
350
-			$builder = $this->connection->getQueryBuilder();
351
-			$builder->insert('external_applicable')
352
-				->setValue('mount_id', $builder->createNamedParameter($mountId))
353
-				->setValue('type', $builder->createNamedParameter($type))
354
-				->setValue('value', $builder->createNamedParameter($value))
355
-				->executeStatement();
356
-		} catch (Exception $e) {
357
-			// applicable exists already
358
-			if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
359
-				throw $e;
360
-			}
361
-		}
362
-	}
363
-
364
-	public function removeApplicable($mountId, $type, $value) {
365
-		$builder = $this->connection->getQueryBuilder();
366
-		$query = $builder->delete('external_applicable')
367
-			->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
368
-			->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
369
-
370
-		if (is_null($value)) {
371
-			$query = $query->andWhere($builder->expr()->isNull('value'));
372
-		} else {
373
-			$query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
374
-		}
375
-
376
-		$query->executeStatement();
377
-	}
378
-
379
-	private function getMountsFromQuery(IQueryBuilder $query) {
380
-		$result = $query->executeQuery();
381
-		$mounts = $result->fetchAllAssociative();
382
-		$uniqueMounts = [];
383
-		foreach ($mounts as $mount) {
384
-			$id = $mount['mount_id'];
385
-			if (!isset($uniqueMounts[$id])) {
386
-				$uniqueMounts[$id] = $mount;
387
-			}
388
-		}
389
-		$uniqueMounts = array_values($uniqueMounts);
390
-
391
-		$mountIds = array_map(function ($mount) {
392
-			return $mount['mount_id'];
393
-		}, $uniqueMounts);
394
-		$mountIds = array_values(array_unique($mountIds));
395
-
396
-		$applicable = $this->getApplicableForMounts($mountIds);
397
-		$config = $this->getConfigForMounts($mountIds);
398
-		$options = $this->getOptionsForMounts($mountIds);
399
-
400
-		return array_map(function ($mount, $applicable, $config, $options) {
401
-			$mount['type'] = (int)$mount['type'];
402
-			$mount['priority'] = (int)$mount['priority'];
403
-			$mount['applicable'] = $applicable;
404
-			$mount['config'] = $config;
405
-			$mount['options'] = $options;
406
-			return $mount;
407
-		}, $uniqueMounts, $applicable, $config, $options);
408
-	}
409
-
410
-	/**
411
-	 * Get mount options from a table grouped by mount id
412
-	 *
413
-	 * @param string $table
414
-	 * @param string[] $fields
415
-	 * @param int[] $mountIds
416
-	 * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
417
-	 */
418
-	private function selectForMounts($table, array $fields, array $mountIds) {
419
-		if (count($mountIds) === 0) {
420
-			return [];
421
-		}
422
-		$builder = $this->connection->getQueryBuilder();
423
-		$fields[] = 'mount_id';
424
-		$placeHolders = array_map(function ($id) use ($builder) {
425
-			return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
426
-		}, $mountIds);
427
-		$query = $builder->select($fields)
428
-			->from($table)
429
-			->where($builder->expr()->in('mount_id', $placeHolders));
430
-
431
-		$result = $query->executeQuery();
432
-		$rows = $result->fetchAllAssociative();
433
-		$result->closeCursor();
434
-
435
-		$result = [];
436
-		foreach ($mountIds as $mountId) {
437
-			$result[$mountId] = [];
438
-		}
439
-		foreach ($rows as $row) {
440
-			if (isset($row['type'])) {
441
-				$row['type'] = (int)$row['type'];
442
-			}
443
-			$result[$row['mount_id']][] = $row;
444
-		}
445
-		return $result;
446
-	}
447
-
448
-	/**
449
-	 * @param int[] $mountIds
450
-	 * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
451
-	 */
452
-	public function getApplicableForMounts($mountIds) {
453
-		return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
454
-	}
455
-
456
-	/**
457
-	 * @param int[] $mountIds
458
-	 * @return array [$id => ['key1' => $value1, ...], ...]
459
-	 */
460
-	public function getConfigForMounts($mountIds) {
461
-		$mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
462
-		return array_map([$this, 'createKeyValueMap'], $mountConfigs);
463
-	}
464
-
465
-	/**
466
-	 * @param int[] $mountIds
467
-	 * @return array [$id => ['key1' => $value1, ...], ...]
468
-	 */
469
-	public function getOptionsForMounts($mountIds) {
470
-		$mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
471
-		$optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
472
-		return array_map(function (array $options) {
473
-			return array_map(function ($option) {
474
-				return json_decode($option);
475
-			}, $options);
476
-		}, $optionsMap);
477
-	}
478
-
479
-	/**
480
-	 * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
481
-	 * @return array ['key1' => $value1, ...]
482
-	 */
483
-	private function createKeyValueMap(array $keyValuePairs) {
484
-		$decryptedPairts = array_map(function ($pair) {
485
-			if ($pair['key'] === 'password') {
486
-				$pair['value'] = $this->decryptValue($pair['value']);
487
-			}
488
-			return $pair;
489
-		}, $keyValuePairs);
490
-		$keys = array_map(function ($pair) {
491
-			return $pair['key'];
492
-		}, $decryptedPairts);
493
-		$values = array_map(function ($pair) {
494
-			return $pair['value'];
495
-		}, $decryptedPairts);
496
-
497
-		return array_combine($keys, $values);
498
-	}
499
-
500
-	private function encryptValue($value) {
501
-		return $this->crypto->encrypt($value);
502
-	}
503
-
504
-	private function decryptValue($value) {
505
-		try {
506
-			return $this->crypto->decrypt($value);
507
-		} catch (\Exception $e) {
508
-			return $value;
509
-		}
510
-	}
511
-
512
-	/**
513
-	 * Check if any mountpoint is configured that overwrite the home folder
514
-	 */
515
-	public function hasHomeFolderOverwriteMount(): bool {
516
-		$builder = $this->connection->getQueryBuilder();
517
-		$query = $builder->select('mount_id')
518
-			->from('external_mounts')
519
-			->where($builder->expr()->eq('mount_point', $builder->createNamedParameter('/')))
520
-			->setMaxResults(1);
521
-		$result = $query->executeQuery();
522
-		return count($result->fetchAllAssociative()) > 0;
523
-	}
20
+    public const MOUNT_TYPE_ADMIN = 1;
21
+    public const MOUNT_TYPE_PERSONAL = 2;
22
+    /** @deprecated use MOUNT_TYPE_PERSONAL (full uppercase) instead */
23
+    public const MOUNT_TYPE_PERSONAl = 2;
24
+
25
+    public const APPLICABLE_TYPE_GLOBAL = 1;
26
+    public const APPLICABLE_TYPE_GROUP = 2;
27
+    public const APPLICABLE_TYPE_USER = 3;
28
+
29
+    public function __construct(
30
+        private IDBConnection $connection,
31
+        private ICrypto $crypto,
32
+    ) {
33
+    }
34
+
35
+    public function getMountById(int $mountId): ?array {
36
+        $builder = $this->connection->getQueryBuilder();
37
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
38
+            ->from('external_mounts', 'm')
39
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
40
+        $mounts = $this->getMountsFromQuery($query);
41
+        if (count($mounts) > 0) {
42
+            return $mounts[0];
43
+        } else {
44
+            return null;
45
+        }
46
+    }
47
+
48
+    /**
49
+     * Get all configured mounts
50
+     *
51
+     * @return array
52
+     */
53
+    public function getAllMounts() {
54
+        $builder = $this->connection->getQueryBuilder();
55
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
56
+            ->from('external_mounts');
57
+        return $this->getMountsFromQuery($query);
58
+    }
59
+
60
+    public function getMountsForUser($userId, $groupIds) {
61
+        $builder = $this->connection->getQueryBuilder();
62
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
63
+            ->from('external_mounts', 'm')
64
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
65
+            ->where($builder->expr()->orX(
66
+                $builder->expr()->andX( // global mounts
67
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)),
68
+                    $builder->expr()->isNull('a.value'),
69
+                ),
70
+                $builder->expr()->andX( // mounts for user
71
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)),
72
+                    $builder->expr()->eq('a.value', $builder->createNamedParameter($userId)),
73
+                ),
74
+                $builder->expr()->andX( // mounts for group
75
+                    $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
76
+                    $builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)),
77
+                ),
78
+            ));
79
+
80
+        return $this->getMountsFromQuery($query);
81
+    }
82
+
83
+    public function modifyMountsOnUserDelete(string $uid): void {
84
+        $this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER);
85
+    }
86
+
87
+    public function modifyMountsOnGroupDelete(string $gid): void {
88
+        $this->modifyMountsOnDelete($gid, self::APPLICABLE_TYPE_GROUP);
89
+    }
90
+
91
+    protected function modifyMountsOnDelete(string $applicableId, int $applicableType): void {
92
+        $builder = $this->connection->getQueryBuilder();
93
+        $query = $builder->select(['a.mount_id', $builder->func()->count('a.mount_id', 'count')])
94
+            ->from('external_applicable', 'a')
95
+            ->leftJoin('a', 'external_applicable', 'b', $builder->expr()->eq('a.mount_id', 'b.mount_id'))
96
+            ->where($builder->expr()->andX(
97
+                $builder->expr()->eq('b.type', $builder->createNamedParameter($applicableType, IQueryBuilder::PARAM_INT)),
98
+                $builder->expr()->eq('b.value', $builder->createNamedParameter($applicableId)),
99
+            ),
100
+            )
101
+            ->groupBy(['a.mount_id']);
102
+        $stmt = $query->executeQuery();
103
+        $result = $stmt->fetchAllAssociative();
104
+        $stmt->closeCursor();
105
+
106
+        foreach ($result as $row) {
107
+            if ((int)$row['count'] > 1) {
108
+                $this->removeApplicable($row['mount_id'], $applicableType, $applicableId);
109
+            } else {
110
+                $this->removeMount($row['mount_id']);
111
+            }
112
+        }
113
+    }
114
+
115
+    /**
116
+     * Get admin defined mounts
117
+     *
118
+     * @return array
119
+     */
120
+    public function getAdminMounts() {
121
+        $builder = $this->connection->getQueryBuilder();
122
+        $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
123
+            ->from('external_mounts')
124
+            ->where($builder->expr()->eq('type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
125
+        return $this->getMountsFromQuery($query);
126
+    }
127
+
128
+    protected function getForQuery(IQueryBuilder $builder, $type, $value) {
129
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
130
+            ->from('external_mounts', 'm')
131
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
132
+            ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
133
+
134
+        if (is_null($value)) {
135
+            $query = $query->andWhere($builder->expr()->isNull('a.value'));
136
+        } else {
137
+            $query = $query->andWhere($builder->expr()->eq('a.value', $builder->createNamedParameter($value)));
138
+        }
139
+
140
+        return $query;
141
+    }
142
+
143
+    /**
144
+     * Get mounts by applicable
145
+     *
146
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
147
+     * @param string|null $value user_id, group_id or null for global mounts
148
+     * @return array
149
+     */
150
+    public function getMountsFor($type, $value) {
151
+        $builder = $this->connection->getQueryBuilder();
152
+        $query = $this->getForQuery($builder, $type, $value);
153
+
154
+        return $this->getMountsFromQuery($query);
155
+    }
156
+
157
+    /**
158
+     * Get admin defined mounts by applicable
159
+     *
160
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
161
+     * @param string|null $value user_id, group_id or null for global mounts
162
+     * @return array
163
+     */
164
+    public function getAdminMountsFor($type, $value) {
165
+        $builder = $this->connection->getQueryBuilder();
166
+        $query = $this->getForQuery($builder, $type, $value);
167
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
168
+
169
+        return $this->getMountsFromQuery($query);
170
+    }
171
+
172
+    /**
173
+     * Get admin defined mounts for multiple applicable
174
+     *
175
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
176
+     * @param string[] $values user_ids or group_ids
177
+     * @return array
178
+     */
179
+    public function getAdminMountsForMultiple($type, array $values) {
180
+        $builder = $this->connection->getQueryBuilder();
181
+        $params = array_map(function ($value) use ($builder) {
182
+            return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR);
183
+        }, $values);
184
+
185
+        $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type'])
186
+            ->from('external_mounts', 'm')
187
+            ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id'))
188
+            ->where($builder->expr()->eq('a.type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
189
+            ->andWhere($builder->expr()->in('a.value', $params));
190
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT)));
191
+
192
+        return $this->getMountsFromQuery($query);
193
+    }
194
+
195
+    /**
196
+     * Get user defined mounts by applicable
197
+     *
198
+     * @param int $type any of the self::APPLICABLE_TYPE_ constants
199
+     * @param string|null $value user_id, group_id or null for global mounts
200
+     * @return array
201
+     */
202
+    public function getUserMountsFor($type, $value) {
203
+        $builder = $this->connection->getQueryBuilder();
204
+        $query = $this->getForQuery($builder, $type, $value);
205
+        $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAL, IQueryBuilder::PARAM_INT)));
206
+
207
+        return $this->getMountsFromQuery($query);
208
+    }
209
+
210
+    /**
211
+     * Add a mount to the database
212
+     *
213
+     * @param string $mountPoint
214
+     * @param string $storageBackend
215
+     * @param string $authBackend
216
+     * @param int $priority
217
+     * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL
218
+     * @return int the id of the new mount
219
+     */
220
+    public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) {
221
+        if (!$priority) {
222
+            $priority = 100;
223
+        }
224
+        $builder = $this->connection->getQueryBuilder();
225
+        $query = $builder->insert('external_mounts')
226
+            ->values([
227
+                'mount_point' => $builder->createNamedParameter($mountPoint, IQueryBuilder::PARAM_STR),
228
+                'storage_backend' => $builder->createNamedParameter($storageBackend, IQueryBuilder::PARAM_STR),
229
+                'auth_backend' => $builder->createNamedParameter($authBackend, IQueryBuilder::PARAM_STR),
230
+                'priority' => $builder->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
231
+                'type' => $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT),
232
+            ]);
233
+        $query->executeStatement();
234
+        return $query->getLastInsertId();
235
+    }
236
+
237
+    /**
238
+     * Remove a mount from the database
239
+     *
240
+     * @param int $mountId
241
+     */
242
+    public function removeMount($mountId) {
243
+        $builder = $this->connection->getQueryBuilder();
244
+        $query = $builder->delete('external_mounts')
245
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
246
+        $query->executeStatement();
247
+
248
+        $builder = $this->connection->getQueryBuilder();
249
+        $query = $builder->delete('external_applicable')
250
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
251
+        $query->executeStatement();
252
+
253
+        $builder = $this->connection->getQueryBuilder();
254
+        $query = $builder->delete('external_config')
255
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
256
+        $query->executeStatement();
257
+
258
+        $builder = $this->connection->getQueryBuilder();
259
+        $query = $builder->delete('external_options')
260
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
261
+        $query->executeStatement();
262
+    }
263
+
264
+    /**
265
+     * @param int $mountId
266
+     * @param string $newMountPoint
267
+     */
268
+    public function setMountPoint($mountId, $newMountPoint) {
269
+        $builder = $this->connection->getQueryBuilder();
270
+
271
+        $query = $builder->update('external_mounts')
272
+            ->set('mount_point', $builder->createNamedParameter($newMountPoint))
273
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
274
+
275
+        $query->executeStatement();
276
+    }
277
+
278
+    /**
279
+     * @param int $mountId
280
+     * @param string $newAuthBackend
281
+     */
282
+    public function setAuthBackend($mountId, $newAuthBackend) {
283
+        $builder = $this->connection->getQueryBuilder();
284
+
285
+        $query = $builder->update('external_mounts')
286
+            ->set('auth_backend', $builder->createNamedParameter($newAuthBackend))
287
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
288
+
289
+        $query->executeStatement();
290
+    }
291
+
292
+    /**
293
+     * @param int $mountId
294
+     * @param string $key
295
+     * @param string $value
296
+     */
297
+    public function setConfig($mountId, $key, $value) {
298
+        if ($key === 'password') {
299
+            $value = $this->encryptValue($value);
300
+        }
301
+
302
+        try {
303
+            $builder = $this->connection->getQueryBuilder();
304
+            $builder->insert('external_config')
305
+                ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
306
+                ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
307
+                ->setValue('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
308
+                ->executeStatement();
309
+        } catch (Exception $e) {
310
+            if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
311
+                throw $e;
312
+            }
313
+            $builder = $this->connection->getQueryBuilder();
314
+            $query = $builder->update('external_config')
315
+                ->set('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR))
316
+                ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
317
+                ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
318
+            $query->executeStatement();
319
+        }
320
+    }
321
+
322
+    /**
323
+     * @param int $mountId
324
+     * @param string $key
325
+     * @param string $value
326
+     */
327
+    public function setOption($mountId, $key, $value) {
328
+        try {
329
+            $builder = $this->connection->getQueryBuilder();
330
+            $builder->insert('external_options')
331
+                ->setValue('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))
332
+                ->setValue('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR))
333
+                ->setValue('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
334
+                ->executeStatement();
335
+        } catch (Exception $e) {
336
+            if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
337
+                throw $e;
338
+            }
339
+            $builder = $this->connection->getQueryBuilder();
340
+            $query = $builder->update('external_options')
341
+                ->set('value', $builder->createNamedParameter(json_encode($value), IQueryBuilder::PARAM_STR))
342
+                ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
343
+                ->andWhere($builder->expr()->eq('key', $builder->createNamedParameter($key, IQueryBuilder::PARAM_STR)));
344
+            $query->executeStatement();
345
+        }
346
+    }
347
+
348
+    public function addApplicable($mountId, $type, $value) {
349
+        try {
350
+            $builder = $this->connection->getQueryBuilder();
351
+            $builder->insert('external_applicable')
352
+                ->setValue('mount_id', $builder->createNamedParameter($mountId))
353
+                ->setValue('type', $builder->createNamedParameter($type))
354
+                ->setValue('value', $builder->createNamedParameter($value))
355
+                ->executeStatement();
356
+        } catch (Exception $e) {
357
+            // applicable exists already
358
+            if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
359
+                throw $e;
360
+            }
361
+        }
362
+    }
363
+
364
+    public function removeApplicable($mountId, $type, $value) {
365
+        $builder = $this->connection->getQueryBuilder();
366
+        $query = $builder->delete('external_applicable')
367
+            ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
368
+            ->andWhere($builder->expr()->eq('type', $builder->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
369
+
370
+        if (is_null($value)) {
371
+            $query = $query->andWhere($builder->expr()->isNull('value'));
372
+        } else {
373
+            $query = $query->andWhere($builder->expr()->eq('value', $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR)));
374
+        }
375
+
376
+        $query->executeStatement();
377
+    }
378
+
379
+    private function getMountsFromQuery(IQueryBuilder $query) {
380
+        $result = $query->executeQuery();
381
+        $mounts = $result->fetchAllAssociative();
382
+        $uniqueMounts = [];
383
+        foreach ($mounts as $mount) {
384
+            $id = $mount['mount_id'];
385
+            if (!isset($uniqueMounts[$id])) {
386
+                $uniqueMounts[$id] = $mount;
387
+            }
388
+        }
389
+        $uniqueMounts = array_values($uniqueMounts);
390
+
391
+        $mountIds = array_map(function ($mount) {
392
+            return $mount['mount_id'];
393
+        }, $uniqueMounts);
394
+        $mountIds = array_values(array_unique($mountIds));
395
+
396
+        $applicable = $this->getApplicableForMounts($mountIds);
397
+        $config = $this->getConfigForMounts($mountIds);
398
+        $options = $this->getOptionsForMounts($mountIds);
399
+
400
+        return array_map(function ($mount, $applicable, $config, $options) {
401
+            $mount['type'] = (int)$mount['type'];
402
+            $mount['priority'] = (int)$mount['priority'];
403
+            $mount['applicable'] = $applicable;
404
+            $mount['config'] = $config;
405
+            $mount['options'] = $options;
406
+            return $mount;
407
+        }, $uniqueMounts, $applicable, $config, $options);
408
+    }
409
+
410
+    /**
411
+     * Get mount options from a table grouped by mount id
412
+     *
413
+     * @param string $table
414
+     * @param string[] $fields
415
+     * @param int[] $mountIds
416
+     * @return array [$mountId => [['field1' => $value1, ...], ...], ...]
417
+     */
418
+    private function selectForMounts($table, array $fields, array $mountIds) {
419
+        if (count($mountIds) === 0) {
420
+            return [];
421
+        }
422
+        $builder = $this->connection->getQueryBuilder();
423
+        $fields[] = 'mount_id';
424
+        $placeHolders = array_map(function ($id) use ($builder) {
425
+            return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT);
426
+        }, $mountIds);
427
+        $query = $builder->select($fields)
428
+            ->from($table)
429
+            ->where($builder->expr()->in('mount_id', $placeHolders));
430
+
431
+        $result = $query->executeQuery();
432
+        $rows = $result->fetchAllAssociative();
433
+        $result->closeCursor();
434
+
435
+        $result = [];
436
+        foreach ($mountIds as $mountId) {
437
+            $result[$mountId] = [];
438
+        }
439
+        foreach ($rows as $row) {
440
+            if (isset($row['type'])) {
441
+                $row['type'] = (int)$row['type'];
442
+            }
443
+            $result[$row['mount_id']][] = $row;
444
+        }
445
+        return $result;
446
+    }
447
+
448
+    /**
449
+     * @param int[] $mountIds
450
+     * @return array [$id => [['type' => $type, 'value' => $value], ...], ...]
451
+     */
452
+    public function getApplicableForMounts($mountIds) {
453
+        return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds);
454
+    }
455
+
456
+    /**
457
+     * @param int[] $mountIds
458
+     * @return array [$id => ['key1' => $value1, ...], ...]
459
+     */
460
+    public function getConfigForMounts($mountIds) {
461
+        $mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds);
462
+        return array_map([$this, 'createKeyValueMap'], $mountConfigs);
463
+    }
464
+
465
+    /**
466
+     * @param int[] $mountIds
467
+     * @return array [$id => ['key1' => $value1, ...], ...]
468
+     */
469
+    public function getOptionsForMounts($mountIds) {
470
+        $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds);
471
+        $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions);
472
+        return array_map(function (array $options) {
473
+            return array_map(function ($option) {
474
+                return json_decode($option);
475
+            }, $options);
476
+        }, $optionsMap);
477
+    }
478
+
479
+    /**
480
+     * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...]
481
+     * @return array ['key1' => $value1, ...]
482
+     */
483
+    private function createKeyValueMap(array $keyValuePairs) {
484
+        $decryptedPairts = array_map(function ($pair) {
485
+            if ($pair['key'] === 'password') {
486
+                $pair['value'] = $this->decryptValue($pair['value']);
487
+            }
488
+            return $pair;
489
+        }, $keyValuePairs);
490
+        $keys = array_map(function ($pair) {
491
+            return $pair['key'];
492
+        }, $decryptedPairts);
493
+        $values = array_map(function ($pair) {
494
+            return $pair['value'];
495
+        }, $decryptedPairts);
496
+
497
+        return array_combine($keys, $values);
498
+    }
499
+
500
+    private function encryptValue($value) {
501
+        return $this->crypto->encrypt($value);
502
+    }
503
+
504
+    private function decryptValue($value) {
505
+        try {
506
+            return $this->crypto->decrypt($value);
507
+        } catch (\Exception $e) {
508
+            return $value;
509
+        }
510
+    }
511
+
512
+    /**
513
+     * Check if any mountpoint is configured that overwrite the home folder
514
+     */
515
+    public function hasHomeFolderOverwriteMount(): bool {
516
+        $builder = $this->connection->getQueryBuilder();
517
+        $query = $builder->select('mount_id')
518
+            ->from('external_mounts')
519
+            ->where($builder->expr()->eq('mount_point', $builder->createNamedParameter('/')))
520
+            ->setMaxResults(1);
521
+        $result = $query->executeQuery();
522
+        return count($result->fetchAllAssociative()) > 0;
523
+    }
524 524
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Command/Notify.php 1 patch
Indentation   +225 added lines, -225 removed lines patch added patch discarded remove patch
@@ -25,229 +25,229 @@
 block discarded – undo
25 25
 use Symfony\Component\Console\Output\OutputInterface;
26 26
 
27 27
 class Notify extends StorageAuthBase {
28
-	public function __construct(
29
-		private IDBConnection $connection,
30
-		private LoggerInterface $logger,
31
-		GlobalStoragesService $globalService,
32
-		IUserManager $userManager,
33
-	) {
34
-		parent::__construct($globalService, $userManager);
35
-	}
36
-
37
-	protected function configure(): void {
38
-		$this
39
-			->setName('files_external:notify')
40
-			->setDescription('Listen for active update notifications for a configured external mount')
41
-			->addArgument(
42
-				'mount_id',
43
-				InputArgument::REQUIRED,
44
-				'the mount id of the mount to listen to'
45
-			)->addOption(
46
-				'user',
47
-				'u',
48
-				InputOption::VALUE_REQUIRED,
49
-				'The username for the remote mount (required only for some mount configuration that don\'t store credentials)'
50
-			)->addOption(
51
-				'password',
52
-				'p',
53
-				InputOption::VALUE_REQUIRED,
54
-				'The password for the remote mount (required only for some mount configuration that don\'t store credentials)'
55
-			)->addOption(
56
-				'path',
57
-				'',
58
-				InputOption::VALUE_REQUIRED,
59
-				'The directory in the storage to listen for updates in',
60
-				'/'
61
-			)->addOption(
62
-				'no-self-check',
63
-				'',
64
-				InputOption::VALUE_NONE,
65
-				'Disable self check on startup'
66
-			)->addOption(
67
-				'dry-run',
68
-				'',
69
-				InputOption::VALUE_NONE,
70
-				'Don\'t make any changes, only log detected changes'
71
-			);
72
-		parent::configure();
73
-	}
74
-
75
-	protected function execute(InputInterface $input, OutputInterface $output): int {
76
-		[$mount, $storage] = $this->createStorage($input, $output);
77
-		if ($storage === null) {
78
-			return self::FAILURE;
79
-		}
80
-
81
-		if (!$storage instanceof INotifyStorage) {
82
-			$output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>');
83
-			return self::FAILURE;
84
-		}
85
-
86
-		$dryRun = $input->getOption('dry-run');
87
-		if ($dryRun && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
88
-			$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
89
-		}
90
-
91
-		$path = trim($input->getOption('path'), '/');
92
-		$notifyHandler = $storage->notify($path);
93
-
94
-		if (!$input->getOption('no-self-check')) {
95
-			$this->selfTest($storage, $notifyHandler, $output);
96
-		}
97
-
98
-		$notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun): void {
99
-			$this->logUpdate($change, $output);
100
-			if ($change instanceof IRenameChange) {
101
-				$this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun);
102
-			}
103
-			$this->markParentAsOutdated($mount->getId(), $change->getPath(), $output, $dryRun);
104
-		});
105
-		return self::SUCCESS;
106
-	}
107
-
108
-	private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun): void {
109
-		$parent = ltrim(dirname($path), '/');
110
-		if ($parent === '.') {
111
-			$parent = '';
112
-		}
113
-
114
-		try {
115
-			$storages = $this->getStorageIds($mountId, $parent);
116
-		} catch (DriverException $ex) {
117
-			$this->logger->warning('Error while trying to find correct storage ids.', ['exception' => $ex]);
118
-			$this->connection = $this->reconnectToDatabase($this->connection, $output);
119
-			$output->writeln('<info>Needed to reconnect to the database</info>');
120
-			$storages = $this->getStorageIds($mountId, $path);
121
-		}
122
-		if (count($storages) === 0) {
123
-			$output->writeln("  no users found with access to '$parent', skipping", OutputInterface::VERBOSITY_VERBOSE);
124
-			return;
125
-		}
126
-
127
-		$users = array_map(function (array $storage) {
128
-			return $storage['user_id'];
129
-		}, $storages);
130
-
131
-		$output->writeln("  marking '$parent' as outdated for " . implode(', ', $users), OutputInterface::VERBOSITY_VERBOSE);
132
-
133
-		$storageIds = array_map(function (array $storage) {
134
-			return intval($storage['storage_id']);
135
-		}, $storages);
136
-		$storageIds = array_values(array_unique($storageIds));
137
-
138
-		if ($dryRun) {
139
-			$output->writeln('  dry-run: skipping database write');
140
-		} else {
141
-			$result = $this->updateParent($storageIds, $parent);
142
-			if ($result === 0) {
143
-				//TODO: Find existing parent further up the tree in the database and register that folder instead.
144
-				$this->logger->info('Failed updating parent for "' . $path . '" while trying to register change. It may not exist in the filecache.');
145
-			}
146
-		}
147
-	}
148
-
149
-	private function logUpdate(IChange $change, OutputInterface $output): void {
150
-		$text = match ($change->getType()) {
151
-			INotifyStorage::NOTIFY_ADDED => 'added',
152
-			INotifyStorage::NOTIFY_MODIFIED => 'modified',
153
-			INotifyStorage::NOTIFY_REMOVED => 'removed',
154
-			INotifyStorage::NOTIFY_RENAMED => 'renamed',
155
-			default => '',
156
-		};
157
-
158
-		if ($text === '') {
159
-			return;
160
-		}
161
-
162
-		$text .= ' ' . $change->getPath();
163
-		if ($change instanceof IRenameChange) {
164
-			$text .= ' to ' . $change->getTargetPath();
165
-		}
166
-
167
-		$output->writeln($text, OutputInterface::VERBOSITY_VERBOSE);
168
-	}
169
-
170
-	private function getStorageIds(int $mountId, string $path): array {
171
-		$pathHash = md5(trim(\OC_Util::normalizeUnicode($path), '/'));
172
-		$qb = $this->connection->getQueryBuilder();
173
-		return $qb
174
-			->select('storage_id', 'user_id')
175
-			->from('mounts', 'm')
176
-			->innerJoin('m', 'filecache', 'f', $qb->expr()->eq('m.storage_id', 'f.storage'))
177
-			->where($qb->expr()->eq('mount_id', $qb->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
178
-			->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR)))
179
-			->executeQuery()
180
-			->fetchAllAssociative();
181
-	}
182
-
183
-	private function updateParent(array $storageIds, string $parent): int {
184
-		$pathHash = md5(trim(\OC_Util::normalizeUnicode($parent), '/'));
185
-		$qb = $this->connection->getQueryBuilder();
186
-		return $qb
187
-			->update('filecache')
188
-			->set('size', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT))
189
-			->where($qb->expr()->in('storage', $qb->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY, ':storage_ids')))
190
-			->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR)))
191
-			->executeStatement();
192
-	}
193
-
194
-	private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output): IDBConnection {
195
-		try {
196
-			$connection->close();
197
-		} catch (\Exception $ex) {
198
-			$this->logger->warning('Error while disconnecting from DB', ['exception' => $ex]);
199
-			$output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
200
-		}
201
-		$connected = false;
202
-		while (!$connected) {
203
-			try {
204
-				$connected = $connection->connect();
205
-			} catch (\Exception $ex) {
206
-				$this->logger->warning('Error while re-connecting to database', ['exception' => $ex]);
207
-				$output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
208
-				sleep(60);
209
-			}
210
-		}
211
-		return $connection;
212
-	}
213
-
214
-
215
-	private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output): void {
216
-		usleep(100 * 1000); //give time for the notify to start
217
-		if (!$storage->file_put_contents('/.nc_test_file.txt', 'test content')) {
218
-			$output->writeln('Failed to create test file for self-test');
219
-			return;
220
-		}
221
-		$storage->mkdir('/.nc_test_folder');
222
-		$storage->file_put_contents('/.nc_test_folder/subfile.txt', 'test content');
223
-
224
-		usleep(100 * 1000); //time for all changes to be processed
225
-		$changes = $notifyHandler->getChanges();
226
-
227
-		$storage->unlink('/.nc_test_file.txt');
228
-		$storage->unlink('/.nc_test_folder/subfile.txt');
229
-		$storage->rmdir('/.nc_test_folder');
230
-
231
-		usleep(100 * 1000); //time for all changes to be processed
232
-		$notifyHandler->getChanges(); // flush
233
-
234
-		$foundRootChange = false;
235
-		$foundSubfolderChange = false;
236
-
237
-		foreach ($changes as $change) {
238
-			if ($change->getPath() === '/.nc_test_file.txt' || $change->getPath() === '.nc_test_file.txt') {
239
-				$foundRootChange = true;
240
-			} elseif ($change->getPath() === '/.nc_test_folder/subfile.txt' || $change->getPath() === '.nc_test_folder/subfile.txt') {
241
-				$foundSubfolderChange = true;
242
-			}
243
-		}
244
-
245
-		if ($foundRootChange && $foundSubfolderChange) {
246
-			$output->writeln('<info>Self-test successful</info>', OutputInterface::VERBOSITY_VERBOSE);
247
-		} elseif ($foundRootChange) {
248
-			$output->writeln('<error>Error while running self-test, change is subfolder not detected</error>');
249
-		} else {
250
-			$output->writeln('<error>Error while running self-test, no changes detected</error>');
251
-		}
252
-	}
28
+    public function __construct(
29
+        private IDBConnection $connection,
30
+        private LoggerInterface $logger,
31
+        GlobalStoragesService $globalService,
32
+        IUserManager $userManager,
33
+    ) {
34
+        parent::__construct($globalService, $userManager);
35
+    }
36
+
37
+    protected function configure(): void {
38
+        $this
39
+            ->setName('files_external:notify')
40
+            ->setDescription('Listen for active update notifications for a configured external mount')
41
+            ->addArgument(
42
+                'mount_id',
43
+                InputArgument::REQUIRED,
44
+                'the mount id of the mount to listen to'
45
+            )->addOption(
46
+                'user',
47
+                'u',
48
+                InputOption::VALUE_REQUIRED,
49
+                'The username for the remote mount (required only for some mount configuration that don\'t store credentials)'
50
+            )->addOption(
51
+                'password',
52
+                'p',
53
+                InputOption::VALUE_REQUIRED,
54
+                'The password for the remote mount (required only for some mount configuration that don\'t store credentials)'
55
+            )->addOption(
56
+                'path',
57
+                '',
58
+                InputOption::VALUE_REQUIRED,
59
+                'The directory in the storage to listen for updates in',
60
+                '/'
61
+            )->addOption(
62
+                'no-self-check',
63
+                '',
64
+                InputOption::VALUE_NONE,
65
+                'Disable self check on startup'
66
+            )->addOption(
67
+                'dry-run',
68
+                '',
69
+                InputOption::VALUE_NONE,
70
+                'Don\'t make any changes, only log detected changes'
71
+            );
72
+        parent::configure();
73
+    }
74
+
75
+    protected function execute(InputInterface $input, OutputInterface $output): int {
76
+        [$mount, $storage] = $this->createStorage($input, $output);
77
+        if ($storage === null) {
78
+            return self::FAILURE;
79
+        }
80
+
81
+        if (!$storage instanceof INotifyStorage) {
82
+            $output->writeln('<error>Mount of type "' . $mount->getBackend()->getText() . '" does not support active update notifications</error>');
83
+            return self::FAILURE;
84
+        }
85
+
86
+        $dryRun = $input->getOption('dry-run');
87
+        if ($dryRun && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
88
+            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
89
+        }
90
+
91
+        $path = trim($input->getOption('path'), '/');
92
+        $notifyHandler = $storage->notify($path);
93
+
94
+        if (!$input->getOption('no-self-check')) {
95
+            $this->selfTest($storage, $notifyHandler, $output);
96
+        }
97
+
98
+        $notifyHandler->listen(function (IChange $change) use ($mount, $output, $dryRun): void {
99
+            $this->logUpdate($change, $output);
100
+            if ($change instanceof IRenameChange) {
101
+                $this->markParentAsOutdated($mount->getId(), $change->getTargetPath(), $output, $dryRun);
102
+            }
103
+            $this->markParentAsOutdated($mount->getId(), $change->getPath(), $output, $dryRun);
104
+        });
105
+        return self::SUCCESS;
106
+    }
107
+
108
+    private function markParentAsOutdated($mountId, $path, OutputInterface $output, bool $dryRun): void {
109
+        $parent = ltrim(dirname($path), '/');
110
+        if ($parent === '.') {
111
+            $parent = '';
112
+        }
113
+
114
+        try {
115
+            $storages = $this->getStorageIds($mountId, $parent);
116
+        } catch (DriverException $ex) {
117
+            $this->logger->warning('Error while trying to find correct storage ids.', ['exception' => $ex]);
118
+            $this->connection = $this->reconnectToDatabase($this->connection, $output);
119
+            $output->writeln('<info>Needed to reconnect to the database</info>');
120
+            $storages = $this->getStorageIds($mountId, $path);
121
+        }
122
+        if (count($storages) === 0) {
123
+            $output->writeln("  no users found with access to '$parent', skipping", OutputInterface::VERBOSITY_VERBOSE);
124
+            return;
125
+        }
126
+
127
+        $users = array_map(function (array $storage) {
128
+            return $storage['user_id'];
129
+        }, $storages);
130
+
131
+        $output->writeln("  marking '$parent' as outdated for " . implode(', ', $users), OutputInterface::VERBOSITY_VERBOSE);
132
+
133
+        $storageIds = array_map(function (array $storage) {
134
+            return intval($storage['storage_id']);
135
+        }, $storages);
136
+        $storageIds = array_values(array_unique($storageIds));
137
+
138
+        if ($dryRun) {
139
+            $output->writeln('  dry-run: skipping database write');
140
+        } else {
141
+            $result = $this->updateParent($storageIds, $parent);
142
+            if ($result === 0) {
143
+                //TODO: Find existing parent further up the tree in the database and register that folder instead.
144
+                $this->logger->info('Failed updating parent for "' . $path . '" while trying to register change. It may not exist in the filecache.');
145
+            }
146
+        }
147
+    }
148
+
149
+    private function logUpdate(IChange $change, OutputInterface $output): void {
150
+        $text = match ($change->getType()) {
151
+            INotifyStorage::NOTIFY_ADDED => 'added',
152
+            INotifyStorage::NOTIFY_MODIFIED => 'modified',
153
+            INotifyStorage::NOTIFY_REMOVED => 'removed',
154
+            INotifyStorage::NOTIFY_RENAMED => 'renamed',
155
+            default => '',
156
+        };
157
+
158
+        if ($text === '') {
159
+            return;
160
+        }
161
+
162
+        $text .= ' ' . $change->getPath();
163
+        if ($change instanceof IRenameChange) {
164
+            $text .= ' to ' . $change->getTargetPath();
165
+        }
166
+
167
+        $output->writeln($text, OutputInterface::VERBOSITY_VERBOSE);
168
+    }
169
+
170
+    private function getStorageIds(int $mountId, string $path): array {
171
+        $pathHash = md5(trim(\OC_Util::normalizeUnicode($path), '/'));
172
+        $qb = $this->connection->getQueryBuilder();
173
+        return $qb
174
+            ->select('storage_id', 'user_id')
175
+            ->from('mounts', 'm')
176
+            ->innerJoin('m', 'filecache', 'f', $qb->expr()->eq('m.storage_id', 'f.storage'))
177
+            ->where($qb->expr()->eq('mount_id', $qb->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)))
178
+            ->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR)))
179
+            ->executeQuery()
180
+            ->fetchAllAssociative();
181
+    }
182
+
183
+    private function updateParent(array $storageIds, string $parent): int {
184
+        $pathHash = md5(trim(\OC_Util::normalizeUnicode($parent), '/'));
185
+        $qb = $this->connection->getQueryBuilder();
186
+        return $qb
187
+            ->update('filecache')
188
+            ->set('size', $qb->createNamedParameter(-1, IQueryBuilder::PARAM_INT))
189
+            ->where($qb->expr()->in('storage', $qb->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY, ':storage_ids')))
190
+            ->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash, IQueryBuilder::PARAM_STR)))
191
+            ->executeStatement();
192
+    }
193
+
194
+    private function reconnectToDatabase(IDBConnection $connection, OutputInterface $output): IDBConnection {
195
+        try {
196
+            $connection->close();
197
+        } catch (\Exception $ex) {
198
+            $this->logger->warning('Error while disconnecting from DB', ['exception' => $ex]);
199
+            $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
200
+        }
201
+        $connected = false;
202
+        while (!$connected) {
203
+            try {
204
+                $connected = $connection->connect();
205
+            } catch (\Exception $ex) {
206
+                $this->logger->warning('Error while re-connecting to database', ['exception' => $ex]);
207
+                $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
208
+                sleep(60);
209
+            }
210
+        }
211
+        return $connection;
212
+    }
213
+
214
+
215
+    private function selfTest(IStorage $storage, INotifyHandler $notifyHandler, OutputInterface $output): void {
216
+        usleep(100 * 1000); //give time for the notify to start
217
+        if (!$storage->file_put_contents('/.nc_test_file.txt', 'test content')) {
218
+            $output->writeln('Failed to create test file for self-test');
219
+            return;
220
+        }
221
+        $storage->mkdir('/.nc_test_folder');
222
+        $storage->file_put_contents('/.nc_test_folder/subfile.txt', 'test content');
223
+
224
+        usleep(100 * 1000); //time for all changes to be processed
225
+        $changes = $notifyHandler->getChanges();
226
+
227
+        $storage->unlink('/.nc_test_file.txt');
228
+        $storage->unlink('/.nc_test_folder/subfile.txt');
229
+        $storage->rmdir('/.nc_test_folder');
230
+
231
+        usleep(100 * 1000); //time for all changes to be processed
232
+        $notifyHandler->getChanges(); // flush
233
+
234
+        $foundRootChange = false;
235
+        $foundSubfolderChange = false;
236
+
237
+        foreach ($changes as $change) {
238
+            if ($change->getPath() === '/.nc_test_file.txt' || $change->getPath() === '.nc_test_file.txt') {
239
+                $foundRootChange = true;
240
+            } elseif ($change->getPath() === '/.nc_test_folder/subfile.txt' || $change->getPath() === '.nc_test_folder/subfile.txt') {
241
+                $foundSubfolderChange = true;
242
+            }
243
+        }
244
+
245
+        if ($foundRootChange && $foundSubfolderChange) {
246
+            $output->writeln('<info>Self-test successful</info>', OutputInterface::VERBOSITY_VERBOSE);
247
+        } elseif ($foundRootChange) {
248
+            $output->writeln('<error>Error while running self-test, change is subfolder not detected</error>');
249
+        } else {
250
+            $output->writeln('<error>Error while running self-test, no changes detected</error>');
251
+        }
252
+    }
253 253
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Migration/Version1015Date20211104103506.php 2 patches
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -20,71 +20,71 @@
 block discarded – undo
20 20
 
21 21
 class Version1015Date20211104103506 extends SimpleMigrationStep {
22 22
 
23
-	public function __construct(
24
-		private IDBConnection $connection,
25
-		private LoggerInterface $logger,
26
-	) {
27
-	}
23
+    public function __construct(
24
+        private IDBConnection $connection,
25
+        private LoggerInterface $logger,
26
+    ) {
27
+    }
28 28
 
29
-	public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
30
-		$qb = $this->connection->getQueryBuilder();
31
-		$qb->update('storages')
32
-			->set('id', $qb->createParameter('newId'))
33
-			->where($qb->expr()->eq('id', $qb->createParameter('oldId')));
29
+    public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
30
+        $qb = $this->connection->getQueryBuilder();
31
+        $qb->update('storages')
32
+            ->set('id', $qb->createParameter('newId'))
33
+            ->where($qb->expr()->eq('id', $qb->createParameter('oldId')));
34 34
 
35
-		$mounts = $this->getS3Mounts();
36
-		if (!$mounts instanceof IResult) {
37
-			throw new \Exception('Could not fetch existing mounts for migration');
38
-		}
35
+        $mounts = $this->getS3Mounts();
36
+        if (!$mounts instanceof IResult) {
37
+            throw new \Exception('Could not fetch existing mounts for migration');
38
+        }
39 39
 
40
-		while ($mount = $mounts->fetchAssociative()) {
41
-			$config = $this->getStorageConfig((int)$mount['mount_id']);
42
-			$hostname = $config['hostname'];
43
-			$bucket = $config['bucket'];
44
-			$key = $config['key'];
45
-			$oldId = Storage::adjustStorageId('amazon::' . $bucket);
46
-			$newId = Storage::adjustStorageId('amazon::external::' . md5($hostname . ':' . $bucket . ':' . $key));
47
-			try {
48
-				$qb->setParameter('oldId', $oldId);
49
-				$qb->setParameter('newId', $newId);
50
-				$qb->executeStatement();
51
-				$this->logger->info('Migrated s3 storage id for mount with id ' . $mount['mount_id'] . ' to ' . $newId);
52
-			} catch (Exception $e) {
53
-				$this->logger->error('Failed to migrate external s3 storage id for mount with id ' . $mount['mount_id'], [
54
-					'exception' => $e
55
-				]);
56
-			}
57
-		}
58
-		return null;
59
-	}
40
+        while ($mount = $mounts->fetchAssociative()) {
41
+            $config = $this->getStorageConfig((int)$mount['mount_id']);
42
+            $hostname = $config['hostname'];
43
+            $bucket = $config['bucket'];
44
+            $key = $config['key'];
45
+            $oldId = Storage::adjustStorageId('amazon::' . $bucket);
46
+            $newId = Storage::adjustStorageId('amazon::external::' . md5($hostname . ':' . $bucket . ':' . $key));
47
+            try {
48
+                $qb->setParameter('oldId', $oldId);
49
+                $qb->setParameter('newId', $newId);
50
+                $qb->executeStatement();
51
+                $this->logger->info('Migrated s3 storage id for mount with id ' . $mount['mount_id'] . ' to ' . $newId);
52
+            } catch (Exception $e) {
53
+                $this->logger->error('Failed to migrate external s3 storage id for mount with id ' . $mount['mount_id'], [
54
+                    'exception' => $e
55
+                ]);
56
+            }
57
+        }
58
+        return null;
59
+    }
60 60
 
61
-	/**
62
-	 * @throws Exception
63
-	 * @return IResult|int
64
-	 */
65
-	private function getS3Mounts() {
66
-		$qb = $this->connection->getQueryBuilder();
67
-		$qb->select('m.mount_id')
68
-			->selectAlias('c.value', 'bucket')
69
-			->from('external_mounts', 'm')
70
-			->innerJoin('m', 'external_config', 'c', 'c.mount_id = m.mount_id')
71
-			->where($qb->expr()->eq('m.storage_backend', $qb->createPositionalParameter('amazons3')))
72
-			->andWhere($qb->expr()->eq('c.key', $qb->createPositionalParameter('bucket')));
73
-		return $qb->executeQuery();
74
-	}
61
+    /**
62
+     * @throws Exception
63
+     * @return IResult|int
64
+     */
65
+    private function getS3Mounts() {
66
+        $qb = $this->connection->getQueryBuilder();
67
+        $qb->select('m.mount_id')
68
+            ->selectAlias('c.value', 'bucket')
69
+            ->from('external_mounts', 'm')
70
+            ->innerJoin('m', 'external_config', 'c', 'c.mount_id = m.mount_id')
71
+            ->where($qb->expr()->eq('m.storage_backend', $qb->createPositionalParameter('amazons3')))
72
+            ->andWhere($qb->expr()->eq('c.key', $qb->createPositionalParameter('bucket')));
73
+        return $qb->executeQuery();
74
+    }
75 75
 
76
-	/**
77
-	 * @throws Exception
78
-	 */
79
-	private function getStorageConfig(int $mountId): array {
80
-		$qb = $this->connection->getQueryBuilder();
81
-		$qb->select('key', 'value')
82
-			->from('external_config')
83
-			->where($qb->expr()->eq('mount_id', $qb->createPositionalParameter($mountId)));
84
-		$config = [];
85
-		foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
86
-			$config[$row['key']] = $row['value'];
87
-		}
88
-		return $config;
89
-	}
76
+    /**
77
+     * @throws Exception
78
+     */
79
+    private function getStorageConfig(int $mountId): array {
80
+        $qb = $this->connection->getQueryBuilder();
81
+        $qb->select('key', 'value')
82
+            ->from('external_config')
83
+            ->where($qb->expr()->eq('mount_id', $qb->createPositionalParameter($mountId)));
84
+        $config = [];
85
+        foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
86
+            $config[$row['key']] = $row['value'];
87
+        }
88
+        return $config;
89
+    }
90 90
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -38,19 +38,19 @@
 block discarded – undo
38 38
 		}
39 39
 
40 40
 		while ($mount = $mounts->fetchAssociative()) {
41
-			$config = $this->getStorageConfig((int)$mount['mount_id']);
41
+			$config = $this->getStorageConfig((int) $mount['mount_id']);
42 42
 			$hostname = $config['hostname'];
43 43
 			$bucket = $config['bucket'];
44 44
 			$key = $config['key'];
45
-			$oldId = Storage::adjustStorageId('amazon::' . $bucket);
46
-			$newId = Storage::adjustStorageId('amazon::external::' . md5($hostname . ':' . $bucket . ':' . $key));
45
+			$oldId = Storage::adjustStorageId('amazon::'.$bucket);
46
+			$newId = Storage::adjustStorageId('amazon::external::'.md5($hostname.':'.$bucket.':'.$key));
47 47
 			try {
48 48
 				$qb->setParameter('oldId', $oldId);
49 49
 				$qb->setParameter('newId', $newId);
50 50
 				$qb->executeStatement();
51
-				$this->logger->info('Migrated s3 storage id for mount with id ' . $mount['mount_id'] . ' to ' . $newId);
51
+				$this->logger->info('Migrated s3 storage id for mount with id '.$mount['mount_id'].' to '.$newId);
52 52
 			} catch (Exception $e) {
53
-				$this->logger->error('Failed to migrate external s3 storage id for mount with id ' . $mount['mount_id'], [
53
+				$this->logger->error('Failed to migrate external s3 storage id for mount with id '.$mount['mount_id'], [
54 54
 					'exception' => $e
55 55
 				]);
56 56
 			}
Please login to merge, or discard this patch.
apps/files/tests/BackgroundJob/DeleteOrphanedItemsJobTest.php 1 patch
Indentation   +224 added lines, -224 removed lines patch added patch discarded remove patch
@@ -23,228 +23,228 @@
 block discarded – undo
23 23
  */
24 24
 #[\PHPUnit\Framework\Attributes\Group('DB')]
25 25
 class DeleteOrphanedItemsJobTest extends \Test\TestCase {
26
-	protected IDBConnection $connection;
27
-	protected LoggerInterface $logger;
28
-	protected ITimeFactory $timeFactory;
29
-
30
-	protected function setUp(): void {
31
-		parent::setUp();
32
-		$this->connection = Server::get(IDBConnection::class);
33
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
34
-		$this->logger = Server::get(LoggerInterface::class);
35
-	}
36
-
37
-	protected function cleanMapping(string $table): void {
38
-		$query = $this->connection->getQueryBuilder();
39
-		$query->delete($table)->executeStatement();
40
-	}
41
-
42
-	protected function getMappings(string $table): array {
43
-		$query = $this->connection->getQueryBuilder();
44
-		$query->select('*')
45
-			->from($table);
46
-		$result = $query->executeQuery();
47
-		$mapping = $result->fetchAllAssociative();
48
-		$result->closeCursor();
49
-
50
-		return $mapping;
51
-	}
52
-
53
-	/**
54
-	 * Test clearing orphaned system tag mappings
55
-	 */
56
-	public function testClearSystemTagMappings(): void {
57
-		$this->cleanMapping('systemtag_object_mapping');
58
-
59
-		$query = $this->connection->getQueryBuilder();
60
-		$query->insert('filecache')
61
-			->values([
62
-				'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
63
-				'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
64
-				'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
65
-			])->executeStatement();
66
-		$fileId = $query->getLastInsertId();
67
-
68
-		// Existing file
69
-		$query = $this->connection->getQueryBuilder();
70
-		$query->insert('systemtag_object_mapping')
71
-			->values([
72
-				'objectid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
73
-				'objecttype' => $query->createNamedParameter('files'),
74
-				'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
75
-			])->executeStatement();
76
-
77
-		// Non-existing file
78
-		$query = $this->connection->getQueryBuilder();
79
-		$query->insert('systemtag_object_mapping')
80
-			->values([
81
-				'objectid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
82
-				'objecttype' => $query->createNamedParameter('files'),
83
-				'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
84
-			])->executeStatement();
85
-
86
-		$mapping = $this->getMappings('systemtag_object_mapping');
87
-		$this->assertCount(2, $mapping);
88
-
89
-		$job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
90
-		self::invokePrivate($job, 'cleanSystemTags');
91
-
92
-		$mapping = $this->getMappings('systemtag_object_mapping');
93
-		$this->assertCount(1, $mapping);
94
-
95
-		$query = $this->connection->getQueryBuilder();
96
-		$query->delete('filecache')
97
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
98
-			->executeStatement();
99
-		$this->cleanMapping('systemtag_object_mapping');
100
-	}
101
-
102
-	/**
103
-	 * Test clearing orphaned system tag mappings
104
-	 */
105
-	public function testClearUserTagMappings(): void {
106
-		$this->cleanMapping('vcategory_to_object');
107
-
108
-		$query = $this->connection->getQueryBuilder();
109
-		$query->insert('filecache')
110
-			->values([
111
-				'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
112
-				'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
113
-				'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
114
-			])->executeStatement();
115
-		$fileId = $query->getLastInsertId();
116
-
117
-		// Existing file
118
-		$query = $this->connection->getQueryBuilder();
119
-		$query->insert('vcategory_to_object')
120
-			->values([
121
-				'objid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
122
-				'type' => $query->createNamedParameter('files'),
123
-				'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
124
-			])->executeStatement();
125
-
126
-		// Non-existing file
127
-		$query = $this->connection->getQueryBuilder();
128
-		$query->insert('vcategory_to_object')
129
-			->values([
130
-				'objid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
131
-				'type' => $query->createNamedParameter('files'),
132
-				'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
133
-			])->executeStatement();
134
-
135
-		$mapping = $this->getMappings('vcategory_to_object');
136
-		$this->assertCount(2, $mapping);
137
-
138
-		$job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
139
-		self::invokePrivate($job, 'cleanUserTags');
140
-
141
-		$mapping = $this->getMappings('vcategory_to_object');
142
-		$this->assertCount(1, $mapping);
143
-
144
-		$query = $this->connection->getQueryBuilder();
145
-		$query->delete('filecache')
146
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
147
-			->executeStatement();
148
-		$this->cleanMapping('vcategory_to_object');
149
-	}
150
-
151
-	/**
152
-	 * Test clearing orphaned system tag mappings
153
-	 */
154
-	public function testClearComments(): void {
155
-		$this->cleanMapping('comments');
156
-
157
-		$query = $this->connection->getQueryBuilder();
158
-		$query->insert('filecache')
159
-			->values([
160
-				'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
161
-				'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
162
-				'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
163
-			])->executeStatement();
164
-		$fileId = $query->getLastInsertId();
165
-
166
-		// Existing file
167
-		$query = $this->connection->getQueryBuilder();
168
-		$query->insert('comments')
169
-			->values([
170
-				'object_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
171
-				'object_type' => $query->createNamedParameter('files'),
172
-				'actor_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
173
-				'actor_type' => $query->createNamedParameter('users'),
174
-			])->executeStatement();
175
-
176
-		// Non-existing file
177
-		$query = $this->connection->getQueryBuilder();
178
-		$query->insert('comments')
179
-			->values([
180
-				'object_id' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
181
-				'object_type' => $query->createNamedParameter('files'),
182
-				'actor_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
183
-				'actor_type' => $query->createNamedParameter('users'),
184
-			])->executeStatement();
185
-
186
-		$mapping = $this->getMappings('comments');
187
-		$this->assertCount(2, $mapping);
188
-
189
-		$job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
190
-		self::invokePrivate($job, 'cleanComments');
191
-
192
-		$mapping = $this->getMappings('comments');
193
-		$this->assertCount(1, $mapping);
194
-
195
-		$query = $this->connection->getQueryBuilder();
196
-		$query->delete('filecache')
197
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
198
-			->executeStatement();
199
-		$this->cleanMapping('comments');
200
-	}
201
-
202
-	/**
203
-	 * Test clearing orphaned system tag mappings
204
-	 */
205
-	public function testClearCommentReadMarks(): void {
206
-		$this->cleanMapping('comments_read_markers');
207
-
208
-		$query = $this->connection->getQueryBuilder();
209
-		$query->insert('filecache')
210
-			->values([
211
-				'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
212
-				'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
213
-				'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
214
-			])->executeStatement();
215
-		$fileId = $query->getLastInsertId();
216
-
217
-		// Existing file
218
-		$query = $this->connection->getQueryBuilder();
219
-		$query->insert('comments_read_markers')
220
-			->values([
221
-				'object_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
222
-				'object_type' => $query->createNamedParameter('files'),
223
-				'user_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
224
-			])->executeStatement();
225
-
226
-		// Non-existing file
227
-		$query = $this->connection->getQueryBuilder();
228
-		$query->insert('comments_read_markers')
229
-			->values([
230
-				'object_id' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
231
-				'object_type' => $query->createNamedParameter('files'),
232
-				'user_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
233
-			])->executeStatement();
234
-
235
-		$mapping = $this->getMappings('comments_read_markers');
236
-		$this->assertCount(2, $mapping);
237
-
238
-		$job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
239
-		self::invokePrivate($job, 'cleanCommentMarkers');
240
-
241
-		$mapping = $this->getMappings('comments_read_markers');
242
-		$this->assertCount(1, $mapping);
243
-
244
-		$query = $this->connection->getQueryBuilder();
245
-		$query->delete('filecache')
246
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
247
-			->executeStatement();
248
-		$this->cleanMapping('comments_read_markers');
249
-	}
26
+    protected IDBConnection $connection;
27
+    protected LoggerInterface $logger;
28
+    protected ITimeFactory $timeFactory;
29
+
30
+    protected function setUp(): void {
31
+        parent::setUp();
32
+        $this->connection = Server::get(IDBConnection::class);
33
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
34
+        $this->logger = Server::get(LoggerInterface::class);
35
+    }
36
+
37
+    protected function cleanMapping(string $table): void {
38
+        $query = $this->connection->getQueryBuilder();
39
+        $query->delete($table)->executeStatement();
40
+    }
41
+
42
+    protected function getMappings(string $table): array {
43
+        $query = $this->connection->getQueryBuilder();
44
+        $query->select('*')
45
+            ->from($table);
46
+        $result = $query->executeQuery();
47
+        $mapping = $result->fetchAllAssociative();
48
+        $result->closeCursor();
49
+
50
+        return $mapping;
51
+    }
52
+
53
+    /**
54
+     * Test clearing orphaned system tag mappings
55
+     */
56
+    public function testClearSystemTagMappings(): void {
57
+        $this->cleanMapping('systemtag_object_mapping');
58
+
59
+        $query = $this->connection->getQueryBuilder();
60
+        $query->insert('filecache')
61
+            ->values([
62
+                'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
63
+                'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
64
+                'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
65
+            ])->executeStatement();
66
+        $fileId = $query->getLastInsertId();
67
+
68
+        // Existing file
69
+        $query = $this->connection->getQueryBuilder();
70
+        $query->insert('systemtag_object_mapping')
71
+            ->values([
72
+                'objectid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
73
+                'objecttype' => $query->createNamedParameter('files'),
74
+                'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
75
+            ])->executeStatement();
76
+
77
+        // Non-existing file
78
+        $query = $this->connection->getQueryBuilder();
79
+        $query->insert('systemtag_object_mapping')
80
+            ->values([
81
+                'objectid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
82
+                'objecttype' => $query->createNamedParameter('files'),
83
+                'systemtagid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
84
+            ])->executeStatement();
85
+
86
+        $mapping = $this->getMappings('systemtag_object_mapping');
87
+        $this->assertCount(2, $mapping);
88
+
89
+        $job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
90
+        self::invokePrivate($job, 'cleanSystemTags');
91
+
92
+        $mapping = $this->getMappings('systemtag_object_mapping');
93
+        $this->assertCount(1, $mapping);
94
+
95
+        $query = $this->connection->getQueryBuilder();
96
+        $query->delete('filecache')
97
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
98
+            ->executeStatement();
99
+        $this->cleanMapping('systemtag_object_mapping');
100
+    }
101
+
102
+    /**
103
+     * Test clearing orphaned system tag mappings
104
+     */
105
+    public function testClearUserTagMappings(): void {
106
+        $this->cleanMapping('vcategory_to_object');
107
+
108
+        $query = $this->connection->getQueryBuilder();
109
+        $query->insert('filecache')
110
+            ->values([
111
+                'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
112
+                'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
113
+                'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
114
+            ])->executeStatement();
115
+        $fileId = $query->getLastInsertId();
116
+
117
+        // Existing file
118
+        $query = $this->connection->getQueryBuilder();
119
+        $query->insert('vcategory_to_object')
120
+            ->values([
121
+                'objid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
122
+                'type' => $query->createNamedParameter('files'),
123
+                'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
124
+            ])->executeStatement();
125
+
126
+        // Non-existing file
127
+        $query = $this->connection->getQueryBuilder();
128
+        $query->insert('vcategory_to_object')
129
+            ->values([
130
+                'objid' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
131
+                'type' => $query->createNamedParameter('files'),
132
+                'categoryid' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
133
+            ])->executeStatement();
134
+
135
+        $mapping = $this->getMappings('vcategory_to_object');
136
+        $this->assertCount(2, $mapping);
137
+
138
+        $job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
139
+        self::invokePrivate($job, 'cleanUserTags');
140
+
141
+        $mapping = $this->getMappings('vcategory_to_object');
142
+        $this->assertCount(1, $mapping);
143
+
144
+        $query = $this->connection->getQueryBuilder();
145
+        $query->delete('filecache')
146
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
147
+            ->executeStatement();
148
+        $this->cleanMapping('vcategory_to_object');
149
+    }
150
+
151
+    /**
152
+     * Test clearing orphaned system tag mappings
153
+     */
154
+    public function testClearComments(): void {
155
+        $this->cleanMapping('comments');
156
+
157
+        $query = $this->connection->getQueryBuilder();
158
+        $query->insert('filecache')
159
+            ->values([
160
+                'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
161
+                'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
162
+                'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
163
+            ])->executeStatement();
164
+        $fileId = $query->getLastInsertId();
165
+
166
+        // Existing file
167
+        $query = $this->connection->getQueryBuilder();
168
+        $query->insert('comments')
169
+            ->values([
170
+                'object_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
171
+                'object_type' => $query->createNamedParameter('files'),
172
+                'actor_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
173
+                'actor_type' => $query->createNamedParameter('users'),
174
+            ])->executeStatement();
175
+
176
+        // Non-existing file
177
+        $query = $this->connection->getQueryBuilder();
178
+        $query->insert('comments')
179
+            ->values([
180
+                'object_id' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
181
+                'object_type' => $query->createNamedParameter('files'),
182
+                'actor_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
183
+                'actor_type' => $query->createNamedParameter('users'),
184
+            ])->executeStatement();
185
+
186
+        $mapping = $this->getMappings('comments');
187
+        $this->assertCount(2, $mapping);
188
+
189
+        $job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
190
+        self::invokePrivate($job, 'cleanComments');
191
+
192
+        $mapping = $this->getMappings('comments');
193
+        $this->assertCount(1, $mapping);
194
+
195
+        $query = $this->connection->getQueryBuilder();
196
+        $query->delete('filecache')
197
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
198
+            ->executeStatement();
199
+        $this->cleanMapping('comments');
200
+    }
201
+
202
+    /**
203
+     * Test clearing orphaned system tag mappings
204
+     */
205
+    public function testClearCommentReadMarks(): void {
206
+        $this->cleanMapping('comments_read_markers');
207
+
208
+        $query = $this->connection->getQueryBuilder();
209
+        $query->insert('filecache')
210
+            ->values([
211
+                'storage' => $query->createNamedParameter(1337, IQueryBuilder::PARAM_INT),
212
+                'path' => $query->createNamedParameter('apps/files/tests/deleteorphanedtagsjobtest.php'),
213
+                'path_hash' => $query->createNamedParameter(md5('apps/files/tests/deleteorphanedtagsjobtest.php')),
214
+            ])->executeStatement();
215
+        $fileId = $query->getLastInsertId();
216
+
217
+        // Existing file
218
+        $query = $this->connection->getQueryBuilder();
219
+        $query->insert('comments_read_markers')
220
+            ->values([
221
+                'object_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
222
+                'object_type' => $query->createNamedParameter('files'),
223
+                'user_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
224
+            ])->executeStatement();
225
+
226
+        // Non-existing file
227
+        $query = $this->connection->getQueryBuilder();
228
+        $query->insert('comments_read_markers')
229
+            ->values([
230
+                'object_id' => $query->createNamedParameter($fileId + 1, IQueryBuilder::PARAM_INT),
231
+                'object_type' => $query->createNamedParameter('files'),
232
+                'user_id' => $query->createNamedParameter('Alice', IQueryBuilder::PARAM_INT),
233
+            ])->executeStatement();
234
+
235
+        $mapping = $this->getMappings('comments_read_markers');
236
+        $this->assertCount(2, $mapping);
237
+
238
+        $job = new DeleteOrphanedItems($this->timeFactory, $this->connection, $this->logger);
239
+        self::invokePrivate($job, 'cleanCommentMarkers');
240
+
241
+        $mapping = $this->getMappings('comments_read_markers');
242
+        $this->assertCount(1, $mapping);
243
+
244
+        $query = $this->connection->getQueryBuilder();
245
+        $query->delete('filecache')
246
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
247
+            ->executeStatement();
248
+        $this->cleanMapping('comments_read_markers');
249
+    }
250 250
 }
Please login to merge, or discard this patch.
apps/files/tests/Command/DeleteOrphanedFilesTest.php 1 patch
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -28,111 +28,111 @@
 block discarded – undo
28 28
 #[\PHPUnit\Framework\Attributes\Group('DB')]
29 29
 class DeleteOrphanedFilesTest extends TestCase {
30 30
 
31
-	private DeleteOrphanedFiles $command;
32
-	private IDBConnection $connection;
33
-	private string $user1;
31
+    private DeleteOrphanedFiles $command;
32
+    private IDBConnection $connection;
33
+    private string $user1;
34 34
 
35
-	protected function setUp(): void {
36
-		parent::setUp();
35
+    protected function setUp(): void {
36
+        parent::setUp();
37 37
 
38
-		$this->connection = Server::get(IDBConnection::class);
38
+        $this->connection = Server::get(IDBConnection::class);
39 39
 
40
-		$this->user1 = $this->getUniqueID('user1_');
40
+        $this->user1 = $this->getUniqueID('user1_');
41 41
 
42
-		$userManager = Server::get(IUserManager::class);
43
-		$userManager->createUser($this->user1, 'pass');
42
+        $userManager = Server::get(IUserManager::class);
43
+        $userManager->createUser($this->user1, 'pass');
44 44
 
45
-		$this->command = new DeleteOrphanedFiles($this->connection);
46
-	}
45
+        $this->command = new DeleteOrphanedFiles($this->connection);
46
+    }
47 47
 
48
-	protected function tearDown(): void {
49
-		$userManager = Server::get(IUserManager::class);
50
-		$user1 = $userManager->get($this->user1);
51
-		if ($user1) {
52
-			$user1->delete();
53
-		}
48
+    protected function tearDown(): void {
49
+        $userManager = Server::get(IUserManager::class);
50
+        $user1 = $userManager->get($this->user1);
51
+        if ($user1) {
52
+            $user1->delete();
53
+        }
54 54
 
55
-		$this->logout();
55
+        $this->logout();
56 56
 
57
-		parent::tearDown();
58
-	}
57
+        parent::tearDown();
58
+    }
59 59
 
60
-	protected function getFile(int $fileId): array {
61
-		$query = $this->connection->getQueryBuilder();
62
-		$query->select('*')
63
-			->from('filecache')
64
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId)));
65
-		return $query->executeQuery()->fetchAllAssociative();
66
-	}
60
+    protected function getFile(int $fileId): array {
61
+        $query = $this->connection->getQueryBuilder();
62
+        $query->select('*')
63
+            ->from('filecache')
64
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId)));
65
+        return $query->executeQuery()->fetchAllAssociative();
66
+    }
67 67
 
68
-	protected function getMounts(int $storageId): array {
69
-		$query = $this->connection->getQueryBuilder();
70
-		$query->select('*')
71
-			->from('mounts')
72
-			->where($query->expr()->eq('storage_id', $query->createNamedParameter($storageId)));
73
-		return $query->executeQuery()->fetchAllAssociative();
74
-	}
68
+    protected function getMounts(int $storageId): array {
69
+        $query = $this->connection->getQueryBuilder();
70
+        $query->select('*')
71
+            ->from('mounts')
72
+            ->where($query->expr()->eq('storage_id', $query->createNamedParameter($storageId)));
73
+        return $query->executeQuery()->fetchAllAssociative();
74
+    }
75 75
 
76
-	/**
77
-	 * Test clearing orphaned files
78
-	 */
79
-	public function testClearFiles(): void {
80
-		$input = $this->createMock(InputInterface::class);
81
-		$output = $this->createMock(OutputInterface::class);
76
+    /**
77
+     * Test clearing orphaned files
78
+     */
79
+    public function testClearFiles(): void {
80
+        $input = $this->createMock(InputInterface::class);
81
+        $output = $this->createMock(OutputInterface::class);
82 82
 
83
-		$rootFolder = Server::get(IRootFolder::class);
83
+        $rootFolder = Server::get(IRootFolder::class);
84 84
 
85
-		// scan home storage so that mounts are properly setup
86
-		$rootFolder->getUserFolder($this->user1)->getStorage()->getScanner()->scan('');
85
+        // scan home storage so that mounts are properly setup
86
+        $rootFolder->getUserFolder($this->user1)->getStorage()->getScanner()->scan('');
87 87
 
88
-		$this->loginAsUser($this->user1);
88
+        $this->loginAsUser($this->user1);
89 89
 
90
-		$view = new View('/' . $this->user1 . '/');
91
-		$view->mkdir('files/test');
90
+        $view = new View('/' . $this->user1 . '/');
91
+        $view->mkdir('files/test');
92 92
 
93
-		$fileInfo = $view->getFileInfo('files/test');
93
+        $fileInfo = $view->getFileInfo('files/test');
94 94
 
95
-		$storageId = $fileInfo->getStorage()->getId();
96
-		$numericStorageId = $fileInfo->getStorage()->getStorageCache()->getNumericId();
95
+        $storageId = $fileInfo->getStorage()->getId();
96
+        $numericStorageId = $fileInfo->getStorage()->getStorageCache()->getNumericId();
97 97
 
98
-		$this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is available');
99
-		$this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is available');
98
+        $this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is available');
99
+        $this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is available');
100 100
 
101
-		$this->command->execute($input, $output);
101
+        $this->command->execute($input, $output);
102 102
 
103
-		$this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is still available');
104
-		$this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is still available');
103
+        $this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is still available');
104
+        $this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is still available');
105 105
 
106 106
 
107
-		$deletedRows = $this->connection->executeUpdate('DELETE FROM `*PREFIX*storages` WHERE `id` = ?', [$storageId]);
108
-		$this->assertNotNull($deletedRows, 'Asserts that storage got deleted');
109
-		$this->assertSame(1, $deletedRows, 'Asserts that storage got deleted');
107
+        $deletedRows = $this->connection->executeUpdate('DELETE FROM `*PREFIX*storages` WHERE `id` = ?', [$storageId]);
108
+        $this->assertNotNull($deletedRows, 'Asserts that storage got deleted');
109
+        $this->assertSame(1, $deletedRows, 'Asserts that storage got deleted');
110 110
 
111
-		// parent folder, `files`, ´test` and `welcome.txt` => 4 elements
112
-		$calls = [
113
-			'3 orphaned file cache entries deleted',
114
-			'0 orphaned file cache extended entries deleted',
115
-			'1 orphaned mount entries deleted',
116
-		];
117
-		$output
118
-			->expects($this->exactly(3))
119
-			->method('writeln')
120
-			->willReturnCallback(function (string $message) use (&$calls): void {
121
-				$expected = array_shift($calls);
122
-				$this->assertSame($expected, $message);
123
-			});
111
+        // parent folder, `files`, ´test` and `welcome.txt` => 4 elements
112
+        $calls = [
113
+            '3 orphaned file cache entries deleted',
114
+            '0 orphaned file cache extended entries deleted',
115
+            '1 orphaned mount entries deleted',
116
+        ];
117
+        $output
118
+            ->expects($this->exactly(3))
119
+            ->method('writeln')
120
+            ->willReturnCallback(function (string $message) use (&$calls): void {
121
+                $expected = array_shift($calls);
122
+                $this->assertSame($expected, $message);
123
+            });
124 124
 
125
-		$this->command->execute($input, $output);
125
+        $this->command->execute($input, $output);
126 126
 
127
-		$this->assertCount(0, $this->getFile($fileInfo->getId()), 'Asserts that file gets cleaned up');
128
-		$this->assertCount(0, $this->getMounts($numericStorageId), 'Asserts that mount gets cleaned up');
127
+        $this->assertCount(0, $this->getFile($fileInfo->getId()), 'Asserts that file gets cleaned up');
128
+        $this->assertCount(0, $this->getMounts($numericStorageId), 'Asserts that mount gets cleaned up');
129 129
 
130
-		// Rescan folder to add back to cache before deleting
131
-		$rootFolder->getUserFolder($this->user1)->getStorage()->getScanner()->scan('');
132
-		// since we deleted the storage it might throw a (valid) StorageNotAvailableException
133
-		try {
134
-			$view->unlink('files/test');
135
-		} catch (StorageNotAvailableException $e) {
136
-		}
137
-	}
130
+        // Rescan folder to add back to cache before deleting
131
+        $rootFolder->getUserFolder($this->user1)->getStorage()->getScanner()->scan('');
132
+        // since we deleted the storage it might throw a (valid) StorageNotAvailableException
133
+        try {
134
+            $view->unlink('files/test');
135
+        } catch (StorageNotAvailableException $e) {
136
+        }
137
+    }
138 138
 }
Please login to merge, or discard this patch.