Compare commits
4 Commits
06d860c601
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a08f7cbe2 | ||
|
|
dd37238510 | ||
|
|
c8d5f335b1 | ||
|
|
bb80b3ee54 |
13
cista/api.py
13
cista/api.py
@@ -37,16 +37,23 @@ async def upload(req, ws):
|
|||||||
)
|
)
|
||||||
req = msgspec.json.decode(text, type=FileRange)
|
req = msgspec.json.decode(text, type=FileRange)
|
||||||
pos = req.start
|
pos = req.start
|
||||||
data = None
|
while True:
|
||||||
while pos < req.end and (data := await ws.recv()) and isinstance(data, bytes):
|
data = await ws.recv()
|
||||||
|
if not isinstance(data, bytes):
|
||||||
|
break
|
||||||
|
if len(data) > req.end - pos:
|
||||||
|
raise ValueError(
|
||||||
|
f"Expected up to {req.end - pos} bytes, got {len(data)} bytes"
|
||||||
|
)
|
||||||
sentsize = await alink(("upload", req.name, pos, data, req.size))
|
sentsize = await alink(("upload", req.name, pos, data, req.size))
|
||||||
pos += typing.cast(int, sentsize)
|
pos += typing.cast(int, sentsize)
|
||||||
|
if pos >= req.end:
|
||||||
|
break
|
||||||
if pos != req.end:
|
if pos != req.end:
|
||||||
d = f"{len(data)} bytes" if isinstance(data, bytes) else data
|
d = f"{len(data)} bytes" if isinstance(data, bytes) else data
|
||||||
raise ValueError(f"Expected {req.end - pos} more bytes, got {d}")
|
raise ValueError(f"Expected {req.end - pos} more bytes, got {d}")
|
||||||
# Report success
|
# Report success
|
||||||
res = StatusMsg(status="ack", req=req)
|
res = StatusMsg(status="ack", req=req)
|
||||||
print("ack", res)
|
|
||||||
await asend(ws, res)
|
await asend(ws, res)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ class File:
|
|||||||
self.open_rw()
|
self.open_rw()
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
if file_size is not None:
|
if file_size is not None:
|
||||||
|
assert pos + len(buffer) <= file_size
|
||||||
os.ftruncate(self.fd, file_size)
|
os.ftruncate(self.fd, file_size)
|
||||||
os.lseek(self.fd, pos, os.SEEK_SET)
|
if buffer:
|
||||||
os.write(self.fd, buffer)
|
os.lseek(self.fd, pos, os.SEEK_SET)
|
||||||
|
os.write(self.fd, buffer)
|
||||||
|
|
||||||
def __getitem__(self, slice):
|
def __getitem__(self, slice):
|
||||||
if self.fd is None:
|
if self.fd is None:
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ let modifiedTimer: any = null
|
|||||||
const updateModified = () => {
|
const updateModified = () => {
|
||||||
for (const doc of props.documents) doc.modified = formatUnixDate(doc.mtime)
|
for (const doc of props.documents) doc.modified = formatUnixDate(doc.mtime)
|
||||||
}
|
}
|
||||||
onMounted(() => { modifiedTimer = setInterval(updateModified, 1000) })
|
onMounted(() => { updateModified(); modifiedTimer = setInterval(updateModified, 1000) })
|
||||||
onUnmounted(() => { clearInterval(modifiedTimer) })
|
onUnmounted(() => { clearInterval(modifiedTimer) })
|
||||||
const mkdir = (doc: Document, name: string) => {
|
const mkdir = (doc: Document, name: string) => {
|
||||||
const control = connect(controlUrl, {
|
const control = connect(controlUrl, {
|
||||||
|
|||||||
@@ -16,13 +16,51 @@ type CloudFile = {
|
|||||||
cloudName: string
|
cloudName: string
|
||||||
cloudPos: number
|
cloudPos: number
|
||||||
}
|
}
|
||||||
|
function pasteHandler(event: ClipboardEvent) {
|
||||||
|
const items = Array.from(event.clipboardData?.items ?? [])
|
||||||
|
const infiles = [] as File[]
|
||||||
|
const dirs = [] as FileSystemDirectoryEntry[]
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.kind !== 'file') continue
|
||||||
|
const entry = item.webkitGetAsEntry()
|
||||||
|
if (entry?.isFile) {
|
||||||
|
const file = item.getAsFile()
|
||||||
|
infiles.push(file)
|
||||||
|
} else if (entry?.isDirectory) {
|
||||||
|
dirs.push(entry as FileSystemDirectoryEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (infiles.length || dirs.length) {
|
||||||
|
event.preventDefault()
|
||||||
|
uploadFiles(infiles)
|
||||||
|
for (const entry of dirs) pasteDirectory(entry, `${props.path!.join('/')}/${entry.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pasteDirectory = async (entry: FileSystemDirectoryEntry, loc: string) => {
|
||||||
|
const reader = entry.createReader()
|
||||||
|
const entries = await new Promise<any[]>(resolve => reader.readEntries(resolve))
|
||||||
|
const cloudfiles = [] as CloudFile[]
|
||||||
|
for (const entry of entries) {
|
||||||
|
const cloudName = `${loc}/${entry.name}`
|
||||||
|
if (entry.isFile) {
|
||||||
|
const file = await new Promise(resolve => entry.file(resolve)) as File
|
||||||
|
cloudfiles.push({file, cloudName, cloudPos: 0})
|
||||||
|
} else if (entry.isDirectory) {
|
||||||
|
await pasteDirectory(entry, cloudName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cloudfiles.length) uploadCloudFiles(cloudfiles)
|
||||||
|
}
|
||||||
function uploadHandler(event: Event) {
|
function uploadHandler(event: Event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const infiles = Array.from(event.dataTransfer?.files || event.target.files) as File[]
|
const input = event.target as HTMLInputElement | null
|
||||||
if (!infiles.length) return
|
const infiles = Array.from((input ?? (event as DragEvent).dataTransfer)?.files ?? []) as File[]
|
||||||
|
if (input) input.value = ''
|
||||||
|
if (infiles.length) uploadFiles(infiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFiles = (infiles: File[]) => {
|
||||||
const loc = props.path!.join('/')
|
const loc = props.path!.join('/')
|
||||||
let files = []
|
let files = []
|
||||||
for (const file of infiles) {
|
for (const file of infiles) {
|
||||||
@@ -32,6 +70,9 @@ function uploadHandler(event: Event) {
|
|||||||
cloudPos: 0,
|
cloudPos: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
uploadCloudFiles(files)
|
||||||
|
}
|
||||||
|
const uploadCloudFiles = (files: CloudFile[]) => {
|
||||||
const dotfiles = files.filter(f => f.cloudName.includes('/.'))
|
const dotfiles = files.filter(f => f.cloudName.includes('/.'))
|
||||||
if (dotfiles.length) {
|
if (dotfiles.length) {
|
||||||
documentStore.error = "Won't upload dotfiles"
|
documentStore.error = "Won't upload dotfiles"
|
||||||
@@ -165,10 +206,6 @@ const worker = async () => {
|
|||||||
const ws = await WSCreate()
|
const ws = await WSCreate()
|
||||||
while (upqueue.length) {
|
while (upqueue.length) {
|
||||||
const f = upqueue[0]
|
const f = upqueue[0]
|
||||||
if (f.cloudPos === f.file.size) {
|
|
||||||
upqueue.shift()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const start = f.cloudPos
|
const start = f.cloudPos
|
||||||
const end = Math.min(f.file.size, start + (1<<20))
|
const end = Math.min(f.file.size, start + (1<<20))
|
||||||
const control = { name: f.cloudName, size: f.file.size, start, end }
|
const control = { name: f.cloudName, size: f.file.size, start, end }
|
||||||
@@ -179,6 +216,7 @@ const worker = async () => {
|
|||||||
ws.sendMsg(control)
|
ws.sendMsg(control)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await ws.sendData(data)
|
await ws.sendData(data)
|
||||||
|
if (f.cloudPos === f.file.size) upqueue.shift()
|
||||||
}
|
}
|
||||||
if (upqueue.length) startWorker()
|
if (upqueue.length) startWorker()
|
||||||
uprogress.status = "idle"
|
uprogress.status = "idle"
|
||||||
@@ -196,8 +234,10 @@ onMounted(() => {
|
|||||||
// Need to prevent both to prevent browser from opening the file
|
// Need to prevent both to prevent browser from opening the file
|
||||||
addEventListener('dragover', uploadHandler)
|
addEventListener('dragover', uploadHandler)
|
||||||
addEventListener('drop', uploadHandler)
|
addEventListener('drop', uploadHandler)
|
||||||
|
addEventListener('paste', pasteHandler)
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
removeEventListener('paste', pasteHandler)
|
||||||
removeEventListener('dragover', uploadHandler)
|
removeEventListener('dragover', uploadHandler)
|
||||||
removeEventListener('drop', uploadHandler)
|
removeEventListener('drop', uploadHandler)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user