From 52addc91df3639707be0c9578280c61150400b18 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 27 Jul 2025 13:44:21 +0800 Subject: [PATCH] :recycle: Changed the way to clean upload folder --- DysonNetwork.Drive/Client/src/views/index.vue | 153 +----------------- .../Storage/CloudFileUnusedRecyclingJob.cs | 17 +- .../Storage/FileExpirationJob.cs | 8 +- DysonNetwork.Drive/Storage/FileService.cs | 2 - DysonNetwork.Drive/Storage/TusService.cs | 2 - 5 files changed, 25 insertions(+), 157 deletions(-) diff --git a/DysonNetwork.Drive/Client/src/views/index.vue b/DysonNetwork.Drive/Client/src/views/index.vue index 7c52755..566ad5c 100644 --- a/DysonNetwork.Drive/Client/src/views/index.vue +++ b/DysonNetwork.Drive/Client/src/views/index.vue @@ -105,24 +105,17 @@ import { NP, NInput, NSwitch, - NSelect, - NTag, NCollapseTransition, NDatePicker, type UploadCustomRequestOptions, type UploadSettledFileInfo, - type SelectOption, - type SelectRenderTag, type UploadFileInfo, useMessage, - NDivider, - NTooltip, } from 'naive-ui' -import { computed, h, onMounted, ref } from 'vue' +import { computed, onMounted, ref } from 'vue' import { CloudUploadRound } from '@vicons/material' import { useUserStore } from '@/stores/user' import type { SnFilePool } from '@/types/pool' -import { formatBytes } from './format' import FilePoolSelect from '@/components/FilePoolSelect.vue' @@ -146,141 +139,6 @@ async function fetchPools() { } onMounted(() => fetchPools()) -const renderSingleSelectTag: SelectRenderTag = ({ option }) => { - return h( - 'div', - { - style: { - display: 'flex', - alignItems: 'center', - }, - }, - [option.name as string], - ) -} - -const perkPrivilegeList = ['Stellar', 'Nova', 'Supernova'] - -function renderPoolSelectLabel(option: SelectOption & SnFilePool) { - const policy: any = option.policy_config - return h( - 'div', - { - style: { - padding: '8px 2px', - }, - }, - [ - h('div', null, [option.name as string]), - option.description && - h( - 'div', - { - style: { - fontSize: '0.875rem', - opacity: '0.75', - }, - }, - option.description, - ), - h( - 'div', - { - style: { - display: 'flex', - marginBottom: '4px', - fontSize: '0.75rem', - opacity: '0.75', - }, - }, - [ - policy.max_file_size && h('span', `Max ${formatBytes(policy.max_file_size)}`), - policy.accept_types && - h( - NTooltip, - {}, - { - trigger: () => h('span', `Accept limited types`), - default: () => h('span', policy.accept_types.join(', ')), - }, - ), - policy.require_privilege && - h('span', `Require ${perkPrivilegeList[policy.require_privilege - 1]} Program`), - h('span', `Cost x${option.billing_config.cost_multiplier.toFixed(1)} NSD`) - ] - .filter((el) => el) - .flatMap((el, idx, arr) => - idx < arr.length - 1 ? [el, h(NDivider, { vertical: true })] : [el], - ), - ), - h( - 'div', - { - style: { - display: 'flex', - gap: '0.25rem', - marginTop: '2px', - marginLeft: '-2px', - marginRight: '-2px', - }, - }, - [ - policy.public_usable && - h( - NTag, - { - type: 'info', - size: 'small', - round: true, - }, - { default: () => 'Public Shared' }, - ), - policy.public_indexable && - h( - NTag, - { - type: 'success', - size: 'small', - round: true, - }, - { default: () => 'Public Indexable' }, - ), - policy.allow_encryption && - h( - NTag, - { - type: 'warning', - size: 'small', - round: true, - }, - { default: () => 'Allow Encryption' }, - ), - policy.allow_anonymous && - h( - NTag, - { - type: 'info', - size: 'small', - round: true, - }, - { default: () => 'Allow Anonymous' }, - ), - policy.enable_recycle && - h( - NTag, - { - type: 'info', - size: 'small', - round: true, - }, - { default: () => 'Recycle Enabled' }, - ), - ], - ), - ], - ) -} - const modeAdvanced = ref(false) const filePool = ref(null) @@ -296,10 +154,8 @@ const messageDisplay = useMessage() function customRequest({ file, - data, headers, withCredentials, - action, onFinish, onError, onProgress, @@ -311,6 +167,8 @@ function customRequest({ const upload = new tus.Upload(file.file, { endpoint: '/api/tus', retryDelays: [0, 3000, 5000, 10000, 20000], + removeFingerprintOnSuccess: false, + uploadDataDuringCreation: false, metadata: { filename: file.name, 'content-type': file.type ?? 'application/octet-stream', @@ -319,6 +177,7 @@ function customRequest({ ...requestHeaders, ...headers, }, + onShouldRetry: () => false, onError: function (error) { if (error instanceof tus.DetailedError) { const failedBody = error.originalResponse?.getBody() @@ -364,14 +223,14 @@ function createThumbnailUrl( } function customDownload(file: UploadFileInfo) { - const { url, name } = file + const { url } = file if (!url) return window.open(url.replace('/api', ''), '_blank') } function customPreview(file: UploadFileInfo, detail: { event: MouseEvent }) { detail.event.preventDefault() - const { url, type } = file + const { url } = file if (!url) return window.open(url.replace('/api', ''), '_blank') } diff --git a/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs b/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs index 312a3da..f2ae3a9 100644 --- a/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs +++ b/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs @@ -7,12 +7,27 @@ namespace DysonNetwork.Drive.Storage; public class CloudFileUnusedRecyclingJob( AppDatabase db, FileReferenceService fileRefService, - ILogger logger + ILogger logger, + IConfiguration configuration ) : IJob { public async Task Execute(IJobExecutionContext context) { + logger.LogInformation("Cleaning tus cloud files..."); + var storePath = configuration["Tus:StorePath"]; + if (Directory.Exists(storePath)) + { + var oneHourAgo = SystemClock.Instance.GetCurrentInstant() - Duration.FromHours(1); + var files = Directory.GetFiles(storePath); + foreach (var file in files) + { + var creationTime = File.GetCreationTime(file).ToUniversalTime(); + if (creationTime < oneHourAgo.ToDateTimeUtc()) + File.Delete(file); + } + } + logger.LogInformation("Marking unused cloud files..."); var recyclablePools = await db.Pools diff --git a/DysonNetwork.Drive/Storage/FileExpirationJob.cs b/DysonNetwork.Drive/Storage/FileExpirationJob.cs index 13fde3d..f53aedb 100644 --- a/DysonNetwork.Drive/Storage/FileExpirationJob.cs +++ b/DysonNetwork.Drive/Storage/FileExpirationJob.cs @@ -48,11 +48,9 @@ public class FileExpirationJob(AppDatabase db, FileService fileService, ILogger< if (remainingReferences == 0) { var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId); - if (file != null) - { - logger.LogInformation("Deleting file {fileId} as all references have expired", fileId); - await fileService.DeleteFileAsync(file); - } + if (file == null) continue; + logger.LogInformation("Deleting file {fileId} as all references have expired", fileId); + await fileService.DeleteFileAsync(file); } else { diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index 5cd37fc..09169eb 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -170,7 +170,6 @@ public class FileService( // await db.SaveChangesAsync(); // // Since the file content is a duplicate, we can delete the new upload and we are done. // await stream.DisposeAsync(); - // await store.DeleteFileAsync(file.Id, CancellationToken.None); // return file; // } @@ -432,7 +431,6 @@ public class FileService( } finally { - await store.DeleteFileAsync(fileId, CancellationToken.None); await nfs._PurgeCacheAsync(fileId); } } diff --git a/DysonNetwork.Drive/Storage/TusService.cs b/DysonNetwork.Drive/Storage/TusService.cs index 37ae17e..157b323 100644 --- a/DysonNetwork.Drive/Storage/TusService.cs +++ b/DysonNetwork.Drive/Storage/TusService.cs @@ -142,8 +142,6 @@ public abstract class TusService { eventContext.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; await eventContext.HttpContext.Response.WriteAsync(ex.Message); - if (eventContext.Store is TusDiskStore disk) - await disk.DeleteFileAsync(file.Id, eventContext.CancellationToken); } finally {