@ -27,8 +27,13 @@ namespace OCA\Files_Versions\Versions;
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, INameableVersionBackend, IDeletableVersionBackend {
/** @var (IVersionBackend[])[] */
@ -94,7 +99,7 @@ class VersionManager implements IVersionManager, INameableVersionBackend, IDelet
public function rollback(IVersion $version) {
$backend = $version->getBackend();
$result = $backend->rollback($version);
$result = self::handleAppLocks(fn(): ?bool => $backend->rollback($version) );
// rollback doesn't have a return type yet and some implementations don't return anything
if ($result === null || $result === true) {
\OC_Hook::emit('\OCP\Versions', 'rollback', [
@ -133,4 +138,48 @@ class VersionManager implements IVersionManager, INameableVersionBackend, IDelet
$backend->deleteVersion($version);
}
}
/**
* 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;
}
}
}