Skip to content

Commit

Permalink
Merge pull request #38044 from nextcloud/backport/37787/stable25
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusknorr authored May 4, 2023
2 parents 39a8d7a + 7f2de58 commit f47dcbc
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 16 deletions.
32 changes: 17 additions & 15 deletions apps/files_versions/lib/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -436,24 +436,26 @@ private static function copyFileContents($view, $path1, $path2) {
$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);

// TODO add a proper way of overwriting a file while maintaining file ids
if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
$source = $storage1->fopen($internalPath1, 'r');
$target = $storage2->fopen($internalPath2, 'w');
[, $result] = \OC_Helper::streamCopy($source, $target);
fclose($source);
fclose($target);

if ($result !== false) {
$storage1->unlink($internalPath1);
try {
// TODO add a proper way of overwriting a file while maintaining file ids
if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
$source = $storage1->fopen($internalPath1, 'r');
$target = $storage2->fopen($internalPath2, 'w');
[, $result] = \OC_Helper::streamCopy($source, $target);
fclose($source);
fclose($target);

if ($result !== false) {
$storage1->unlink($internalPath1);
}
} else {
$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
}
} else {
$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
} finally {
$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
}

$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);

return ($result !== false);
}

Expand Down
51 changes: 50 additions & 1 deletion apps/files_versions/lib/Versions/VersionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@

use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Lock\ILock;
use OCP\Files\Lock\ILockManager;
use OCP\Files\Lock\LockContext;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
use OCP\Lock\ManuallyLockedException;

class VersionManager implements IVersionManager {
/** @var (IVersionBackend[])[] */
Expand Down Expand Up @@ -94,7 +99,7 @@ public function createVersion(IUser $user, FileInfo $file) {

public function rollback(IVersion $version) {
$backend = $version->getBackend();
return $backend->rollback($version);
return self::handleAppLocks(fn(): ?bool => $backend->rollback($version));
}

public function read(IVersion $version) {
Expand All @@ -110,4 +115,48 @@ public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): Fi
public function useBackendForStorage(IStorage $storage): bool {
return false;
}

/**
* Catch ManuallyLockedException and retry in app context if possible.
*
* Allow users to go back to old versions via the versions tab in the sidebar
* even when the file is opened in the viewer next to it.
*
* Context: If a file is currently opened for editing
* the files_lock app will throw ManuallyLockedExceptions.
* This prevented the user from rolling an opened file back to a previous version.
*
* Text and Richdocuments can handle changes of open files.
* So we execute the rollback under their lock context
* to let them handle the conflict.
*
* @param callable $callback function to run with app locks handled
* @return bool|null
* @throws ManuallyLockedException
*
*/
private static function handleAppLocks(callable $callback): ?bool {
try {
return $callback();
} catch (ManuallyLockedException $e) {
$owner = (string) $e->getOwner();
$appsThatHandleUpdates = array("text", "richdocuments");
if (!in_array($owner, $appsThatHandleUpdates)) {
throw $e;
}
// The LockWrapper in the files_lock app only compares the lock type and owner
// when checking the lock against the current scope.
// So we do not need to get the actual node here
// and use the root node instead.
$root = \OC::$server->get(IRootFolder::class);
$lockContext = new LockContext($root, ILock::TYPE_APP, $owner);
$lockManager = \OC::$server->get(ILockManager::class);
$result = null;
$lockManager->runInScope($lockContext, function() use ($callback, &$result) {
$result = $callback();
});
return $result;
}
}

}

0 comments on commit f47dcbc

Please sign in to comment.