Signed-off-by: Louis Chemineau <louis@chmn.me>tags/v28.0.0rc2
@@ -59,6 +59,7 @@ return array( | |||
'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php', | |||
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php', | |||
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php', | |||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php', | |||
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php', | |||
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', |
@@ -74,6 +74,7 @@ class ComposerStaticInitFiles | |||
'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', | |||
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php', | |||
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php', | |||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php', | |||
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php', | |||
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', |
@@ -3,7 +3,7 @@ | |||
'name' => '__root__', | |||
'pretty_version' => 'dev-master', | |||
'version' => 'dev-master', | |||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||
'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8', | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), | |||
@@ -13,7 +13,7 @@ | |||
'__root__' => array( | |||
'pretty_version' => 'dev-master', | |||
'version' => 'dev-master', | |||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||
'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8', | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), |
@@ -43,11 +43,13 @@ use OCA\Files\DirectEditingCapabilities; | |||
use OCA\Files\Event\LoadSidebar; | |||
use OCA\Files\Listener\LoadSidebarListener; | |||
use OCA\Files\Listener\RenderReferenceEventListener; | |||
use OCA\Files\Listener\SyncLivePhotosListener; | |||
use OCA\Files\Notification\Notifier; | |||
use OCA\Files\Search\FilesSearchProvider; | |||
use OCA\Files\Service\TagService; | |||
use OCA\Files\Service\UserConfig; | |||
use OCA\Files\Service\ViewConfig; | |||
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; | |||
use OCP\Activity\IManager as IActivityManager; | |||
use OCP\AppFramework\App; | |||
use OCP\AppFramework\Bootstrap\IBootContext; | |||
@@ -56,6 +58,9 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; | |||
use OCP\Collaboration\Reference\RenderReferenceEvent; | |||
use OCP\Collaboration\Resources\IProviderManager; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\Files\Cache\CacheEntryRemovedEvent; | |||
use OCP\Files\Events\Node\BeforeNodeDeletedEvent; | |||
use OCP\Files\Events\Node\BeforeNodeRenamedEvent; | |||
use OCP\IConfig; | |||
use OCP\IPreview; | |||
use OCP\IRequest; | |||
@@ -120,6 +125,10 @@ class Application extends App implements IBootstrap { | |||
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); | |||
$context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); | |||
$context->registerEventListener(BeforeNodeRenamedEvent::class, SyncLivePhotosListener::class); | |||
$context->registerEventListener(BeforeNodeDeletedEvent::class, SyncLivePhotosListener::class); | |||
$context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class); | |||
$context->registerEventListener(CacheEntryRemovedEvent::class, SyncLivePhotosListener::class); | |||
$context->registerSearchProvider(FilesSearchProvider::class); | |||
@@ -0,0 +1,252 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me> | |||
* | |||
* @author Louis Chemineau <louis@chmn.me> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCA\Files\Listener; | |||
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; | |||
use OCA\Files_Trashbin\Trash\ITrashItem; | |||
use OCA\Files_Trashbin\Trash\ITrashManager; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\EventDispatcher\IEventListener; | |||
use OCP\Files\Cache\CacheEntryRemovedEvent; | |||
use OCP\Files\Events\Node\BeforeNodeDeletedEvent; | |||
use OCP\Files\Events\Node\BeforeNodeRenamedEvent; | |||
use OCP\Files\Folder; | |||
use OCP\Files\Node; | |||
use OCP\Files\NotFoundException; | |||
use OCP\Files\NotPermittedException; | |||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; | |||
use OCP\FilesMetadata\IFilesMetadataManager; | |||
use OCP\IUserSession; | |||
/** | |||
* @template-implements IEventListener<Event> | |||
*/ | |||
class SyncLivePhotosListener implements IEventListener { | |||
/** @var Array<int, string> */ | |||
private $pendingRenames = []; | |||
/** @var Array<int, bool> */ | |||
private $pendingDeletion = []; | |||
/** @var Array<int, bool> */ | |||
private $pendingRestores = []; | |||
public function __construct( | |||
private ?Folder $userFolder, | |||
private ?IUserSession $userSession, | |||
private ITrashManager $trashManager, | |||
private IFilesMetadataManager $filesMetadataManager, | |||
) { | |||
} | |||
public function handle(Event $event): void { | |||
if ($this->userFolder === null || $this->userSession === null) { | |||
return; | |||
} | |||
$peerFile = null; | |||
if ($event instanceof BeforeNodeRenamedEvent) { | |||
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId()); | |||
} elseif ($event instanceof BeforeNodeRestoredEvent) { | |||
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId()); | |||
} elseif ($event instanceof BeforeNodeDeletedEvent) { | |||
$peerFile = $this->getLivePhotoPeer($event->getNode()->getId()); | |||
} elseif ($event instanceof CacheEntryRemovedEvent) { | |||
$peerFile = $this->getLivePhotoPeer($event->getFileId()); | |||
} else { | |||
return; | |||
} | |||
if ($peerFile === null) { | |||
return; | |||
} | |||
if ($event instanceof BeforeNodeRenamedEvent) { | |||
$sourceFile = $event->getSource(); | |||
$targetFile = $event->getTarget(); | |||
$targetParent = $targetFile->getParent(); | |||
$sourceExtension = $sourceFile->getExtension(); | |||
$peerFileExtension = $peerFile->getExtension(); | |||
$targetName = $targetFile->getName(); | |||
$targetPath = $targetFile->getPath(); | |||
// Prevent rename of the .mov file if the peer file do not have the same path. | |||
if ($sourceFile->getMimetype() === 'video/quicktime') { | |||
$peerFilePath = $this->pendingRenames[$peerFile->getId()] ?? $peerFile->getPath(); | |||
$targetPathWithoutExtension = preg_replace("/\.$sourceExtension$/", '', $targetPath); | |||
$peerFilePathWithoutExtension = preg_replace("/\.$peerFileExtension$/", '', $peerFilePath); | |||
if ($targetPathWithoutExtension !== $peerFilePathWithoutExtension) { | |||
$event->abortOperation(new NotPermittedException("The video part of a live photo need to have the same name as the image")); | |||
} | |||
unset($this->pendingRenames[$peerFile->getId()]); | |||
return; | |||
} | |||
if (!str_ends_with($targetName, ".".$sourceExtension)) { | |||
$event->abortOperation(new NotPermittedException("Cannot change the extension of a live photo")); | |||
} | |||
try { | |||
$targetParent->get($targetName); | |||
$event->abortOperation(new NotPermittedException("A file already exist at destination path")); | |||
} catch (NotFoundException $ex) { | |||
} | |||
try { | |||
$peerTargetName = preg_replace("/\.$sourceExtension$/", '.mov', $targetName); | |||
$targetParent->get($peerTargetName); | |||
$event->abortOperation(new NotPermittedException("A file already exist at destination path")); | |||
} catch (NotFoundException $ex) { | |||
} | |||
$peerTargetPath = preg_replace("/\.$sourceExtension$/", '.mov', $targetPath); | |||
$this->pendingRenames[$sourceFile->getId()] = $targetPath; | |||
try { | |||
$peerFile->move($peerTargetPath); | |||
} catch (\Throwable $ex) { | |||
$event->abortOperation($ex); | |||
} | |||
return; | |||
} | |||
if ($event instanceof BeforeNodeDeletedEvent) { | |||
$deletedFile = $event->getNode(); | |||
if ($deletedFile->getMimetype() === 'video/quicktime') { | |||
if (isset($this->pendingDeletion[$peerFile->getId()])) { | |||
unset($this->pendingDeletion[$peerFile->getId()]); | |||
return; | |||
} else { | |||
$event->abortOperation(new NotPermittedException("Cannot delete the video part of a live photo")); | |||
} | |||
} else { | |||
$this->pendingDeletion[$deletedFile->getId()] = true; | |||
try { | |||
$peerFile->delete(); | |||
} catch (\Throwable $ex) { | |||
$event->abortOperation($ex); | |||
} | |||
} | |||
return; | |||
} | |||
if ($event instanceof CacheEntryRemovedEvent) { | |||
$peerFile->delete(); | |||
} | |||
if ($event instanceof BeforeNodeRestoredEvent) { | |||
$sourceFile = $event->getSource(); | |||
if ($sourceFile->getMimetype() === 'video/quicktime') { | |||
if (isset($this->pendingRestores[$peerFile->getId()])) { | |||
unset($this->pendingRestores[$peerFile->getId()]); | |||
return; | |||
} else { | |||
$event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo")); | |||
} | |||
} else { | |||
$user = $this->userSession->getUser(); | |||
if ($user === null) { | |||
return; | |||
} | |||
$peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId()); | |||
// Peer file in not in the bin, no need to restore it. | |||
if ($peerTrashItem === null) { | |||
return; | |||
} | |||
$trashRoot = $this->trashManager->listTrashRoot($user); | |||
$trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath()); | |||
if ($trashItem === null) { | |||
$event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin")); | |||
} | |||
$this->pendingRestores[$sourceFile->getId()] = true; | |||
try { | |||
$this->trashManager->restoreItem($trashItem); | |||
} catch (\Throwable $ex) { | |||
$event->abortOperation($ex); | |||
} | |||
} | |||
} | |||
} | |||
private function getLivePhotoPeer(int $nodeId): ?Node { | |||
if ($this->userFolder === null || $this->userSession === null) { | |||
return null; | |||
} | |||
try { | |||
$metadata = $this->filesMetadataManager->getMetadata($nodeId); | |||
} catch (FilesMetadataNotFoundException $ex) { | |||
return null; | |||
} | |||
if (!$metadata->hasKey('files-live-photo')) { | |||
return null; | |||
} | |||
$peerFileId = (int)$metadata->getString('files-live-photo'); | |||
// Check the user's folder. | |||
$nodes = $this->userFolder->getById($peerFileId); | |||
if (count($nodes) !== 0) { | |||
return $nodes[0]; | |||
} | |||
// Check the user's trashbin. | |||
$user = $this->userSession->getUser(); | |||
if ($user !== null) { | |||
$peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId); | |||
if ($peerFile !== null) { | |||
return $peerFile; | |||
} | |||
} | |||
$metadata->unset('files-live-photo'); | |||
return null; | |||
} | |||
private function getTrashItem(array $trashFolder, string $path): ?ITrashItem { | |||
foreach($trashFolder as $trashItem) { | |||
if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) { | |||
if ($path === "files_trashbin/files".$trashItem->getTrashPath()) { | |||
return $trashItem; | |||
} | |||
if ($trashItem instanceof Folder) { | |||
$node = $this->getTrashItem($trashItem->getDirectoryListing(), $path); | |||
if ($node !== null) { | |||
return $node; | |||
} | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -16,7 +16,9 @@ return array( | |||
'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => $baseDir . '/../lib/Command/RestoreAllFiles.php', | |||
'OCA\\Files_Trashbin\\Command\\Size' => $baseDir . '/../lib/Command/Size.php', | |||
'OCA\\Files_Trashbin\\Controller\\PreviewController' => $baseDir . '/../lib/Controller/PreviewController.php', | |||
'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => $baseDir . '/../lib/Events/BeforeNodeRestoredEvent.php', | |||
'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => $baseDir . '/../lib/Events/MoveToTrashEvent.php', | |||
'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => $baseDir . '/../lib/Events/NodeRestoredEvent.php', | |||
'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => $baseDir . '/../lib/Exceptions/CopyRecursiveException.php', | |||
'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php', | |||
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php', |
@@ -31,7 +31,9 @@ class ComposerStaticInitFiles_Trashbin | |||
'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => __DIR__ . '/..' . '/../lib/Command/RestoreAllFiles.php', | |||
'OCA\\Files_Trashbin\\Command\\Size' => __DIR__ . '/..' . '/../lib/Command/Size.php', | |||
'OCA\\Files_Trashbin\\Controller\\PreviewController' => __DIR__ . '/..' . '/../lib/Controller/PreviewController.php', | |||
'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/BeforeNodeRestoredEvent.php', | |||
'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => __DIR__ . '/..' . '/../lib/Events/MoveToTrashEvent.php', | |||
'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/NodeRestoredEvent.php', | |||
'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => __DIR__ . '/..' . '/../lib/Exceptions/CopyRecursiveException.php', | |||
'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', | |||
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', |
@@ -0,0 +1,52 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me> | |||
* | |||
* @author Louis Chemineau <louis@chmn.me> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\Files_Trashbin\Events; | |||
use Exception; | |||
use OCP\Files\Events\Node\AbstractNodesEvent; | |||
use OCP\Files\Node; | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
class BeforeNodeRestoredEvent extends AbstractNodesEvent { | |||
public function __construct(Node $source, Node $target, private bool &$run) { | |||
parent::__construct($source, $target); | |||
} | |||
/** | |||
* @return never | |||
*/ | |||
public function abortOperation(\Throwable $ex = null) { | |||
$this->stopPropagation(); | |||
$this->run = false; | |||
if ($ex !== null) { | |||
throw $ex; | |||
} else { | |||
throw new Exception('Operation aborted'); | |||
} | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me> | |||
* | |||
* @author Louis Chemineau <louis@chmn.me> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\Files_Trashbin\Events; | |||
use OCP\Files\Events\Node\AbstractNodesEvent; | |||
use OCP\Files\Node; | |||
/** | |||
* @since 28.0.0 | |||
*/ | |||
class NodeRestoredEvent extends AbstractNodesEvent { | |||
public function __construct(Node $source, Node $target) { | |||
parent::__construct($source, $target); | |||
} | |||
} |
@@ -44,19 +44,27 @@ | |||
*/ | |||
namespace OCA\Files_Trashbin; | |||
use Exception; | |||
use OC\Files\Cache\Cache; | |||
use OC\Files\Cache\CacheEntry; | |||
use OC\Files\Cache\CacheQueryBuilder; | |||
use OC\Files\Filesystem; | |||
use OC\Files\Node\File; | |||
use OC\Files\Node\Folder; | |||
use OC\Files\Node\NonExistingFile; | |||
use OC\Files\Node\NonExistingFolder; | |||
use OC\Files\ObjectStore\ObjectStoreStorage; | |||
use OC\Files\View; | |||
use OC_User; | |||
use OCA\Files_Trashbin\AppInfo\Application; | |||
use OCA\Files_Trashbin\Command\Expire; | |||
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; | |||
use OCA\Files_Trashbin\Events\NodeRestoredEvent; | |||
use OCP\App\IAppManager; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\Files\File; | |||
use OCP\Files\Folder; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\Files\IRootFolder; | |||
use OCP\Files\Node; | |||
use OCP\Files\NotFoundException; | |||
use OCP\Files\NotPermittedException; | |||
use OCP\FilesMetadata\IFilesMetadataManager; | |||
@@ -508,6 +516,21 @@ class Trashbin { | |||
if (!$view->isCreatable(dirname($target))) { | |||
throw new NotPermittedException("Can't restore trash item because the target folder is not writable"); | |||
} | |||
$sourcePath = Filesystem::normalizePath($file); | |||
$targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); | |||
$sourceNode = self::getNodeForPath($sourcePath); | |||
$targetNode = self::getNodeForPath($targetPath); | |||
$run = true; | |||
$event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run); | |||
$dispatcher = \OC::$server->get(IEventDispatcher::class); | |||
$dispatcher->dispatchTyped($event); | |||
if (!$run) { | |||
return false; | |||
} | |||
$restoreResult = $view->rename($source, $target); | |||
// handle the restore result | |||
@@ -516,8 +539,13 @@ class Trashbin { | |||
$view->chroot('/' . $user . '/files'); | |||
$view->touch('/' . $location . '/' . $uniqueFilename, $mtime); | |||
$view->chroot($fakeRoot); | |||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename), | |||
'trashPath' => Filesystem::normalizePath($file)]); | |||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]); | |||
$sourceNode = self::getNodeForPath($sourcePath); | |||
$targetNode = self::getNodeForPath($targetPath); | |||
$event = new NodeRestoredEvent($sourceNode, $targetNode); | |||
$dispatcher = \OC::$server->get(IEventDispatcher::class); | |||
$dispatcher->dispatchTyped($event); | |||
self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp); | |||
@@ -1151,4 +1179,33 @@ class Trashbin { | |||
} | |||
return $trashFilename; | |||
} | |||
private static function getNodeForPath(string $path): Node { | |||
$user = OC_User::getUser(); | |||
$rootFolder = \OC::$server->get(IRootFolder::class); | |||
if ($user !== false) { | |||
$userFolder = $rootFolder->getUserFolder($user); | |||
/** @var Folder */ | |||
$trashFolder = $userFolder->getParent()->get('files_trashbin/files'); | |||
try { | |||
return $trashFolder->get($path); | |||
} catch (NotFoundException $ex) { | |||
} | |||
} | |||
$view = \OC::$server->get(View::class); | |||
$fsView = Filesystem::getView(); | |||
if ($fsView === null) { | |||
throw new Exception('View should not be null'); | |||
} | |||
$fullPath = $fsView->getAbsolutePath($path); | |||
if (Filesystem::is_dir($path)) { | |||
return new NonExistingFolder($rootFolder, $view, $fullPath); | |||
} else { | |||
return new NonExistingFile($rootFolder, $view, $fullPath); | |||
} | |||
} | |||
} |
@@ -133,7 +133,7 @@ class HookConnector { | |||
$this->root->emit('\OC\Files', 'preDelete', [$node]); | |||
$this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node)); | |||
$event = new BeforeNodeDeletedEvent($node); | |||
$event = new BeforeNodeDeletedEvent($node, $arguments['run']); | |||
$this->dispatcher->dispatchTyped($event); | |||
} | |||
@@ -171,7 +171,7 @@ class HookConnector { | |||
$this->root->emit('\OC\Files', 'preRename', [$source, $target]); | |||
$this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target])); | |||
$event = new BeforeNodeRenamedEvent($source, $target); | |||
$event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']); | |||
$this->dispatcher->dispatchTyped($event); | |||
} | |||
@@ -25,8 +25,31 @@ declare(strict_types=1); | |||
*/ | |||
namespace OCP\Files\Events\Node; | |||
use Exception; | |||
use OCP\Files\Node; | |||
/** | |||
* @since 20.0.0 | |||
*/ | |||
class BeforeNodeDeletedEvent extends AbstractNodeEvent { | |||
/** | |||
* @since 20.0.0 | |||
*/ | |||
public function __construct(Node $node, private bool &$run) { | |||
parent::__construct($node); | |||
} | |||
/** | |||
* @since 28.0.0 | |||
* @return never | |||
*/ | |||
public function abortOperation(\Throwable $ex = null) { | |||
$this->stopPropagation(); | |||
$this->run = false; | |||
if ($ex !== null) { | |||
throw $ex; | |||
} else { | |||
throw new Exception('Operation aborted'); | |||
} | |||
} | |||
} |
@@ -25,8 +25,31 @@ declare(strict_types=1); | |||
*/ | |||
namespace OCP\Files\Events\Node; | |||
use Exception; | |||
use OCP\Files\Node; | |||
/** | |||
* @since 20.0.0 | |||
*/ | |||
class BeforeNodeRenamedEvent extends AbstractNodesEvent { | |||
/** | |||
* @since 20.0.0 | |||
*/ | |||
public function __construct(Node $source, Node $target, private bool &$run) { | |||
parent::__construct($source, $target); | |||
} | |||
/** | |||
* @since 28.0.0 | |||
* @return never | |||
*/ | |||
public function abortOperation(\Throwable $ex = null) { | |||
$this->stopPropagation(); | |||
$this->run = false; | |||
if ($ex !== null) { | |||
throw $ex; | |||
} else { | |||
throw new Exception('Operation aborted'); | |||
} | |||
} | |||
} |