feat: added file management functionality and API operations
- Improved functionality for file management has been added. - Added file system management functionality through IPC. - Added functionality to interact with files including selection, upload, deletion, and batch operations. - Added new file operations to the custom API, including file select, upload, delete, batch upload, and batch delete functions. - Implemented feature to select and upload files via API.
This commit is contained in:
parent
2bad5a1184
commit
76d1f0bb1e
@ -1,4 +1,5 @@
|
||||
import { app, dialog } from 'electron'
|
||||
import * as crypto from 'crypto'
|
||||
import { app, dialog, OpenDialogOptions } from 'electron'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@ -6,9 +7,11 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
interface FileMetadata {
|
||||
id: string
|
||||
name: string
|
||||
fileName: string
|
||||
path: string
|
||||
createdAt: Date
|
||||
size: number
|
||||
ext: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export class File {
|
||||
@ -25,41 +28,103 @@ export class File {
|
||||
}
|
||||
}
|
||||
|
||||
async selectFile(): Promise<FileMetadata | null> {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openFile']
|
||||
private async getFileHash(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('md5')
|
||||
const stream = fs.createReadStream(filePath)
|
||||
stream.on('data', (data) => hash.update(data))
|
||||
stream.on('end', () => resolve(hash.digest('hex')))
|
||||
stream.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async findDuplicateFile(filePath: string): Promise<FileMetadata | null> {
|
||||
const stats = fs.statSync(filePath)
|
||||
const fileSize = stats.size
|
||||
|
||||
const files = await fs.promises.readdir(this.storageDir)
|
||||
for (const file of files) {
|
||||
const storedFilePath = path.join(this.storageDir, file)
|
||||
const storedStats = fs.statSync(storedFilePath)
|
||||
|
||||
if (storedStats.size === fileSize) {
|
||||
const [originalHash, storedHash] = await Promise.all([
|
||||
this.getFileHash(filePath),
|
||||
this.getFileHash(storedFilePath)
|
||||
])
|
||||
|
||||
if (originalHash === storedHash) {
|
||||
const ext = path.extname(file)
|
||||
return {
|
||||
id: path.basename(file, ext),
|
||||
name: path.basename(filePath),
|
||||
fileName: file,
|
||||
path: storedFilePath,
|
||||
createdAt: storedStats.birthtime,
|
||||
size: storedStats.size,
|
||||
ext: ext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async selectFile(options?: OpenDialogOptions): Promise<FileMetadata[] | null> {
|
||||
const defaultOptions: OpenDialogOptions = {
|
||||
properties: ['openFile']
|
||||
}
|
||||
|
||||
const dialogOptions = { ...defaultOptions, ...options }
|
||||
|
||||
const result = await dialog.showOpenDialog(dialogOptions)
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const filePath = result.filePaths[0]
|
||||
const fileMetadataPromises = result.filePaths.map(async (filePath) => {
|
||||
const stats = fs.statSync(filePath)
|
||||
const ext = path.extname(filePath)
|
||||
|
||||
return {
|
||||
id: uuidv4(),
|
||||
name: path.basename(filePath),
|
||||
fileName: path.basename(filePath),
|
||||
path: filePath,
|
||||
createdAt: stats.birthtime,
|
||||
size: stats.size
|
||||
size: stats.size,
|
||||
ext: ext
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.all(fileMetadataPromises)
|
||||
}
|
||||
|
||||
async uploadFile(filePath: string): Promise<FileMetadata> {
|
||||
const id = uuidv4()
|
||||
const duplicateFile = await this.findDuplicateFile(filePath)
|
||||
|
||||
if (duplicateFile) {
|
||||
return duplicateFile
|
||||
}
|
||||
|
||||
const uuid = uuidv4()
|
||||
const name = path.basename(filePath)
|
||||
const destPath = path.join(this.storageDir, id)
|
||||
const ext = path.extname(name)
|
||||
const destPath = path.join(this.storageDir, uuid + ext)
|
||||
|
||||
await fs.promises.copyFile(filePath, destPath)
|
||||
const stats = await fs.promises.stat(destPath)
|
||||
|
||||
return {
|
||||
id,
|
||||
id: uuid,
|
||||
name,
|
||||
fileName: uuid + ext,
|
||||
path: destPath,
|
||||
createdAt: stats.birthtime,
|
||||
size: stats.size
|
||||
size: stats.size,
|
||||
ext: ext
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { BrowserWindow, ipcMain, session, shell } from 'electron'
|
||||
import { BrowserWindow, ipcMain, OpenDialogOptions, session, shell } from 'electron'
|
||||
|
||||
import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config'
|
||||
import { File } from './file'
|
||||
import AppUpdater from './updater'
|
||||
import { openFile, saveFile } from './utils/file'
|
||||
import { compress, decompress } from './utils/zip'
|
||||
import { createMinappWindow } from './window'
|
||||
|
||||
const fileManager = new File()
|
||||
|
||||
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
const { autoUpdater } = new AppUpdater(mainWindow)
|
||||
|
||||
@ -31,6 +34,28 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle('zip:compress', (_, text: string) => compress(text))
|
||||
ipcMain.handle('zip:decompress', (_, text: Buffer) => decompress(text))
|
||||
|
||||
ipcMain.handle('file:select', async (_, options?: OpenDialogOptions) => {
|
||||
return await fileManager.selectFile(options)
|
||||
})
|
||||
|
||||
ipcMain.handle('file:upload', async (_, filePath: string) => {
|
||||
return await fileManager.uploadFile(filePath)
|
||||
})
|
||||
|
||||
ipcMain.handle('file:delete', async (_, fileId: string) => {
|
||||
await fileManager.deleteFile(fileId)
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
ipcMain.handle('file:batchUpload', async (_, filePaths: string[]) => {
|
||||
return await fileManager.batchUploadFiles(filePaths)
|
||||
})
|
||||
|
||||
ipcMain.handle('file:batchDelete', async (_, fileIds: string[]) => {
|
||||
await fileManager.batchDeleteFiles(fileIds)
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
ipcMain.handle('minapp', (_, args) => {
|
||||
createMinappWindow({
|
||||
url: args.url,
|
||||
|
||||
7
src/preload/index.d.ts
vendored
7
src/preload/index.d.ts
vendored
@ -1,6 +1,8 @@
|
||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
import type { OpenDialogOptions } from 'electron'
|
||||
|
||||
import type FileMetadata from '../main/file'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
@ -20,6 +22,11 @@ declare global {
|
||||
reload: () => void
|
||||
compress: (text: string) => Promise<Buffer>
|
||||
decompress: (text: Buffer) => Promise<string>
|
||||
fileSelect: (options?: OpenDialogOptions) => Promise<FileMetadata[] | null>
|
||||
fileUpload: (filePath: string) => Promise<FileMetadata>
|
||||
fileDelete: (fileId: string) => Promise<{ success: boolean }>
|
||||
fileBatchUpload: (filePaths: string[]) => Promise<FileMetadata[]>
|
||||
fileBatchDelete: (fileIds: string[]) => Promise<{ success: boolean }>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { contextBridge, ipcRenderer, OpenDialogOptions } from 'electron'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
@ -15,7 +15,12 @@ const api = {
|
||||
ipcRenderer.invoke('save-file', path, content, options)
|
||||
},
|
||||
compress: (text: string) => ipcRenderer.invoke('zip:compress', text),
|
||||
decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text)
|
||||
decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text),
|
||||
fileSelect: (options?: OpenDialogOptions) => ipcRenderer.invoke('file:select', options),
|
||||
fileUpload: (filePath: string) => ipcRenderer.invoke('file:upload', filePath),
|
||||
fileDelete: (fileId: string) => ipcRenderer.invoke('file:delete', fileId),
|
||||
fileBatchUpload: (filePaths: string[]) => ipcRenderer.invoke('file:batchUpload', filePaths),
|
||||
fileBatchDelete: (fileIds: string[]) => ipcRenderer.invoke('file:batchDelete', fileIds)
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
@ -7,13 +7,23 @@ import styled from 'styled-components'
|
||||
const FilesPage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleSelectFile = async () => {
|
||||
const files = await window.api.fileSelect({
|
||||
properties: ['openFile', 'multiSelections']
|
||||
})
|
||||
for (const file of files || []) {
|
||||
const result = await window.api.fileUpload(file.path)
|
||||
console.log('Selected file:', file, result)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('files.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer>
|
||||
<Button>添加文件</Button>
|
||||
<Button onClick={handleSelectFile}>添加文件</Button>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user