mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
fix: allow deletion of partially-uploaded (pending) files and add test
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/8d14b17a-78d8-48c6-b6d8-51d9697adde0 Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com>
This commit is contained in:
co-authored by
Meldiron
parent
9353fbfb74
commit
898e8e214b
@@ -108,10 +108,17 @@ class Delete extends Action
|
||||
|
||||
$deviceDeleted = false;
|
||||
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
|
||||
$deviceDeleted = $deviceForFiles->abort(
|
||||
$file->getAttribute('path'),
|
||||
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
||||
);
|
||||
try {
|
||||
$deviceDeleted = $deviceForFiles->abort(
|
||||
$file->getAttribute('path'),
|
||||
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
||||
);
|
||||
} catch (\Exception) {
|
||||
// If the partial upload chunks are already gone from the device
|
||||
// (e.g. the upload never wrote anything to disk), treat it as deleted
|
||||
// so the pending file document can still be removed from the database.
|
||||
$deviceDeleted = true;
|
||||
}
|
||||
} else {
|
||||
$deviceDeleted = $deviceForFiles->delete($file->getAttribute('path'));
|
||||
}
|
||||
|
||||
@@ -1069,6 +1069,87 @@ trait StorageBase
|
||||
$this->assertNotEmpty($preview['body']);
|
||||
}
|
||||
|
||||
public function testDeletePartiallyUploadedFile(): void
|
||||
{
|
||||
// Create a bucket for this test
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket Partial Upload',
|
||||
'fileSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
// Simulate a partial (cancelled) chunked upload by sending only the first chunk
|
||||
$source = __DIR__ . "/../../../resources/disk-a/large-file.mp4";
|
||||
$totalSize = \filesize($source);
|
||||
$chunkSize = 5 * 1024 * 1024; // 5MB chunks
|
||||
$mimeType = mime_content_type($source);
|
||||
|
||||
$handle = @fopen($source, "rb");
|
||||
$chunkData = @fread($handle, $chunkSize);
|
||||
@fclose($handle);
|
||||
|
||||
$curlFile = new \CURLFile(
|
||||
'data://' . $mimeType . ';base64,' . base64_encode($chunkData),
|
||||
$mimeType,
|
||||
'large-file.mp4'
|
||||
);
|
||||
|
||||
// Send only the first chunk (bytes 0 to chunkSize-1 of totalSize)
|
||||
$end = min($chunkSize - 1, $totalSize - 1);
|
||||
$partialFile = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'content-range' => 'bytes 0-' . $end . '/' . $totalSize,
|
||||
], $this->getHeaders()), [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => $curlFile,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $partialFile['headers']['status-code']);
|
||||
$fileId = $partialFile['body']['$id'];
|
||||
|
||||
// Confirm the file is in a pending state (chunksTotal > chunksUploaded)
|
||||
$this->assertGreaterThan(
|
||||
$partialFile['body']['chunksUploaded'],
|
||||
$partialFile['body']['chunksTotal'],
|
||||
'File should be partially uploaded (pending)'
|
||||
);
|
||||
|
||||
// Delete the partially-uploaded (pending) file — this should succeed
|
||||
$deleteResponse = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $deleteResponse['headers']['status-code']);
|
||||
$this->assertEmpty($deleteResponse['body']);
|
||||
|
||||
// Confirm the file is gone
|
||||
$getResponse = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $getResponse['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteBucketFile(): void
|
||||
{
|
||||
// Create a fresh file just for deletion testing (not using cache since we delete it)
|
||||
|
||||
Reference in New Issue
Block a user