♻️ Changed the way to clean upload folder
This commit is contained in:
@@ -105,24 +105,17 @@ import {
|
|||||||
NP,
|
NP,
|
||||||
NInput,
|
NInput,
|
||||||
NSwitch,
|
NSwitch,
|
||||||
NSelect,
|
|
||||||
NTag,
|
|
||||||
NCollapseTransition,
|
NCollapseTransition,
|
||||||
NDatePicker,
|
NDatePicker,
|
||||||
type UploadCustomRequestOptions,
|
type UploadCustomRequestOptions,
|
||||||
type UploadSettledFileInfo,
|
type UploadSettledFileInfo,
|
||||||
type SelectOption,
|
|
||||||
type SelectRenderTag,
|
|
||||||
type UploadFileInfo,
|
type UploadFileInfo,
|
||||||
useMessage,
|
useMessage,
|
||||||
NDivider,
|
|
||||||
NTooltip,
|
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { CloudUploadRound } from '@vicons/material'
|
import { CloudUploadRound } from '@vicons/material'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import type { SnFilePool } from '@/types/pool'
|
import type { SnFilePool } from '@/types/pool'
|
||||||
import { formatBytes } from './format'
|
|
||||||
|
|
||||||
import FilePoolSelect from '@/components/FilePoolSelect.vue'
|
import FilePoolSelect from '@/components/FilePoolSelect.vue'
|
||||||
|
|
||||||
@@ -146,141 +139,6 @@ async function fetchPools() {
|
|||||||
}
|
}
|
||||||
onMounted(() => 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 modeAdvanced = ref(false)
|
||||||
|
|
||||||
const filePool = ref<string | null>(null)
|
const filePool = ref<string | null>(null)
|
||||||
@@ -296,10 +154,8 @@ const messageDisplay = useMessage()
|
|||||||
|
|
||||||
function customRequest({
|
function customRequest({
|
||||||
file,
|
file,
|
||||||
data,
|
|
||||||
headers,
|
headers,
|
||||||
withCredentials,
|
withCredentials,
|
||||||
action,
|
|
||||||
onFinish,
|
onFinish,
|
||||||
onError,
|
onError,
|
||||||
onProgress,
|
onProgress,
|
||||||
@@ -311,6 +167,8 @@ function customRequest({
|
|||||||
const upload = new tus.Upload(file.file, {
|
const upload = new tus.Upload(file.file, {
|
||||||
endpoint: '/api/tus',
|
endpoint: '/api/tus',
|
||||||
retryDelays: [0, 3000, 5000, 10000, 20000],
|
retryDelays: [0, 3000, 5000, 10000, 20000],
|
||||||
|
removeFingerprintOnSuccess: false,
|
||||||
|
uploadDataDuringCreation: false,
|
||||||
metadata: {
|
metadata: {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
'content-type': file.type ?? 'application/octet-stream',
|
'content-type': file.type ?? 'application/octet-stream',
|
||||||
@@ -319,6 +177,7 @@ function customRequest({
|
|||||||
...requestHeaders,
|
...requestHeaders,
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
|
onShouldRetry: () => false,
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
if (error instanceof tus.DetailedError) {
|
if (error instanceof tus.DetailedError) {
|
||||||
const failedBody = error.originalResponse?.getBody()
|
const failedBody = error.originalResponse?.getBody()
|
||||||
@@ -364,14 +223,14 @@ function createThumbnailUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function customDownload(file: UploadFileInfo) {
|
function customDownload(file: UploadFileInfo) {
|
||||||
const { url, name } = file
|
const { url } = file
|
||||||
if (!url) return
|
if (!url) return
|
||||||
window.open(url.replace('/api', ''), '_blank')
|
window.open(url.replace('/api', ''), '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
function customPreview(file: UploadFileInfo, detail: { event: MouseEvent }) {
|
function customPreview(file: UploadFileInfo, detail: { event: MouseEvent }) {
|
||||||
detail.event.preventDefault()
|
detail.event.preventDefault()
|
||||||
const { url, type } = file
|
const { url } = file
|
||||||
if (!url) return
|
if (!url) return
|
||||||
window.open(url.replace('/api', ''), '_blank')
|
window.open(url.replace('/api', ''), '_blank')
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,27 @@ namespace DysonNetwork.Drive.Storage;
|
|||||||
public class CloudFileUnusedRecyclingJob(
|
public class CloudFileUnusedRecyclingJob(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
FileReferenceService fileRefService,
|
FileReferenceService fileRefService,
|
||||||
ILogger<CloudFileUnusedRecyclingJob> logger
|
ILogger<CloudFileUnusedRecyclingJob> logger,
|
||||||
|
IConfiguration configuration
|
||||||
)
|
)
|
||||||
: IJob
|
: IJob
|
||||||
{
|
{
|
||||||
public async Task Execute(IJobExecutionContext context)
|
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...");
|
logger.LogInformation("Marking unused cloud files...");
|
||||||
|
|
||||||
var recyclablePools = await db.Pools
|
var recyclablePools = await db.Pools
|
||||||
|
@@ -48,12 +48,10 @@ public class FileExpirationJob(AppDatabase db, FileService fileService, ILogger<
|
|||||||
if (remainingReferences == 0)
|
if (remainingReferences == 0)
|
||||||
{
|
{
|
||||||
var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId);
|
var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId);
|
||||||
if (file != null)
|
if (file == null) continue;
|
||||||
{
|
|
||||||
logger.LogInformation("Deleting file {fileId} as all references have expired", fileId);
|
logger.LogInformation("Deleting file {fileId} as all references have expired", fileId);
|
||||||
await fileService.DeleteFileAsync(file);
|
await fileService.DeleteFileAsync(file);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Just purge the cache
|
// Just purge the cache
|
||||||
|
@@ -170,7 +170,6 @@ public class FileService(
|
|||||||
// await db.SaveChangesAsync();
|
// await db.SaveChangesAsync();
|
||||||
// // Since the file content is a duplicate, we can delete the new upload and we are done.
|
// // Since the file content is a duplicate, we can delete the new upload and we are done.
|
||||||
// await stream.DisposeAsync();
|
// await stream.DisposeAsync();
|
||||||
// await store.DeleteFileAsync(file.Id, CancellationToken.None);
|
|
||||||
// return file;
|
// return file;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -432,7 +431,6 @@ public class FileService(
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await store.DeleteFileAsync(fileId, CancellationToken.None);
|
|
||||||
await nfs._PurgeCacheAsync(fileId);
|
await nfs._PurgeCacheAsync(fileId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -142,8 +142,6 @@ public abstract class TusService
|
|||||||
{
|
{
|
||||||
eventContext.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
eventContext.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
await eventContext.HttpContext.Response.WriteAsync(ex.Message);
|
await eventContext.HttpContext.Response.WriteAsync(ex.Message);
|
||||||
if (eventContext.Store is TusDiskStore disk)
|
|
||||||
await disk.DeleteFileAsync(file.Id, eventContext.CancellationToken);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user