mirror of
https://github.com/colanode/colanode.git
synced 2025-12-15 19:27:46 +01:00
Improve upload and download handling on file delete (#176)
This commit is contained in:
@@ -54,13 +54,6 @@ export class FileDownloadJobHandler implements JobHandler<FileDownloadInput> {
|
||||
};
|
||||
}
|
||||
|
||||
if (!account.server.isAvailable) {
|
||||
return {
|
||||
type: 'retry',
|
||||
delay: ms('5 seconds'),
|
||||
};
|
||||
}
|
||||
|
||||
const workspace = account.getWorkspace(input.workspaceId);
|
||||
if (!workspace) {
|
||||
return {
|
||||
@@ -77,7 +70,12 @@ export class FileDownloadJobHandler implements JobHandler<FileDownloadInput> {
|
||||
|
||||
const file = await this.fetchNode(workspace, download.file_id);
|
||||
if (!file) {
|
||||
await this.deleteDownload(workspace, download.id);
|
||||
await this.updateDownload(workspace, download.id, {
|
||||
status: DownloadStatus.Failed,
|
||||
error_code: 'file_deleted',
|
||||
error_message: 'File has been deleted',
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'cancel',
|
||||
};
|
||||
@@ -90,6 +88,13 @@ export class FileDownloadJobHandler implements JobHandler<FileDownloadInput> {
|
||||
};
|
||||
}
|
||||
|
||||
if (!account.server.isAvailable) {
|
||||
return {
|
||||
type: 'retry',
|
||||
delay: ms('5 seconds'),
|
||||
};
|
||||
}
|
||||
|
||||
return this.performDownload(workspace, download, file);
|
||||
}
|
||||
|
||||
@@ -230,7 +235,7 @@ export class FileDownloadJobHandler implements JobHandler<FileDownloadInput> {
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('id', '=', fileId)
|
||||
.executeTakeFirstOrThrow();
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!node) {
|
||||
return undefined;
|
||||
@@ -262,14 +267,4 @@ export class FileDownloadJobHandler implements JobHandler<FileDownloadInput> {
|
||||
download: mapDownload(updatedDownload),
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteDownload(
|
||||
workspace: WorkspaceService,
|
||||
downloadId: string
|
||||
): Promise<void> {
|
||||
await workspace.database
|
||||
.deleteFrom('downloads')
|
||||
.where('id', '=', downloadId)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,6 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
};
|
||||
}
|
||||
|
||||
if (!account.server.isAvailable) {
|
||||
return {
|
||||
type: 'retry',
|
||||
delay: ms('2 seconds'),
|
||||
};
|
||||
}
|
||||
|
||||
const workspace = account.getWorkspace(input.workspaceId);
|
||||
if (!workspace) {
|
||||
return {
|
||||
@@ -86,6 +79,7 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
const file = await this.fetchNode(workspace, upload.file_id);
|
||||
if (!file) {
|
||||
await this.deleteUpload(workspace, upload.file_id);
|
||||
|
||||
return {
|
||||
type: 'cancel',
|
||||
};
|
||||
@@ -94,11 +88,19 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
const localFile = await this.fetchLocalFile(workspace, file.id);
|
||||
if (!localFile) {
|
||||
await this.deleteUpload(workspace, upload.file_id);
|
||||
|
||||
return {
|
||||
type: 'cancel',
|
||||
};
|
||||
}
|
||||
|
||||
if (!account.server.isAvailable) {
|
||||
return {
|
||||
type: 'retry',
|
||||
delay: ms('2 seconds'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!isNodeSynced(file)) {
|
||||
return {
|
||||
type: 'retry',
|
||||
@@ -363,7 +365,7 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('id', '=', fileId)
|
||||
.executeTakeFirstOrThrow();
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!node) {
|
||||
return undefined;
|
||||
@@ -380,7 +382,7 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
.selectFrom('local_files')
|
||||
.selectAll()
|
||||
.where('id', '=', fileId)
|
||||
.executeTakeFirstOrThrow();
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
private async updateUpload(
|
||||
@@ -411,9 +413,19 @@ export class FileUploadJobHandler implements JobHandler<FileUploadInput> {
|
||||
workspace: WorkspaceService,
|
||||
fileId: string
|
||||
): Promise<void> {
|
||||
await workspace.database
|
||||
const deletedUpload = await workspace.database
|
||||
.deleteFrom('uploads')
|
||||
.where('file_id', '=', fileId)
|
||||
.execute();
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (deletedUpload) {
|
||||
eventBus.publish({
|
||||
type: 'upload.deleted',
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
upload: mapUpload(deletedUpload),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,19 @@ import {
|
||||
SelectNodeReference,
|
||||
} from '@colanode/client/databases/workspace';
|
||||
import { eventBus } from '@colanode/client/lib/event-bus';
|
||||
import { mapNode, mapNodeReference } from '@colanode/client/lib/mappers';
|
||||
import {
|
||||
mapDownload,
|
||||
mapNode,
|
||||
mapNodeReference,
|
||||
mapUpload,
|
||||
} from '@colanode/client/lib/mappers';
|
||||
import {
|
||||
applyMentionUpdates,
|
||||
checkMentionChanges,
|
||||
} from '@colanode/client/lib/mentions';
|
||||
import { deleteNodeRelations, fetchNodeTree } from '@colanode/client/lib/utils';
|
||||
import { WorkspaceService } from '@colanode/client/services/workspaces/workspace-service';
|
||||
import { DownloadStatus } from '@colanode/client/types';
|
||||
import {
|
||||
generateId,
|
||||
IdType,
|
||||
@@ -488,22 +494,63 @@ export class NodeService {
|
||||
return { deletedNode, createdMutation };
|
||||
});
|
||||
|
||||
if (deletedNode) {
|
||||
debug(`Deleted node ${deletedNode.id} with type ${deletedNode.type}`);
|
||||
|
||||
eventBus.publish({
|
||||
type: 'node.deleted',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
node: mapNode(deletedNode),
|
||||
});
|
||||
} else {
|
||||
debug(`Failed to delete node ${nodeId}`);
|
||||
if (!deletedNode || !createdMutation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (createdMutation) {
|
||||
this.workspace.mutations.scheduleSync();
|
||||
debug(`Deleted node ${deletedNode.id} with type ${deletedNode.type}`);
|
||||
|
||||
eventBus.publish({
|
||||
type: 'node.deleted',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
node: mapNode(deletedNode),
|
||||
});
|
||||
|
||||
if (deletedNode.type === 'file') {
|
||||
const deletedUpload = await this.workspace.database
|
||||
.deleteFrom('uploads')
|
||||
.where('file_id', '=', deletedNode.id)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (deletedUpload) {
|
||||
eventBus.publish({
|
||||
type: 'upload.deleted',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
upload: mapUpload(deletedUpload),
|
||||
});
|
||||
}
|
||||
|
||||
const updatedDownloads = await this.workspace.database
|
||||
.updateTable('downloads')
|
||||
.set({
|
||||
status: DownloadStatus.Failed,
|
||||
error_code: 'file_deleted',
|
||||
error_message: 'File has been deleted',
|
||||
})
|
||||
.where('file_id', '=', deletedNode.id)
|
||||
.where('status', 'in', [
|
||||
DownloadStatus.Pending,
|
||||
DownloadStatus.Downloading,
|
||||
])
|
||||
.returningAll()
|
||||
.execute();
|
||||
|
||||
if (updatedDownloads.length > 0) {
|
||||
for (const updatedDownload of updatedDownloads) {
|
||||
eventBus.publish({
|
||||
type: 'download.updated',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
download: mapDownload(updatedDownload),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.workspace.mutations.scheduleSync();
|
||||
}
|
||||
|
||||
public async syncServerNodeUpdate(update: SyncNodeUpdateData) {
|
||||
@@ -821,7 +868,48 @@ export class NodeService {
|
||||
await this.workspace.nodeCounters.checkCountersForDeletedNode(deletedNode);
|
||||
|
||||
if (deletedNode.type === 'file') {
|
||||
this.workspace.files.deleteFile(deletedNode);
|
||||
await this.workspace.files.deleteFile(deletedNode);
|
||||
|
||||
const deletedUpload = await this.workspace.database
|
||||
.deleteFrom('uploads')
|
||||
.where('file_id', '=', deletedNode.id)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (deletedUpload) {
|
||||
eventBus.publish({
|
||||
type: 'upload.deleted',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
upload: mapUpload(deletedUpload),
|
||||
});
|
||||
}
|
||||
|
||||
const updatedDownloads = await this.workspace.database
|
||||
.updateTable('downloads')
|
||||
.set({
|
||||
status: DownloadStatus.Failed,
|
||||
error_code: 'file_deleted',
|
||||
error_message: 'File has been deleted',
|
||||
})
|
||||
.where('file_id', '=', deletedNode.id)
|
||||
.where('status', 'in', [
|
||||
DownloadStatus.Pending,
|
||||
DownloadStatus.Downloading,
|
||||
])
|
||||
.returningAll()
|
||||
.execute();
|
||||
|
||||
if (updatedDownloads.length > 0) {
|
||||
for (const updatedDownload of updatedDownloads) {
|
||||
eventBus.publish({
|
||||
type: 'download.updated',
|
||||
accountId: this.workspace.accountId,
|
||||
workspaceId: this.workspace.id,
|
||||
download: mapDownload(updatedDownload),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Folder } from 'lucide-react';
|
||||
|
||||
import { LocalFileNode, Download } from '@colanode/client/types';
|
||||
import { formatBytes, timeAgo } from '@colanode/core';
|
||||
import { FileIcon } from '@colanode/ui/components/files/file-icon';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -30,19 +31,22 @@ export const WorkspaceDownloadFile = ({
|
||||
nodeId: download.fileId,
|
||||
});
|
||||
|
||||
const file = fileQuery.data as LocalFileNode;
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
const file = fileQuery.data as LocalFileNode | undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors flex items-center gap-6 cursor-pointer"
|
||||
onClick={() => {
|
||||
layout.previewLeft(file.id, true);
|
||||
if (file) {
|
||||
layout.previewLeft(file.id, true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FileThumbnail file={file} className="size-10 text-muted-foreground" />
|
||||
{file ? (
|
||||
<FileThumbnail file={file} className="size-10 text-muted-foreground" />
|
||||
) : (
|
||||
<FileIcon mimeType={download.mimeType} className="size-10" />
|
||||
)}
|
||||
|
||||
<div className="flex-grow flex flex-col gap-2 justify-center items-start min-w-0">
|
||||
<p className="font-medium text-sm truncate">{download.name}</p>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BadgeAlert } from 'lucide-react';
|
||||
|
||||
import { Upload, LocalFileNode } from '@colanode/client/types';
|
||||
import { formatBytes, timeAgo } from '@colanode/core';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
@@ -24,7 +26,28 @@ export const WorkspaceUploadFile = ({ upload }: WorkspaceUploadFileProps) => {
|
||||
const file = fileQuery.data as LocalFileNode;
|
||||
|
||||
if (!file) {
|
||||
return null;
|
||||
return (
|
||||
<div className="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors flex items-center gap-6 cursor-pointer">
|
||||
<BadgeAlert className="size-10 text-muted-foreground" />
|
||||
|
||||
<div className="flex-grow flex flex-col gap-2 justify-center items-start min-w-0">
|
||||
<p className="font-medium text-sm truncate w-full">
|
||||
File not found or has been deleted
|
||||
</p>
|
||||
{upload.errorMessage && (
|
||||
<p className="text-xs text-red-500">{upload.errorMessage}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<div className="w-10 flex items-center justify-center">
|
||||
<WorkspaceUploadStatus
|
||||
status={upload.status}
|
||||
progress={upload.progress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user