diff --git a/DysonNetwork.Drive/Client/src/components/UploadArea.vue b/DysonNetwork.Drive/Client/src/components/UploadArea.vue
index 1520687..b04deb4 100644
--- a/DysonNetwork.Drive/Client/src/components/UploadArea.vue
+++ b/DysonNetwork.Drive/Client/src/components/UploadArea.vue
@@ -34,6 +34,13 @@
:is-date-disabled="disablePreviousDate"
/>
+
@@ -78,12 +85,14 @@ import {
NDatePicker,
NAlert,
NCard,
+ NSwitch,
type UploadCustomRequestOptions,
type UploadSettledFileInfo,
type UploadFileInfo,
useMessage,
} from 'naive-ui'
import { computed, ref } from 'vue'
+import { useRoute } from 'vue-router'
import { CloudUploadRound } from '@vicons/material'
import type { SnFilePool } from '@/types/pool'
@@ -96,21 +105,26 @@ const props = defineProps<{
bundleId?: string
}>()
+const route = useRoute()
+
const filePass = ref('')
const fileExpire = ref(null)
+const fastUpload = ref(false)
+
+const effectiveFilePool = computed(() => (route.query.pool as string) || props.filePool)
const currentFilePool = computed(() => {
- if (!props.filePool) return null
- return props.pools?.find((pool) => pool.id === props.filePool) ?? null
+ if (!effectiveFilePool.value) return null
+ return props.pools?.find((pool) => pool.id === effectiveFilePool.value) ?? null
})
const showRecycleHint = computed(() => {
- if (!props.filePool) return true
+ if (!effectiveFilePool.value) return true
return currentFilePool.value?.policy_config?.enable_recycle || false
})
const messageDisplay = useMessage()
-function customRequest({
+async function customRequest({
file,
headers,
withCredentials,
@@ -118,12 +132,68 @@ function customRequest({
onError,
onProgress,
}: UploadCustomRequestOptions) {
+ if (fastUpload.value) {
+ const hash = await crypto.subtle.digest('SHA-256', await file.file!.arrayBuffer())
+ const hashString = Array.from(new Uint8Array(hash))
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('')
+
+ const resp = await fetch('/api/files/fast', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: file.name,
+ size: file.file?.size,
+ hash: hashString,
+ mime_type: file.file?.type,
+ pool_id: effectiveFilePool.value,
+ }),
+ })
+
+ if (!resp.ok) {
+ messageDisplay.error(`Failed to get presigned URL: ${await resp.text()}`)
+ onError()
+ return
+ }
+
+ const respData = await resp.json()
+ const url = respData.fast_upload_link
+
+ try {
+ const xhr = new XMLHttpRequest()
+ xhr.open('PUT', url, true)
+ xhr.upload.onprogress = (event) => {
+ if (event.lengthComputable) {
+ onProgress({ percent: (event.loaded / event.total) * 100 })
+ }
+ }
+ xhr.onload = () => {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ onFinish()
+ } else {
+ messageDisplay.error(`Upload failed: ${xhr.responseText}`)
+ onError()
+ }
+ }
+ xhr.onerror = () => {
+ messageDisplay.error('Upload failed due to a network error.')
+ onError()
+ }
+ xhr.send(file.file)
+ } catch (e) {
+ console.error(e)
+ messageDisplay.error(`Upload failed: ${e}`)
+ onError()
+ }
+ return
+ }
+
const requestHeaders: Record = {}
- if (props.filePool) requestHeaders['X-FilePool'] = props.filePool
+ if (effectiveFilePool.value) requestHeaders['X-FilePool'] = effectiveFilePool.value
if (filePass.value) requestHeaders['X-FilePass'] = filePass.value
if (fileExpire.value) requestHeaders['X-FileExpire'] = fileExpire.value.toString()
if (props.bundleId) requestHeaders['X-FileBundle'] = props.bundleId
- const upload = new tus.Upload(file.file, {
+ const upload = new tus.Upload(file.file as any, {
endpoint: '/api/tus',
retryDelays: [0, 3000, 5000, 10000, 20000],
removeFingerprintOnSuccess: false,
diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs
index 6b4068a..0f48c8a 100644
--- a/DysonNetwork.Drive/Storage/FileController.cs
+++ b/DysonNetwork.Drive/Storage/FileController.cs
@@ -360,7 +360,7 @@ public class FileController(
return file;
}
- catch (Exception ex)
+ catch (Exception)
{
await transaction.RollbackAsync();
throw;