mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
6e47d76ba6
Client file sharing, rebuilt from the ground up: a private area per client, resumable uploads, folders, groups and categories, sharing with expiry dates and download limits, comments, file versions, an activity log, a REST API, and sixteen languages. This repository begins here. ProjectSend 2 was developed privately, and that development history is not published — the previous generation remains available, with its own history, at projectsend/legacy. Free software under the GNU General Public License v2, or (at your option) any later version.
72 lines
2.7 KiB
PHP
72 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Files\Console;
|
|
|
|
use App\Modules\Audit\Action;
|
|
use App\Modules\Audit\ActivityLogger;
|
|
use App\Modules\Files\OrphanFileScanner;
|
|
use App\Modules\Platform\Settings\ExternalStorageConfigApplier;
|
|
use App\Modules\Platform\Settings\Setting;
|
|
use App\Modules\Platform\Settings\Settings;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class PurgeOrphanFilesCommand extends Command
|
|
{
|
|
protected $signature = 'projectsend:purge-orphan-files';
|
|
|
|
protected $description = 'Permanently delete local-disk orphan files found longer ago than the configured grace period (runs daily; never touches external storage)';
|
|
|
|
public function handle(
|
|
Settings $settings,
|
|
ActivityLogger $activity,
|
|
OrphanFileScanner $scanner,
|
|
ExternalStorageConfigApplier $externalStorage,
|
|
): int {
|
|
// Hardcoded off whenever external storage is active, regardless
|
|
// of the stored setting — this feature only ever operates on the
|
|
// local disk, on purpose (see FileRetentionSettingsController).
|
|
if ($externalStorage->isActive()) {
|
|
$this->info('External storage is active — orphan auto-delete only ever runs against local storage, so it is skipped.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if ($settings->get(Setting::OrphanFilesAutoDeleteEnabled) !== true) {
|
|
$this->info('Auto-delete of orphan files is disabled.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$cutoff = now()->subDays((int) $settings->get(Setting::OrphanFilesDeleteAfterDays));
|
|
$deleted = 0;
|
|
|
|
foreach ($scanner->scan() as $orphan) {
|
|
// Belt and suspenders: scannedDisks() only ever includes
|
|
// 'files_external' when external storage is active, and we
|
|
// already returned above when it is — but filter explicitly
|
|
// rather than relying on that alone.
|
|
if ($orphan['disk'] !== 'files' || $orphan['last_modified'] > $cutoff->timestamp) {
|
|
continue;
|
|
}
|
|
|
|
// Re-validate right before deleting — same defensive re-check
|
|
// the controller's own destroy() does, in case something
|
|
// claimed this path since the scan ran a moment ago.
|
|
if (! $scanner->isOrphan($orphan['disk'], $orphan['path'])) {
|
|
continue;
|
|
}
|
|
|
|
Storage::disk($orphan['disk'])->delete($orphan['path']);
|
|
$activity->logSystem(Action::OrphanFileAutoDeleted, ['name' => basename($orphan['path']), 'disk' => $orphan['disk']]);
|
|
$deleted++;
|
|
}
|
|
|
|
$this->info("Deleted {$deleted} orphan file(s).");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|