Update filemanager (🚧 in construction)
This commit is contained in:
parent
0fe469b5c6
commit
11dc6b2132
14 changed files with 925 additions and 223 deletions
271
client-electron/src/hooks/filemanager-android.ts
Normal file
271
client-electron/src/hooks/filemanager-android.ts
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
import { create } from 'zustand'
|
||||
import useAdb from './useAdb'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { type LinuxFileType } from '@yume-chan/adb'
|
||||
import { fromUnixTime } from 'date-fns'
|
||||
import { Consumable, ReadableStream, WritableStream } from '@yume-chan/stream-extra'
|
||||
import JSZip from 'jszip'
|
||||
|
||||
export interface AndroidFile {
|
||||
filename: string
|
||||
type: LinuxFileType
|
||||
size: bigint
|
||||
dateModified: Date
|
||||
}
|
||||
|
||||
interface FileManagerAndroidHook {
|
||||
rootPath: string
|
||||
currentPath: string
|
||||
pushPath: (path: string) => void
|
||||
popPath: () => void
|
||||
setCurrentPath: (path: string) => void
|
||||
scanPath: (path?: string) => Promise<AndroidFile[] | undefined>
|
||||
pushFile: (file: File, targetPath: string) => Promise<void>
|
||||
pushFiles: (files: File[], targetPath: string) => Promise<void>
|
||||
createDirectory: (dirName: string) => Promise<void>
|
||||
delete: (filename: string) => Promise<void>
|
||||
rename: (filename: string, newName: string) => Promise<void>
|
||||
download: (files: AndroidFile[]) => Promise<void>
|
||||
}
|
||||
|
||||
const useFileManager = create<FileManagerAndroidHook>((set, get) => ({
|
||||
rootPath: '/mnt/sdcard/coffeevending',
|
||||
currentPath: '',
|
||||
pushPath(path) {
|
||||
set({ currentPath: get().currentPath + '/' + path })
|
||||
console.log('currentPath', get().currentPath)
|
||||
},
|
||||
popPath() {
|
||||
set({ currentPath: get().currentPath.slice(0, get().currentPath.lastIndexOf('/')) })
|
||||
console.log('currentPath', get().currentPath)
|
||||
},
|
||||
setCurrentPath(path) {
|
||||
set({ currentPath: path })
|
||||
},
|
||||
async scanPath(path) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const sync = await adb.sync()
|
||||
try {
|
||||
console.log('scanning path', get().rootPath + path)
|
||||
const entries = await sync.readdir(get().rootPath + path)
|
||||
return entries.reduce<AndroidFile[]>((acc, entry) => {
|
||||
if (entry.name === '.' || entry.name === '..') {
|
||||
return acc
|
||||
}
|
||||
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
filename: entry.name,
|
||||
type: entry.type,
|
||||
size: entry.size,
|
||||
dateModified: fromUnixTime(Number(entry.mtime))
|
||||
}
|
||||
]
|
||||
}, [])
|
||||
} finally {
|
||||
await sync.dispose()
|
||||
}
|
||||
},
|
||||
async pushFile(file, targetPath) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = await file.arrayBuffer()
|
||||
const sync = await adb.sync()
|
||||
try {
|
||||
await sync.write({
|
||||
filename: targetPath + '/' + file.name,
|
||||
file: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new Consumable(new Uint8Array(buffer)))
|
||||
controller.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
await sync.dispose()
|
||||
}
|
||||
},
|
||||
async pushFiles(files, targetPath) {
|
||||
for (const file of files) {
|
||||
await get().pushFile(file, targetPath)
|
||||
}
|
||||
},
|
||||
async createDirectory(name) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const process = await adb.subprocess.spawn('mkdir ' + get().rootPath + '/' + name)
|
||||
process.stderr.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.error(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
process.stdout.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if ((await process.exit) != 0) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to create directory',
|
||||
description: 'Please try again'
|
||||
})
|
||||
}
|
||||
},
|
||||
async delete(filename) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const process = await adb.subprocess.spawn('rm ' + get().rootPath + '/' + filename)
|
||||
process.stderr.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.error(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
process.stdout.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if ((await process.exit) != 0) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to delete file',
|
||||
description: 'Please try again'
|
||||
})
|
||||
}
|
||||
},
|
||||
async rename(filename, newName) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const process = await adb.subprocess.spawn(
|
||||
'mv ' + get().rootPath + '/' + filename + ' ' + get().rootPath + '/' + newName
|
||||
)
|
||||
process.stderr.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.error(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
process.stdout.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if ((await process.exit) != 0) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to rename file',
|
||||
description: 'Please try again'
|
||||
})
|
||||
}
|
||||
},
|
||||
async download(files) {
|
||||
const adb = useAdb.getState().adb
|
||||
if (!adb) {
|
||||
toast({
|
||||
duration: 3000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: 'Please connect Adb first'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const sync = await adb.sync()
|
||||
try {
|
||||
const zip = new JSZip()
|
||||
for (const file of files) {
|
||||
const buffer: Uint8Array[] = []
|
||||
const fileStream = sync.read(get().rootPath + '/' + file.filename)
|
||||
await fileStream.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
buffer.push(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
zip.file(file.filename, new Blob(buffer))
|
||||
}
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'download.zip'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} finally {
|
||||
await sync.dispose()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
export default useFileManager
|
||||
Loading…
Add table
Add a link
Reference in a new issue