Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117778099
D5775.1775244544.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
7 KB
Referenced Files
None
Subscribers
None
D5775.1775244544.diff
View Options
diff --git a/src/app/Console/Commands/Fs/ExpungeCommand.php b/src/app/Console/Commands/Fs/ExpungeCommand.php
--- a/src/app/Console/Commands/Fs/ExpungeCommand.php
+++ b/src/app/Console/Commands/Fs/ExpungeCommand.php
@@ -38,12 +38,24 @@
->orderBy('deleted_at')
->cursor();
- // FIXME: Should we use an async job for each file?
+ foreach ($files as $file) {
+ Storage::fileDelete($file);
+ }
+
+ // Remove orphaned files
+ $files = Item::withTrashed()
+ ->where('type', '&', Item::TYPE_FILE)
+ ->whereNull('user_id')
+ ->orderBy('deleted_at')
+ ->cursor();
foreach ($files as $file) {
Storage::fileDelete($file);
}
+ // Remove orphaned items (of any type)
+ Item::withTrashed()->whereNull('user_id')->forceDelete();
+
// We remove orphaned chunks after upload ttl (while marked as deleted some chunks may still be
// in the process of uploading a file).
$chunks = Chunk::withTrashed()
@@ -51,10 +63,10 @@
->orderBy('deleted_at')
->cursor();
- // FIXME: Should we use an async job for each chunk?
-
foreach ($chunks as $chunk) {
Storage::fileChunkDelete($chunk);
}
+
+ // FIXME: What about soft-deleted collection items, should we remove them (after some time)?
}
}
diff --git a/src/app/Console/Commands/User/DelegateCommand.php b/src/app/Console/Commands/User/DelegateCommand.php
--- a/src/app/Console/Commands/User/DelegateCommand.php
+++ b/src/app/Console/Commands/User/DelegateCommand.php
@@ -4,7 +4,6 @@
use App\Console\Command;
use App\Delegation;
-use Carbon\Carbon;
class DelegateCommand extends Command
{
diff --git a/src/database/migrations/2025_12_30_100000_update_fs_items_table.php b/src/database/migrations/2025_12_30_100000_update_fs_items_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2025_12_30_100000_update_fs_items_table.php
@@ -0,0 +1,37 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+ /**
+ * Run the migrations.
+ */
+ public function up(): void
+ {
+ Schema::table(
+ 'fs_items',
+ static function (Blueprint $table) {
+ $table->dropForeign(['user_id']);
+ $table->bigInteger('user_id')->nullable()->change();
+ $table->foreign('user_id')->references('id')->on('users')->cascadeOnUpdate()->nullOnDelete();
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table(
+ 'fs_items',
+ static function (Blueprint $table) {
+ $table->dropForeign(['user_id']);
+ $table->bigInteger('user_id')->change();
+ $table->foreign('user_id')->references('id')->on('users')->cascadeOnUpdate()->cascadeOnDelete();
+ }
+ );
+ }
+};
diff --git a/src/tests/Feature/Backends/StorageTest.php b/src/tests/Feature/Backends/StorageTest.php
--- a/src/tests/Feature/Backends/StorageTest.php
+++ b/src/tests/Feature/Backends/StorageTest.php
@@ -3,12 +3,51 @@
namespace Tests\Feature\Backends;
use App\Backends\Storage;
+use App\Fs\Chunk;
use App\Fs\Item;
use Illuminate\Support\Facades\Storage as LaravelStorage;
use Tests\TestCaseFs;
class StorageTest extends TestCaseFs
{
+ /**
+ * Test Storage::fileChunkDelete()
+ */
+ public function testFileChunkDelete(): void
+ {
+ $disk = LaravelStorage::disk(\config('filesystems.default'));
+ $user = $this->getTestUser('john@kolab.org');
+ $file = $this->getTestFile($user, 'test.txt', 'Test content1', ['mimetype' => 'text/plain']);
+
+ $chunk = $file->chunks()->first();
+ $path = Storage::chunkLocation($chunk->chunk_id, $file);
+
+ $this->assertTrue(LaravelStorage::fileExists($path));
+
+ Storage::fileChunkDelete($chunk);
+
+ $this->assertFalse(LaravelStorage::fileExists($path));
+ $this->assertNull(Chunk::find($chunk->id));
+ }
+
+ /**
+ * Test Storage::fileDelete()
+ */
+ public function testFileDelete(): void
+ {
+ $disk = LaravelStorage::disk(\config('filesystems.default'));
+ $user = $this->getTestUser('john@kolab.org');
+ $file = $this->getTestFile($user, 'test.txt', 'Test content1', ['mimetype' => 'text/plain']);
+
+ $path = $file->path . '/' . $file->id;
+ $this->assertTrue(LaravelStorage::directoryExists($path));
+
+ Storage::fileDelete($file);
+
+ $this->assertFalse(LaravelStorage::directoryExists($path));
+ $this->assertNull(Item::find($file->id));
+ }
+
/**
* Test Storage::fileInput() splitting the input into chunks
*/
diff --git a/src/tests/Feature/Console/Fs/ExpungeTest.php b/src/tests/Feature/Console/Fs/ExpungeTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/Fs/ExpungeTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Tests\Feature\Console\Fs;
+
+use App\Backends\Storage;
+use App\Fs\Chunk;
+use App\Fs\Item;
+use Tests\TestCaseFs;
+
+class ExpungeTest extends TestCaseFs
+{
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('expungetest@kolabnow.com');
+ }
+
+ protected function tearDown(): void
+ {
+ $this->deleteTestUser('expungetest@kolabnow.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test fs:expunge command
+ */
+ public function testHandle(): void
+ {
+ $user = $this->getTestUser('expungetest@kolabnow.com');
+ $folder1 = $this->getTestCollection($user, 'folder');
+ $file1 = $this->getTestFile($user, 'test1.txt', 'Test content1', ['mimetype' => 'text/plain']);
+ $file2 = $this->getTestFile($user, 'test2.txt', 'Test content2', ['mimetype' => 'text/plain']);
+
+ // Expect no changes
+ $code = \Artisan::call('fs:expunge');
+ $this->assertSame(0, $code);
+ $this->assertSame('', trim(\Artisan::output()));
+ $this->assertCount(3, $user->fsItems()->get());
+
+ // Test expunging of soft-deleted files
+ $file1->delete();
+
+ $code = \Artisan::call('fs:expunge');
+ $this->assertSame(0, $code);
+ $this->assertNull(Item::find($file1->id));
+ $this->assertCount(2, $user->fsItems()->get());
+
+ // TODO: Test expunging of orphaned chunks
+ $file2->chunks()->create([
+ 'chunk_id' => '123456789',
+ 'sequence' => 2,
+ 'size' => 2,
+ ]);
+ $chunk = Chunk::where('chunk_id', '123456789')->first();
+ $chunk->update(['deleted_at' => now()->subSeconds(Storage::UPLOAD_TTL + 5)]);
+
+ $code = \Artisan::call('fs:expunge');
+ $this->assertSame(0, $code);
+ $this->assertNull(Chunk::where('chunk_id', $chunk->chunk_id)->first());
+
+ // Delete user (this does not remove files)
+ $user->delete();
+ $this->assertCount(2, $user->fsItems()->get());
+
+ $code = \Artisan::call('fs:expunge');
+ $this->assertSame(0, $code);
+ $this->assertCount(2, $user->fsItems()->get());
+
+ // Force-delete user (should orphan files/collections and then expunge them)
+ $user->forceDelete();
+ $this->assertCount(2, Item::whereNull('user_id')->get());
+
+ $code = \Artisan::call('fs:expunge');
+ $this->assertSame(0, $code);
+ $this->assertCount(0, Item::all());
+ }
+}
diff --git a/src/tests/Feature/Console/User/DelegateTest.php b/src/tests/Feature/Console/User/DelegateTest.php
--- a/src/tests/Feature/Console/User/DelegateTest.php
+++ b/src/tests/Feature/Console/User/DelegateTest.php
@@ -4,7 +4,6 @@
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
-use App\Delegation;
class DelegateTest extends TestCase
{
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 7:29 PM (17 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18826002
Default Alt Text
D5775.1775244544.diff (7 KB)
Attached To
Mode
D5775: Disk cleanup for files in removed accounts
Attached
Detach File
Event Timeline