$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);
}
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[])[] */
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', [
$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;
+ }
+ }
+
}