feat(remote): 文件传输改用Electron IPC通道
- 主进程新增4个IPC handler处理远程文件操作 - 前端通过IPC调用而非浏览器fetch访问远程API - Remote服务新增3003端口专门处理文件传输 - 上传使用文件路径方案,下载使用保存对话框方案
This commit is contained in:
@@ -68,6 +68,18 @@ export const saveDeviceData = async (deviceName: string, lastConnected: string):
|
||||
await getApi().post<null>('saveData', { deviceName, lastConnected })
|
||||
}
|
||||
|
||||
export const fetchRemoteDrives = async (
|
||||
serverHost: string,
|
||||
port: number,
|
||||
password?: string
|
||||
): Promise<RemoteFileItem[]> => {
|
||||
const result = await window.electronAPI.remoteFetchDrives(serverHost, port, password)
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to fetch drives')
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const fetchRemoteFiles = async (
|
||||
serverHost: string,
|
||||
port: number,
|
||||
@@ -75,92 +87,27 @@ export const fetchRemoteFiles = async (
|
||||
password?: string,
|
||||
allowSystem: boolean = true
|
||||
): Promise<RemoteFileItem[]> => {
|
||||
let url = `http://${serverHost}:${port}/api/files/browse?path=${encodeURIComponent(path)}&allowSystem=${allowSystem}`
|
||||
if (password) {
|
||||
url += `&password=${encodeURIComponent(password)}`
|
||||
const result = await window.electronAPI.remoteFetchFiles(serverHost, port, path, password)
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to fetch files')
|
||||
}
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch remote files: ${response.statusText}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
const items = data.items || []
|
||||
return items.map((item: { name: string; isDirectory: boolean; size: number; modified: Date }) => ({
|
||||
name: item.name,
|
||||
path: data.currentPath ? `${data.currentPath}/${item.name}` : item.name,
|
||||
type: item.isDirectory ? 'dir' : 'file',
|
||||
size: item.size,
|
||||
modified: item.modified?.toString(),
|
||||
}))
|
||||
}
|
||||
|
||||
export const fetchRemoteDrives = async (
|
||||
serverHost: string,
|
||||
port: number,
|
||||
password?: string
|
||||
): Promise<RemoteFileItem[]> => {
|
||||
let url = `http://${serverHost}:${port}/api/files/drives`
|
||||
if (password) {
|
||||
url += `?password=${encodeURIComponent(password)}`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch remote drives: ${response.statusText}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
const items = data.items || []
|
||||
return items.map((item: { name: string; isDirectory: boolean; size: number }) => ({
|
||||
name: item.name,
|
||||
path: item.name,
|
||||
type: item.isDirectory ? 'dir' : 'file',
|
||||
size: item.size,
|
||||
modified: '',
|
||||
}))
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const uploadFileToRemote = async (
|
||||
serverHost: string,
|
||||
port: number,
|
||||
file: File,
|
||||
filePath: string,
|
||||
remotePath: string,
|
||||
password?: string,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<void> => {
|
||||
let url = `http://${serverHost}:${port}/api/files/upload`
|
||||
if (password) {
|
||||
url += `?password=${encodeURIComponent(password)}`
|
||||
onProgress?.(50)
|
||||
const result = await window.electronAPI.remoteUploadFile(serverHost, port, filePath, remotePath, password)
|
||||
onProgress?.(100)
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Upload failed')
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
if (remotePath) {
|
||||
formData.append('path', remotePath)
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.upload.addEventListener('progress', (event) => {
|
||||
if (event.lengthComputable && onProgress) {
|
||||
const progress = Math.round((event.loaded / event.total) * 100)
|
||||
onProgress(progress)
|
||||
}
|
||||
})
|
||||
|
||||
xhr.addEventListener('load', () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`Upload failed: ${xhr.statusText}`))
|
||||
}
|
||||
})
|
||||
|
||||
xhr.addEventListener('error', () => {
|
||||
reject(new Error('Upload failed'))
|
||||
})
|
||||
|
||||
xhr.open('POST', url)
|
||||
xhr.send(formData)
|
||||
})
|
||||
}
|
||||
|
||||
export const downloadFileFromRemote = async (
|
||||
@@ -170,15 +117,14 @@ export const downloadFileFromRemote = async (
|
||||
remotePath: string,
|
||||
password?: string,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<Blob> => {
|
||||
let url = `http://${serverHost}:${port}/api/files/${encodeURIComponent(fileName)}`
|
||||
if (password) {
|
||||
url += `?password=${encodeURIComponent(password)}`
|
||||
): Promise<void> => {
|
||||
onProgress?.(50)
|
||||
const result = await window.electronAPI.remoteDownloadFile(serverHost, port, fileName, remotePath, password)
|
||||
onProgress?.(100)
|
||||
if (!result.success) {
|
||||
if (result.canceled) {
|
||||
return
|
||||
}
|
||||
throw new Error(result.error || 'Download failed')
|
||||
}
|
||||
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Download failed: ${response.statusText}`)
|
||||
}
|
||||
return await response.blob()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react'
|
||||
import type { FileItem } from '@/lib/api'
|
||||
import { type RemoteFileItem, uploadFileToRemote, downloadFileFromRemote, fetchSystemFileContent } from '../../api'
|
||||
import { type RemoteFileItem, uploadFileToRemote, downloadFileFromRemote } from '../../api'
|
||||
import { type TransferItem } from '../../types'
|
||||
import { LocalFilePanel } from './LocalFilePanel'
|
||||
import { RemoteFilePanel } from './RemoteFilePanel'
|
||||
@@ -40,10 +40,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
|
||||
setTransfers((prev) => [...prev, newTransfer])
|
||||
|
||||
try {
|
||||
const blob = await fetchSystemFileContent(localSelected.path)
|
||||
const file = new File([blob], localSelected.name, { type: blob.type })
|
||||
|
||||
await uploadFileToRemote(serverHost, port, file, remotePath, password, (progress) => {
|
||||
await uploadFileToRemote(serverHost, port, localSelected.path, remotePath, password, (progress) => {
|
||||
setTransfers((prev) =>
|
||||
prev.map((t) => (t.id === transferId ? { ...t, progress } : t))
|
||||
)
|
||||
@@ -67,7 +64,7 @@ export const FileTransferPage: React.FC<FileTransferPageProps> = ({ serverHost,
|
||||
} finally {
|
||||
setTransferring(false)
|
||||
}
|
||||
}, [localSelected, serverHost, port])
|
||||
}, [localSelected, serverHost, port, remotePath, password])
|
||||
|
||||
const handleDownload = useCallback(async () => {
|
||||
if (!remoteSelected) return
|
||||
|
||||
Reference in New Issue
Block a user