|
27 | 27 |
|
28 | 28 | use OCP\Files\File; |
29 | 29 | use OCP\Files\FileInfo; |
| 30 | +use OCP\Files\IRootFolder; |
| 31 | +use OCP\Files\Lock\ILock; |
| 32 | +use OCP\Files\Lock\ILockManager; |
| 33 | +use OCP\Files\Lock\LockContext; |
30 | 34 | use OCP\Files\Storage\IStorage; |
31 | 35 | use OCP\IUser; |
| 36 | +use OCP\Lock\ManuallyLockedException; |
32 | 37 |
|
33 | 38 | class VersionManager implements IVersionManager, INameableVersionBackend, IDeletableVersionBackend { |
34 | 39 | /** @var (IVersionBackend[])[] */ |
@@ -94,7 +99,7 @@ public function createVersion(IUser $user, FileInfo $file) { |
94 | 99 |
|
95 | 100 | public function rollback(IVersion $version) { |
96 | 101 | $backend = $version->getBackend(); |
97 | | - $result = $backend->rollback($version); |
| 102 | + $result = self::handleAppLocks(fn(): ?boolean => $backend->rollback($version)); |
98 | 103 | // rollback doesn't have a return type yet and some implementations don't return anything |
99 | 104 | if ($result === null || $result === true) { |
100 | 105 | \OC_Hook::emit('\OCP\Versions', 'rollback', [ |
@@ -133,4 +138,45 @@ public function deleteVersion(IVersion $version): void { |
133 | 138 | $backend->deleteVersion($version); |
134 | 139 | } |
135 | 140 | } |
| 141 | + |
| 142 | + /** |
| 143 | + * Catch ManuallyLockedException and retry in app context if possible. |
| 144 | + * |
| 145 | + * The files_lock app may throw ManuallyLockedExceptions |
| 146 | + * when attempting to revert a file that is currently opened. |
| 147 | + * This would prevent the user from rolling back a opened file. |
| 148 | + * |
| 149 | + * Text and Richdocuments handle changes of the file while editing. |
| 150 | + * Allow reverting files even when they are locked by these apps |
| 151 | + * and let the apps handle the conflict. |
| 152 | + * |
| 153 | + * @param callable $callback function to run with app locks handled |
| 154 | + * @return boolean|null |
| 155 | + * @throws ManuallyLockedException |
| 156 | + * |
| 157 | + */ |
| 158 | + private static function handleAppLocks(callable $callback): ?boolean { |
| 159 | + try { |
| 160 | + return $callback(); |
| 161 | + } catch (ManuallyLockedException $e) { |
| 162 | + $owner = (string) $e->getOwner(); |
| 163 | + $appsThatHandleUpdates = array("text", "richdocuments"); |
| 164 | + if (!in_array($owner, $appsThatHandleUpdates)) { |
| 165 | + throw $e; |
| 166 | + } |
| 167 | + // The LockWrapper in the files_lock app only compares the lock type and owner |
| 168 | + // when checking the lock against the current scope. |
| 169 | + // So we do not need to get the actual node here |
| 170 | + // and use the root node instead. |
| 171 | + $root = \OC::$server->get(IRootFolder::class); |
| 172 | + $lockContext = new LockContext($root, ILock::TYPE_APP, $owner()); |
| 173 | + $lockManager = \OC::$server->get(ILockManager::class); |
| 174 | + $result = null; |
| 175 | + $lockManager->runInScope($lockContext, function() use ($callback, &$result) { |
| 176 | + $result = $callback(); |
| 177 | + }); |
| 178 | + return $result; |
| 179 | + } |
| 180 | + } |
| 181 | + |
136 | 182 | } |
0 commit comments