mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
Merge pull request #44069 from nextcloud/artonge/fix/split_live_photo_listener
Split live photo listener to extract trashbin specific code into its own listener
This commit is contained in:
commit
dd26fb2ba4
22 changed files with 533 additions and 175 deletions
|
|
@ -68,6 +68,7 @@ return array(
|
|||
'OCA\\Files\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
|
||||
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
|
||||
'OCA\\Files\\Service\\LivePhotosService' => $baseDir . '/../lib/Service/LivePhotosService.php',
|
||||
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
|
||||
'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php',
|
||||
'OCA\\Files\\Service\\UserConfig' => $baseDir . '/../lib/Service/UserConfig.php',
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class ComposerStaticInitFiles
|
|||
'OCA\\Files\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
|
||||
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
|
||||
'OCA\\Files\\Service\\LivePhotosService' => __DIR__ . '/..' . '/../lib/Service/LivePhotosService.php',
|
||||
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
|
||||
'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php',
|
||||
'OCA\\Files\\Service\\UserConfig' => __DIR__ . '/..' . '/../lib/Service/UserConfig.php',
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ 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;
|
||||
|
|
@ -131,11 +130,10 @@ class Application extends App implements IBootstrap {
|
|||
$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->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
|
||||
$context->registerEventListener(BeforeNodeCopiedEvent::class, SyncLivePhotosListener::class);
|
||||
$context->registerEventListener(NodeCopiedEvent::class, SyncLivePhotosListener::class);
|
||||
$context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
|
||||
|
||||
$context->registerSearchProvider(FilesSearchProvider::class);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Files\Listener;
|
||||
|
||||
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
|
||||
use OCA\Files_Trashbin\Trash\ITrashItem;
|
||||
use OCA\Files_Trashbin\Trash\ITrashManager;
|
||||
use OCA\Files\Service\LivePhotosService;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Exceptions\AbortedEventException;
|
||||
|
|
@ -39,10 +37,7 @@ use OCP\Files\Events\Node\NodeCopiedEvent;
|
|||
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>
|
||||
|
|
@ -52,37 +47,40 @@ class SyncLivePhotosListener implements IEventListener {
|
|||
private array $pendingRenames = [];
|
||||
/** @var Array<int, bool> */
|
||||
private array $pendingDeletion = [];
|
||||
/** @var Array<int, bool> */
|
||||
private array $pendingRestores = [];
|
||||
|
||||
public function __construct(
|
||||
private ?Folder $userFolder,
|
||||
private ?IUserSession $userSession,
|
||||
private ITrashManager $trashManager,
|
||||
private IFilesMetadataManager $filesMetadataManager,
|
||||
private LivePhotosService $livePhotosService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if ($this->userFolder === null || $this->userSession === null) {
|
||||
if ($this->userFolder === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$peerFile = null;
|
||||
$peerFileId = null;
|
||||
|
||||
if ($event instanceof BeforeNodeRenamedEvent) {
|
||||
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
|
||||
} elseif ($event instanceof BeforeNodeRestoredEvent) {
|
||||
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
|
||||
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
|
||||
} elseif ($event instanceof BeforeNodeDeletedEvent) {
|
||||
$peerFile = $this->getLivePhotoPeer($event->getNode()->getId());
|
||||
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getNode()->getId());
|
||||
} elseif ($event instanceof CacheEntryRemovedEvent) {
|
||||
$peerFile = $this->getLivePhotoPeer($event->getFileId());
|
||||
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getFileId());
|
||||
} elseif ($event instanceof BeforeNodeCopiedEvent || $event instanceof NodeCopiedEvent) {
|
||||
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
|
||||
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
|
||||
}
|
||||
|
||||
if ($peerFileId === null) {
|
||||
return; // Not a live photo.
|
||||
}
|
||||
|
||||
// Check the user's folder.
|
||||
$peerFile = $this->userFolder->getFirstNodeById($peerFileId);
|
||||
|
||||
if ($peerFile === null) {
|
||||
return; // not a Live Photo
|
||||
return; // Peer file not found.
|
||||
}
|
||||
|
||||
if ($event instanceof BeforeNodeRenamedEvent) {
|
||||
|
|
@ -91,8 +89,6 @@ class SyncLivePhotosListener implements IEventListener {
|
|||
$this->handleDeletion($event, $peerFile);
|
||||
} elseif ($event instanceof CacheEntryRemovedEvent) {
|
||||
$peerFile->delete();
|
||||
} elseif ($event instanceof BeforeNodeRestoredEvent) {
|
||||
$this->handleRestore($event, $peerFile);
|
||||
} elseif ($event instanceof BeforeNodeCopiedEvent) {
|
||||
$this->handleMove($event, $peerFile, true);
|
||||
} elseif ($event instanceof NodeCopiedEvent) {
|
||||
|
|
@ -208,114 +204,4 @@ class SyncLivePhotosListener implements IEventListener {
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* During restore event, we trigger another recursive restore on the peer file.
|
||||
* Restore operations on the .mov file directly are currently blocked.
|
||||
* The event listener being singleton, we can store the current state
|
||||
* of pending restores inside the 'pendingRestores' property,
|
||||
* to prevent infinite recursivity.
|
||||
*/
|
||||
private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
|
||||
$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 is 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the associated live photo file.
|
||||
* We first look for it in the user folder, and if we
|
||||
* cannot find it here, we look for it in the user's trashbin.
|
||||
*/
|
||||
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.
|
||||
$node = $this->userFolder->getFirstNodeById($peerFileId);
|
||||
if ($node) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* There is currently no method to restore a file based on its fileId or path.
|
||||
* So we have to manually find a ITrashItem from the trash item list.
|
||||
* TODO: This should be replaced by a proper method in the TrashManager.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
apps/files/lib/Service/LivePhotosService.php
Normal file
52
apps/files/lib/Service/LivePhotosService.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 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\Service;
|
||||
|
||||
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
|
||||
use OCP\FilesMetadata\IFilesMetadataManager;
|
||||
|
||||
class LivePhotosService {
|
||||
public function __construct(
|
||||
private IFilesMetadataManager $filesMetadataManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated live photo for a given file id
|
||||
*/
|
||||
public function getLivePhotoPeerId(int $fileId): ?int {
|
||||
try {
|
||||
$metadata = $this->filesMetadataManager->getMetadata($fileId);
|
||||
} catch (FilesMetadataNotFoundException $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$metadata->hasKey('files-live-photo')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$metadata->getString('files-live-photo');
|
||||
}
|
||||
}
|
||||
|
|
@ -26,23 +26,28 @@
|
|||
@update:open="onClose">
|
||||
<!-- Settings API-->
|
||||
<NcAppSettingsSection id="settings" :name="t('files', 'Files settings')">
|
||||
<NcCheckboxRadioSwitch :checked="userConfig.sort_favorites_first"
|
||||
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_favorites_first"
|
||||
:checked="userConfig.sort_favorites_first"
|
||||
@update:checked="setConfig('sort_favorites_first', $event)">
|
||||
{{ t('files', 'Sort favorites first') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch :checked="userConfig.sort_folders_first"
|
||||
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_folders_first"
|
||||
:checked="userConfig.sort_folders_first"
|
||||
@update:checked="setConfig('sort_folders_first', $event)">
|
||||
{{ t('files', 'Sort folders before files') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch :checked="userConfig.show_hidden"
|
||||
<NcCheckboxRadioSwitch data-cy-files-settings-setting="show_hidden"
|
||||
:checked="userConfig.show_hidden"
|
||||
@update:checked="setConfig('show_hidden', $event)">
|
||||
{{ t('files', 'Show hidden files') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch :checked="userConfig.crop_image_previews"
|
||||
<NcCheckboxRadioSwitch data-cy-files-settings-setting="crop_image_previews"
|
||||
:checked="userConfig.crop_image_previews"
|
||||
@update:checked="setConfig('crop_image_previews', $event)">
|
||||
{{ t('files', 'Crop image previews') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch v-if="enableGridView"
|
||||
data-cy-files-settings-setting="grid_view"
|
||||
:checked="userConfig.grid_view"
|
||||
@update:checked="setConfig('grid_view', $event)">
|
||||
{{ t('files', 'Enable the grid view') }}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<template>
|
||||
<NcAppSidebar v-if="file"
|
||||
ref="sidebar"
|
||||
cy-data-sidebar
|
||||
v-bind="appSidebar"
|
||||
:force-menu="true"
|
||||
@close="close"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ return array(
|
|||
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php',
|
||||
'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php',
|
||||
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => $baseDir . '/../lib/Listeners/LoadAdditionalScripts.php',
|
||||
'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => $baseDir . '/../lib/Listeners/SyncLivePhotosListener.php',
|
||||
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => $baseDir . '/../lib/Migration/Version1010Date20200630192639.php',
|
||||
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php',
|
||||
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => $baseDir . '/../lib/Sabre/AbstractTrashFile.php',
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class ComposerStaticInitFiles_Trashbin
|
|||
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
|
||||
'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
|
||||
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => __DIR__ . '/..' . '/../lib/Listeners/LoadAdditionalScripts.php',
|
||||
'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listeners/SyncLivePhotosListener.php',
|
||||
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => __DIR__ . '/..' . '/../lib/Migration/Version1010Date20200630192639.php',
|
||||
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php',
|
||||
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFile.php',
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ namespace OCA\Files_Trashbin\AppInfo;
|
|||
use OCA\DAV\Connector\Sabre\Principal;
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\Files_Trashbin\Capabilities;
|
||||
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
|
||||
use OCA\Files_Trashbin\Expiration;
|
||||
use OCA\Files_Trashbin\Listeners\LoadAdditionalScripts;
|
||||
use OCA\Files_Trashbin\Listeners\SyncLivePhotosListener;
|
||||
use OCA\Files_Trashbin\Trash\ITrashManager;
|
||||
use OCA\Files_Trashbin\Trash\TrashManager;
|
||||
use OCA\Files_Trashbin\UserMigration\TrashbinMigrator;
|
||||
|
|
@ -62,6 +64,8 @@ class Application extends App implements IBootstrap {
|
|||
LoadAdditionalScriptsEvent::class,
|
||||
LoadAdditionalScripts::class
|
||||
);
|
||||
|
||||
$context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
|
|
|
|||
148
apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
Normal file
148
apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?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\Listeners;
|
||||
|
||||
use OCA\Files\Service\LivePhotosService;
|
||||
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\Folder;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<BeforeNodeRestoredEvent>
|
||||
*/
|
||||
class SyncLivePhotosListener implements IEventListener {
|
||||
/** @var Array<int, bool> */
|
||||
private array $pendingRestores = [];
|
||||
|
||||
public function __construct(
|
||||
private ?IUserSession $userSession,
|
||||
private ITrashManager $trashManager,
|
||||
private LivePhotosService $livePhotosService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if ($this->userSession === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var BeforeNodeRestoredEvent $event */
|
||||
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
|
||||
|
||||
if ($peerFileId === null) {
|
||||
return; // Not a live photo.
|
||||
}
|
||||
|
||||
// Check the user's trashbin.
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId);
|
||||
|
||||
if ($peerFile === null) {
|
||||
return; // Peer file not found.
|
||||
}
|
||||
|
||||
$this->handleRestore($event, $peerFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* During restore event, we trigger another recursive restore on the peer file.
|
||||
* Restore operations on the .mov file directly are currently blocked.
|
||||
* The event listener being singleton, we can store the current state
|
||||
* of pending restores inside the 'pendingRestores' property,
|
||||
* to prevent infinite recursivity.
|
||||
*/
|
||||
private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
|
||||
$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 is 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There is currently no method to restore a file based on its fileId or path.
|
||||
* So we have to manually find a ITrashItem from the trash item list.
|
||||
* TODO: This should be replaced by a proper method in the TrashManager.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,10 @@ export default defineConfig({
|
|||
// faster video processing
|
||||
videoCompression: false,
|
||||
|
||||
// Prevent elements to be scrolled under a top bar during actions (click, clear, type, etc). Default is 'top'.
|
||||
// https://github.com/cypress-io/cypress/issues/871
|
||||
scrollBehavior: 'center',
|
||||
|
||||
// Visual regression testing
|
||||
env: {
|
||||
failSilently: false,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ export const configureNextcloud = async function() {
|
|||
await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true)
|
||||
// Speed up test and make them less flaky. If a cron execution is needed, it can be triggered manually.
|
||||
await runExec(container, ['php', 'occ', 'background:cron'], true)
|
||||
|
||||
console.log('└─ Nextcloud is now ready to use 🎉')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,17 +20,31 @@
|
|||
*
|
||||
*/
|
||||
|
||||
export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
|
||||
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
|
||||
|
||||
export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')
|
||||
export const getActionsForFile = (filename: string) => getRowForFile(filename).find('[data-cy-files-list-row-actions]')
|
||||
|
||||
export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).find('button[aria-label="Actions"]')
|
||||
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).find('button[aria-label="Actions"]')
|
||||
|
||||
export const triggerActionForFileId = (fileid: number, actionId: string) => {
|
||||
getActionButtonForFileId(fileid).click()
|
||||
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
|
||||
}
|
||||
export const triggerActionForFile = (filename: string, actionId: string) => {
|
||||
getActionButtonForFile(filename).click()
|
||||
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
|
||||
}
|
||||
|
||||
export const triggerInlineActionForFileId = (fileid: number, actionId: string) => {
|
||||
getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
|
||||
}
|
||||
export const triggerInlineActionForFile = (filename: string, actionId: string) => {
|
||||
getActionsForFile(filename).get(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
|
||||
}
|
||||
|
||||
export const moveFile = (fileName: string, dirName: string) => {
|
||||
getRowForFile(fileName).should('be.visible')
|
||||
triggerActionForFile(fileName, 'move-copy')
|
||||
|
|
@ -85,6 +99,23 @@ export const copyFile = (fileName: string, dirName: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const renameFile = (fileName: string, newFileName: string) => {
|
||||
getRowForFile(fileName)
|
||||
triggerActionForFile(fileName, 'rename')
|
||||
|
||||
// intercept the move so we can wait for it
|
||||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile')
|
||||
|
||||
getRowForFile(fileName).find('[data-cy-files-list-row-name] input').clear()
|
||||
getRowForFile(fileName).find('[data-cy-files-list-row-name] input').type(`${newFileName}{enter}`)
|
||||
|
||||
cy.wait('@moveFile')
|
||||
}
|
||||
|
||||
export const navigateToFolder = (folderName: string) => {
|
||||
getRowForFile(folderName).should('be.visible').find('[data-cy-files-list-row-name-link]').click()
|
||||
}
|
||||
|
||||
export const closeSidebar = () => {
|
||||
cy.get('[cy-data-sidebar] .app-sidebar__close').click()
|
||||
}
|
||||
|
|
|
|||
215
cypress/e2e/files/live_photos.cy.ts
Normal file
215
cypress/e2e/files/live_photos.cy.ts
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me>
|
||||
*
|
||||
* @author Louis Chmn <louis@chmn.me>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import type { User } from '@nextcloud/cypress'
|
||||
import { closeSidebar, copyFile, getRowForFile, getRowForFileId, renameFile, triggerActionForFile, triggerInlineActionForFileId } from './FilesUtils'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param label
|
||||
*/
|
||||
function refreshView(label: string) {
|
||||
cy.intercept('PROPFIND', /\/remote.php\/dav\//).as('propfind')
|
||||
cy.get('[data-cy-files-content-breadcrumbs]').contains(label).click()
|
||||
cy.wait('@propfind')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user
|
||||
* @param fileName
|
||||
* @param domain
|
||||
* @param requesttoken
|
||||
* @param metadata
|
||||
*/
|
||||
function setMetadata(user: User, fileName: string, domain: string, requesttoken: string, metadata: object) {
|
||||
cy.request({
|
||||
method: 'PROPPATCH',
|
||||
url: `http://${domain}/remote.php/dav/files/${user.userId}/${fileName}`,
|
||||
auth: { user: user.userId, pass: user.password },
|
||||
headers: {
|
||||
requesttoken,
|
||||
},
|
||||
body: `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
})
|
||||
}
|
||||
|
||||
describe('Files: Live photos', { testIsolation: true }, () => {
|
||||
let currentUser: User
|
||||
let randomFileName: string
|
||||
let jpgFileId: number
|
||||
let movFileId: number
|
||||
let hostname: string
|
||||
let requesttoken: string
|
||||
|
||||
before(() => {
|
||||
cy.createRandomUser().then((user) => {
|
||||
currentUser = user
|
||||
cy.login(currentUser)
|
||||
cy.visit('/apps/files')
|
||||
})
|
||||
|
||||
cy.url().then(url => { hostname = new URL(url).hostname })
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10)
|
||||
|
||||
cy.uploadContent(currentUser, new Blob(['jpg file'], { type: 'image/jpg' }), 'image/jpg', `/${randomFileName}.jpg`)
|
||||
.then(response => { jpgFileId = parseInt(response.headers['oc-fileid']) })
|
||||
cy.uploadContent(currentUser, new Blob(['mov file'], { type: 'video/mov' }), 'video/mov', `/${randomFileName}.mov`)
|
||||
.then(response => { movFileId = parseInt(response.headers['oc-fileid']) })
|
||||
|
||||
cy.login(currentUser)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
cy.get('head').invoke('attr', 'data-requesttoken').then(_requesttoken => { requesttoken = _requesttoken as string })
|
||||
|
||||
cy.then(() => {
|
||||
setMetadata(currentUser, `${randomFileName}.jpg`, hostname, requesttoken, { 'nc:metadata-files-live-photo': movFileId })
|
||||
setMetadata(currentUser, `${randomFileName}.mov`, hostname, requesttoken, { 'nc:metadata-files-live-photo': jpgFileId })
|
||||
})
|
||||
|
||||
cy.then(() => {
|
||||
cy.visit(`/apps/files/files/${jpgFileId}`) // Refresh and scroll to the .jpg file.
|
||||
closeSidebar()
|
||||
})
|
||||
})
|
||||
|
||||
it('Only renders the .jpg file', () => {
|
||||
getRowForFileId(jpgFileId).should('have.length', 1)
|
||||
getRowForFileId(movFileId).should('have.length', 0)
|
||||
})
|
||||
|
||||
context("'Show hidden files' is enabled", () => {
|
||||
before(() => {
|
||||
cy.login(currentUser)
|
||||
cy.visit('/apps/files')
|
||||
cy.get('[data-cy-files-navigation-settings-button]').click()
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('[data-cy-files-settings-setting="show_hidden"] input').check({ force: true })
|
||||
})
|
||||
|
||||
it("Shows both files when 'Show hidden files' is enabled", () => {
|
||||
getRowForFileId(jpgFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.jpg`)
|
||||
getRowForFileId(movFileId).should('have.length', 1).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}.mov`)
|
||||
})
|
||||
|
||||
it('Copies both files when copying the .jpg', () => {
|
||||
copyFile(`${randomFileName}.jpg`, '.')
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
|
||||
})
|
||||
|
||||
it('Copies both files when copying the .mov', () => {
|
||||
copyFile(`${randomFileName}.mov`, '.')
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName} (copy).jpg`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName} (copy).mov`).should('have.length', 1)
|
||||
})
|
||||
|
||||
it('Moves files when moving the .jpg', () => {
|
||||
renameFile(`${randomFileName}.jpg`, `${randomFileName}_moved.jpg`)
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
|
||||
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
|
||||
})
|
||||
|
||||
it('Moves files when moving the .mov', () => {
|
||||
renameFile(`${randomFileName}.mov`, `${randomFileName}_moved.mov`)
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.jpg`)
|
||||
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('equal', `${randomFileName}_moved.mov`)
|
||||
})
|
||||
|
||||
it('Deletes files when deleting the .jpg', () => {
|
||||
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
|
||||
|
||||
cy.visit('/apps/files/trashbin')
|
||||
|
||||
getRowForFileId(jpgFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.jpg\\.d[0-9]+$`))
|
||||
getRowForFileId(movFileId).invoke('attr', 'data-cy-files-list-row-name').should('to.match', new RegExp(`^${randomFileName}.mov\\.d[0-9]+$`))
|
||||
})
|
||||
|
||||
it('Block deletion when deleting the .mov', () => {
|
||||
triggerActionForFile(`${randomFileName}.mov`, 'delete')
|
||||
refreshView('All files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
|
||||
|
||||
cy.visit('/apps/files/trashbin')
|
||||
|
||||
getRowForFileId(jpgFileId).should('have.length', 0)
|
||||
getRowForFileId(movFileId).should('have.length', 0)
|
||||
})
|
||||
|
||||
it('Restores files when restoring the .jpg', () => {
|
||||
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
|
||||
cy.visit('/apps/files/trashbin')
|
||||
triggerInlineActionForFileId(jpgFileId, 'restore')
|
||||
refreshView('Deleted files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
|
||||
|
||||
cy.visit('/apps/files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 1)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 1)
|
||||
})
|
||||
|
||||
it('Blocks restoration when restoring the .mov', () => {
|
||||
triggerActionForFile(`${randomFileName}.jpg`, 'delete')
|
||||
cy.visit('/apps/files/trashbin')
|
||||
triggerInlineActionForFileId(movFileId, 'restore')
|
||||
refreshView('Deleted files')
|
||||
|
||||
getRowForFileId(jpgFileId).should('have.length', 1)
|
||||
getRowForFileId(movFileId).should('have.length', 1)
|
||||
|
||||
cy.visit('/apps/files')
|
||||
|
||||
getRowForFile(`${randomFileName}.jpg`).should('have.length', 0)
|
||||
getRowForFile(`${randomFileName}.mov`).should('have.length', 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -58,8 +58,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
|
|||
if (shareSettings.download !== undefined) {
|
||||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="download"]').find('input').as('downloadCheckbox')
|
||||
if (shareSettings.download) {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@downloadCheckbox').check({ force: true, scrollBehavior: 'nearest' })
|
||||
} else {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@downloadCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
|
||||
}
|
||||
}
|
||||
|
|
@ -67,8 +69,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
|
|||
if (shareSettings.read !== undefined) {
|
||||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="read"]').find('input').as('readCheckbox')
|
||||
if (shareSettings.read) {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@readCheckbox').check({ force: true, scrollBehavior: 'nearest' })
|
||||
} else {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@readCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
|
||||
}
|
||||
}
|
||||
|
|
@ -76,8 +80,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
|
|||
if (shareSettings.update !== undefined) {
|
||||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="update"]').find('input').as('updateCheckbox')
|
||||
if (shareSettings.update) {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@updateCheckbox').check({ force: true, scrollBehavior: 'nearest' })
|
||||
} else {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@updateCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
|
||||
}
|
||||
}
|
||||
|
|
@ -85,8 +91,10 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
|
|||
if (shareSettings.delete !== undefined) {
|
||||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="delete"]').find('input').as('deleteCheckbox')
|
||||
if (shareSettings.delete) {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@deleteCheckbox').check({ force: true, scrollBehavior: 'nearest' })
|
||||
} else {
|
||||
// Force:true because the checkbox is hidden by the pretty UI.
|
||||
cy.get('@deleteCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ describe('Versions restoration', () => {
|
|||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
Destination: 'https://nextcloud_server1.test/remote.php/dav/versions/admin/restore/target',
|
||||
Destination: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/restore/target`,
|
||||
},
|
||||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
failOnStatusCode: false,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ declare global {
|
|||
* Upload a raw content to a given user storage.
|
||||
* **Warning**: Using this function will reset the previous session
|
||||
*/
|
||||
uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable<void>,
|
||||
uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable<AxiosResponse>,
|
||||
|
||||
/**
|
||||
* Create a new directory
|
||||
|
|
@ -156,7 +156,7 @@ Cypress.Commands.add('setFileAsFavorite', (user: User, target: string, favorite
|
|||
<oc:favorite>${favorite ? 1 : 0}</oc:favorite>
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`
|
||||
</d:propertyupdate>`,
|
||||
})
|
||||
cy.log(`Created directory ${target}`, response)
|
||||
} catch (error) {
|
||||
|
|
@ -198,36 +198,36 @@ Cypress.Commands.add('mkdir', (user: User, target: string) => {
|
|||
* @param {string} mimeType e.g. image/png
|
||||
* @param {string} target the target of the file relative to the user root
|
||||
*/
|
||||
Cypress.Commands.add('uploadContent', (user, blob, mimeType, target, mtime = undefined) => {
|
||||
// eslint-disable-next-line cypress/unsafe-to-chain-command
|
||||
Cypress.Commands.add('uploadContent', (user: User, blob: Blob, mimeType: string, target: string, mtime?: number) => {
|
||||
cy.clearCookies()
|
||||
.then(async () => {
|
||||
const fileName = basename(target)
|
||||
return cy.then(async () => {
|
||||
const fileName = basename(target)
|
||||
|
||||
// Process paths
|
||||
const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
|
||||
const filePath = target.split('/').map(encodeURIComponent).join('/')
|
||||
try {
|
||||
const file = new File([blob], fileName, { type: mimeType })
|
||||
const response = await axios({
|
||||
url: `${rootPath}${filePath}`,
|
||||
method: 'PUT',
|
||||
data: file,
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'X-OC-MTime': mtime ? `${mtime}` : undefined,
|
||||
},
|
||||
auth: {
|
||||
username: user.userId,
|
||||
password: user.password,
|
||||
},
|
||||
})
|
||||
cy.log(`Uploaded content as ${fileName}`, response)
|
||||
} catch (error) {
|
||||
cy.log('error', error)
|
||||
throw new Error('Unable to process fixture')
|
||||
}
|
||||
})
|
||||
// Process paths
|
||||
const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
|
||||
const filePath = target.split('/').map(encodeURIComponent).join('/')
|
||||
try {
|
||||
const file = new File([blob], fileName, { type: mimeType })
|
||||
const response = await axios({
|
||||
url: `${rootPath}${filePath}`,
|
||||
method: 'PUT',
|
||||
data: file,
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'X-OC-MTime': mtime ? `${mtime}` : undefined,
|
||||
},
|
||||
auth: {
|
||||
username: user.userId,
|
||||
password: user.password,
|
||||
},
|
||||
})
|
||||
cy.log(`Uploaded content as ${fileName}`, response)
|
||||
return response
|
||||
} catch (error) {
|
||||
cy.log('error', error)
|
||||
throw new Error('Unable to process fixture')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
|||
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue