Skip to content

Commit

Permalink
v1.2.9
Browse files Browse the repository at this point in the history
  • Loading branch information
eldertek committed Dec 17, 2024
1 parent 77d3017 commit 3287483
Show file tree
Hide file tree
Showing 31 changed files with 1,514 additions and 181 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 1.2.9 - 2024-12-17
### Added
- Origin folders configuration to protect files from deletion
- Backend API for file deletion with improved error handling
- Detailed error messages for file operations
### Changed
- File deletion now uses backend API instead of FileClient
- Improved error handling in frontend with specific error messages
### Fixed
- Better handling of protected files in origin folders
- More informative error messages when file deletion fails

## 1.2.8 - 2024-12-16
### Fixed
- Revert back to v1.2.5 lib
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Normally the detection methods should be used in the order as listed, but if you
## Ignoring Duplicates
To prevent a folder from being scanned for duplicates, place a `.nodupefinder` file inside it. Any files in this folder will be excluded from the duplicate detection process.

## Protected Files in Origin Folders
You can configure "Origin Folders" to protect specific files from accidental deletion. Files located in these designated folders cannot be deleted through the duplicate finder interface, ensuring that original files are preserved. This is particularly useful when you want to:
- Keep original files in specific directories
- Prevent accidental deletion of important files
- Maintain a source of truth for your files

You can configure Origin Folders in the app settings. Files in these folders will be marked as "Protected" in the interface instead of showing a delete button.

## Search and Filter Duplicates
You can search and filter duplicates by file path or name using the search input at the top of the "Unacknowledged" and "Acknowledged" sections.

Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<summary lang="fr">Économisez de l’espace en trouvant vos fichiers en doublon</summary>
<description>Are you tired of sifting through piles of files and folders, only to discover multiple copies of the same content cluttering your storage space?</description>
<description lang="fr">Vous en avez assez de passer au crible des piles de fichiers et de dossiers pour découvrir que plusieurs copies du même contenu encombrent votre espace de stockage ?</description>
<version>1.2.8</version>
<version>1.2.9</version>
<licence>agpl</licence>
<author mail="andrelauret@eclipse-technology.eu" >André Théo LAURET</author>
<namespace>DuplicateFinder</namespace>
Expand Down
4 changes: 4 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@
['name' => 'duplicate_api#clear', 'url' => '/api/duplicates/clear', 'verb' => 'POST'],
['name' => 'settings_api#list', 'url' => '/api/settings', 'verb' => 'GET'],
['name' => 'settings_api#save', 'url' => '/api/settings/{key}/{value}', 'verb' => 'POST'],
['name' => 'origin_folder_api#index', 'url' => '/api/origin-folders', 'verb' => 'GET'],
['name' => 'origin_folder_api#create', 'url' => '/api/origin-folders', 'verb' => 'POST'],
['name' => 'origin_folder_api#destroy', 'url' => '/api/origin-folders/{id}', 'verb' => 'DELETE'],
['name' => 'file_api#delete', 'url' => '/api/files/delete', 'verb' => 'POST'],
],
];
2 changes: 1 addition & 1 deletion js/duplicatefinder-main.js

Large diffs are not rendered by default.

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion js/duplicatefinder-settings.js

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion lib/Controller/DuplicateApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use OCA\DuplicateFinder\Service\FileDuplicateService;
use OCA\DuplicateFinder\Service\FileInfoService;
use OCA\DuplicateFinder\Db\FileDuplicateMapper;
use OCA\DuplicateFinder\Service\OriginFolderService;

