Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend conflict resolution to function with nested groupfolders #2289

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 115 additions & 21 deletions lib/Mount/MountProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,32 +125,31 @@ public function getFoldersForUser(IUser $user): array {
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
$folders = $this->getFoldersForUser($user);

// having the folders sorted by mountpoint is required for nested conflict resolution
usort($folders, function ($a, $b) {
return $a['mount_point'] <=> $b['mount_point'];
});

$mountPoints = array_map(function (array $folder) {
return 'files/' . $folder['mount_point'];
}, $folders);
$conflicts = $this->findConflictsForUser($user, $mountPoints);
$conflicts = $this->findConflictsInHome($user, $mountPoints);
$nestedConflicts = $this->findNestedConflicts($this->getRootStorageId(), $folders);

$mounts = [];
foreach ($folders as $folder) {
$folderId = $folder['folder_id'];

return array_values(array_filter(array_map(function ($folder) use ($user, $loader, $conflicts) {
// check for existing files in the user home and rename them if needed
$originalFolderName = $folder['mount_point'];
if (in_array($originalFolderName, $conflicts)) {
/** @var IStorage $userStorage */
$userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage();
$userCache = $userStorage->getCache();
$i = 1;
$folderName = $folder['mount_point'] . ' (' . $i++ . ')';

while ($userCache->inCache("files/$folderName")) {
$folderName = $originalFolderName . ' (' . $i++ . ')';
}

$userStorage->rename("files/$originalFolderName", "files/$folderName");
$userCache->move("files/$originalFolderName", "files/$folderName");
$userStorage->getPropagator()->propagateChange("files/$folderName", time());
$this->resolveConflictInStorage($userStorage, "files/" . $folder['mount_point']);
}

return $this->getMount(
$folder['folder_id'],
$mount = $this->getMount(
$folderId,
'/' . $user->getUID() . '/files/' . $folder['mount_point'],
$folder['permissions'],
$folder['quota'],
Expand All @@ -159,7 +158,40 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
$folder['acl'],
$user
);
}, $folders)));

if (isset($nestedConflicts[$folderId])) {
foreach ($nestedConflicts[$folderId] as $conflictPath) {
$this->resolveConflictInStorage($mount->getStorage(), $conflictPath);
}
}

$mounts[$folderId] = $mount;
}

return array_values(array_filter($mounts));
}

/**
* Find a new non-existing name for $path and rename it
*
* only works for folders
*
* @param IStorage $storage
* @param string $path
* @return void
*/
private function resolveConflictInStorage(IStorage $storage, string $path): void {
$cache = $storage->getCache();
$i = 1;
$folderName = $path . ' (' . $i++ . ')';

while ($cache->inCache($folderName)) {
$folderName = $path . ' (' . $i++ . ')';
}

$storage->rename($path, $folderName);
$cache->move($path, $folderName);
$storage->getPropagator()->propagateChange($folderName, time());
}

private function getCurrentUID(): ?string {
Expand All @@ -180,7 +212,16 @@ private function getCurrentUID(): ?string {
return $user ? $user->getUID() : null;
}

public function getMount(int $id, string $mountPoint, int $permissions, int $quota, ?ICacheEntry $cacheEntry = null, IStorageFactory $loader = null, bool $acl = false, IUser $user = null): ?IMountPoint {
public function getMount(
int $id,
string $mountPoint,
int $permissions,
int $quota,
?ICacheEntry $cacheEntry = null,
IStorageFactory $loader = null,
bool $acl = false,
IUser $user = null
): ?IMountPoint {
if (!$cacheEntry) {
// trigger folder creation
$folder = $this->getFolder($id);
Expand All @@ -201,15 +242,15 @@ public function getMount(int $id, string $mountPoint, int $permissions, int $quo
$storage = new ACLStorageWrapper([
'storage' => $storage,
'acl_manager' => $aclManager,
'in_share' => $inShare
'in_share' => $inShare,
]);
$aclRootPermissions = $aclManager->getACLPermissionsForPath($rootPath);
$cacheEntry['permissions'] &= $aclRootPermissions;
}

$baseStorage = new Jail([
'storage' => $storage,
'root' => $rootPath
'root' => $rootPath,
]);
if ($this->enableEncryption) {
$quotaStorage = new GroupFolderStorage([
Expand All @@ -232,7 +273,7 @@ public function getMount(int $id, string $mountPoint, int $permissions, int $quo
}
$maskedStore = new PermissionsMask([
'storage' => $quotaStorage,
'mask' => $permissions
'mask' => $permissions,
]);

if (!$this->allowRootShare) {
Expand Down Expand Up @@ -279,7 +320,7 @@ public function getFolder(int $id, bool $create = true): ?Node {
* @param string[] $mountPoints
* @return string[] An array of paths.
*/
private function findConflictsForUser(IUser $user, array $mountPoints): array {
private function findConflictsInHome(IUser $user, array $mountPoints): array {
$userHome = $this->mountProviderCollection->getHomeMountForUser($user);

$pathHashes = array_map('md5', $mountPoints);
Expand All @@ -300,4 +341,57 @@ private function findConflictsForUser(IUser $user, array $mountPoints): array {
return substr($path, 6); // strip leading "files/"
}, $paths);
}

/**
* @param list<array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?ICacheEntry}> $folders
* @return array<int, list<string>>
*/
private function findNestedConflicts(int $rootStorageId, array $folders): array {
$conflictsByFolder = [];

// for every folder, create a list of nested folders
$mountPoints = [];
$pathsToCheck = [];
$folderIds = [];
foreach ($folders as $folder) {
$mountPoint = $folder['mount_point'];
$folderId = $folder['folder_id'];
$folderIds[$mountPoint] = $folderId;

// because we sorted the folder by mountpoint, parent folders will always occur before children
foreach ($mountPoints as $possibleParent) {
if (str_starts_with($mountPoint, $possibleParent)) {
$parentFolderId = $folderIds[$possibleParent];
$relativeMountPoint = substr($mountPoint, strlen($possibleParent) + 1);
$pathsToCheck[] = "__groupfolders/$parentFolderId/$relativeMountPoint";
}
}
$conflictsByFolder[$folderId] = [];
$mountPoints[] = $mountPoint;
}

if (!$pathsToCheck) {
return [];
}

$pathHashes = array_map('md5', $pathsToCheck);

$query = $this->connection->getQueryBuilder();
$query->select('path')
->from('filecache')
->where($query->expr()->eq('storage', $query->createNamedParameter($rootStorageId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->in('path_hash', $query->createParameter('chunk')));

$paths = [];
foreach (array_chunk($pathHashes, 1000) as $chunk) {
$query->setParameter('chunk', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
foreach ($query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN) as $conflictPath) {
[, $folderId, $relativePath] = explode('/', $conflictPath, 3);
$conflictsByFolder[(int)$folderId][] = $relativePath;
}
$paths = array_merge($paths, );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't seem right. otoh $paths isn't read anyway so it doesn't do any harm. but the variable can probably go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

return $conflictsByFolder;
}
}