class DuplicateApiController extends AbstractAPIController
{
Expand All @@ -24,6 +25,8 @@ class DuplicateApiController extends AbstractAPIController
private $userManager;
/** @var LoggerInterface */
protected $logger;
/** @var OriginFolderService */
private $originFolderService;

public function __construct(
$appName,
Expand All @@ -33,14 +36,16 @@ public function __construct(
FileInfoService $fileInfoService,
FileDuplicateMapper $fileDuplidateMapper,
IUserManager $userManager,
LoggerInterface $logger
LoggerInterface $logger,
OriginFolderService $originFolderService
) {
parent::__construct($appName, $request, $userSession, $logger);
$this->fileInfoService = $fileInfoService;
$this->userManager = $userManager;
$this->fileDuplicateService = $fileDuplicateService;
$this->logger = $logger;
$this->fileDuplicateMapper = $fileDuplidateMapper;
$this->originFolderService = $originFolderService;
}

/**
Expand Down
93 changes: 93 additions & 0 deletions lib/Controller/FileApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace OCA\DuplicateFinder\Controller;

use Exception;
use OCA\DuplicateFinder\Service\FileService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

/**
* @package OCA\DuplicateFinder\Controller
*/
class FileApiController extends Controller {
private FileService $service;
private string $userId;
private LoggerInterface $logger;

public function __construct(
string $AppName,
IRequest $request,
FileService $service,
string $userId,
LoggerInterface $logger
) {
parent::__construct($AppName, $request);
$this->service = $service;
$this->userId = $userId;
$this->logger = $logger;
}

/**
* Delete a file
*
* @NoAdminRequired
* @NoCSRFRequired
* @return JSONResponse
*/
public function delete(): JSONResponse {
$path = $this->request->getParam('path');
if (empty($path)) {
return new JSONResponse(
['error' => 'Path parameter is required'],
Http::STATUS_BAD_REQUEST
);
}

try {
$this->logger->debug('Attempting to delete file: {path}', ['path' => $path]);
$this->service->deleteFile($this->userId, $path);
$this->logger->info('Successfully deleted file: {path}', ['path' => $path]);

return new JSONResponse(['status' => 'success']);
} catch (Exception $e) {
$this->logger->error('Error deleting file: {error}', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);

$status = match (true) {
$e instanceof \OCA\DuplicateFinder\Exception\OriginFolderProtectionException => Http::STATUS_FORBIDDEN,
$e instanceof \OCP\Files\NotFoundException => Http::STATUS_NOT_FOUND,
$e instanceof \OCP\Files\NotPermittedException => Http::STATUS_FORBIDDEN,
default => Http::STATUS_INTERNAL_SERVER_ERROR,
};

$message = match (true) {
$e instanceof \OCA\DuplicateFinder\Exception\OriginFolderProtectionException => [
'error' => 'ORIGIN_FOLDER_PROTECTED',
'message' => $e->getMessage()
],
$e instanceof \OCP\Files\NotFoundException => [
'error' => 'FILE_NOT_FOUND',
'message' => 'File not found: ' . $path
],
$e instanceof \OCP\Files\NotPermittedException => [
'error' => 'PERMISSION_DENIED',
'message' => 'Permission denied to delete file: ' . $path
],
default => [
'error' => 'INTERNAL_ERROR',
'message' => 'An unexpected error occurred'
],
};

return new JSONResponse($message, $status);
}
}
}
107 changes: 107 additions & 0 deletions lib/Controller/OriginFolderApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace OCA\DuplicateFinder\Controller;

use Exception;
use OCA\DuplicateFinder\Service\OriginFolderService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

class OriginFolderApiController extends Controller {
private OriginFolderService $service;
private string $userId;
private LoggerInterface $logger;

public function __construct(
string $AppName,
IRequest $request,
OriginFolderService $service,
string $userId,
LoggerInterface $logger
) {
parent::__construct($AppName, $request);
$this->service = $service;
$this->userId = $userId;
$this->logger = $logger;
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(): JSONResponse {
try {
$folders = $this->service->findAll();
return new JSONResponse([
'folders' => array_map(function ($folder) {
return [
'id' => $folder->getId(),
'path' => $folder->getFolderPath()
];
}, $folders)
]);
} catch (Exception $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function create(array $folders): JSONResponse {
try {
$this->logger->debug('Creating origin folders: {folders}', ['folders' => json_encode($folders)]);
$createdFolders = [];
$errors = [];

foreach ($folders as $folderPath) {
try {
$this->logger->debug('Attempting to create origin folder: {path}', ['path' => $folderPath]);
$this->service->create($folderPath);
$createdFolders[] = $folderPath;
$this->logger->debug('Successfully created origin folder: {path}', ['path' => $folderPath]);
} catch (Exception $e) {
$this->logger->error('Failed to create origin folder: {path}, error: {error}', [
'path' => $folderPath,
'error' => $e->getMessage()
]);
$errors[] = [
'path' => $folderPath,
'error' => $e->getMessage()
];
}
}

$this->logger->debug('Creation complete. Created: {created}, Errors: {errors}', [
'created' => count($createdFolders),
'errors' => count($errors)
]);

return new JSONResponse([
'created' => $createdFolders,
'errors' => $errors
], empty($errors) ? Http::STATUS_CREATED : Http::STATUS_MULTI_STATUS);
} catch (Exception $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function destroy(int $id): JSONResponse {
try {
$folder = $this->service->delete($id);
return new JSONResponse($folder);
} catch (Exception $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}
}
6 changes: 6 additions & 0 deletions lib/Db/FileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @method void setMimetype(string $s)
* @method void setSize(int $i)
* @method void setIgnored(bool $b)
* @method void setIsInOriginFolder(bool $b)
* @method string getOwner()
* @method string getPath()
* @method string getPathHash()
Expand All @@ -22,6 +23,7 @@
* @method string getMimetype()
* @method int getSize()
* @method bool isIgnored()
* @method bool isInOriginFolder()
*/
class FileInfo extends EEntity
{
Expand All @@ -46,13 +48,17 @@ class FileInfo extends EEntity
protected $size;
/** @var boolean */
protected $ignored;
/** @var boolean */
protected $isInOriginFolder;

public function __construct(?string $path = null, ?string $owner = null)
{
$this->addInternalType('updatedAt', 'date');
$this->addInternalProperty('nodeId');
$this->addType('size', 'integer');
$this->addType('ignored', 'boolean');
$this->addType('isInOriginFolder', 'boolean');
$this->isInOriginFolder = false;

if (!is_null($path)) {
$this->setPath($path);
Expand Down
37 changes: 37 additions & 0 deletions lib/Db/OriginFolder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace OCA\DuplicateFinder\Db;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;

/**
* @method string getUserId()
* @method void setUserId(string $userId)
* @method string getFolderPath()
* @method void setFolderPath(string $folderPath)
* @method string getCreatedAt()
* @method void setCreatedAt(string $createdAt)
*/
class OriginFolder extends Entity implements JsonSerializable {
protected string $userId = '';
protected string $folderPath = '';
protected string $createdAt = '';

public function __construct() {
$this->addType('userId', 'string');
$this->addType('folderPath', 'string');
$this->addType('createdAt', 'string');
}

public function jsonSerialize(): array {
return [
'id' => $this->id,
'userId' => $this->userId,
'folderPath' => $this->folderPath,
'createdAt' => $this->createdAt,
];
}
}
Loading

0 comments on commit 3287483

Please sign in to comment.