84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
|
import asyncio
|
||
|
import os
|
||
|
|
||
|
from asynclink import AsyncLink
|
||
|
from lrucache import LRUCache
|
||
|
|
||
|
class File:
|
||
|
def __init__(self, filename):
|
||
|
self.filename = filename
|
||
|
self.fd = None
|
||
|
self.writable = False
|
||
|
|
||
|
def open_ro(self):
|
||
|
self.close()
|
||
|
self.fd = os.open(self.filename, os.O_RDONLY)
|
||
|
|
||
|
def open_rw(self):
|
||
|
self.close()
|
||
|
self.fd = os.open(self.filename, os.O_RDWR | os.O_CREAT)
|
||
|
self.writable = True
|
||
|
|
||
|
def write(self, pos, buffer, *, file_size=None):
|
||
|
if not self.writable:
|
||
|
self.open_rw()
|
||
|
if file_size is not None:
|
||
|
os.ftruncate(self.fd, file_size)
|
||
|
os.lseek(self.fd, pos, os.SEEK_SET)
|
||
|
os.write(self.fd, buffer)
|
||
|
|
||
|
def __getitem__(self, slice):
|
||
|
if self.fd is None:
|
||
|
self.open_ro()
|
||
|
os.lseek(self.fd, slice.start, os.SEEK_SET)
|
||
|
l = slice.stop - slice.start
|
||
|
data = os.read(self.fd, l)
|
||
|
if len(data) < l: raise EOFError("Error reading requested range")
|
||
|
return data
|
||
|
|
||
|
def close(self):
|
||
|
if self.fd is not None:
|
||
|
os.close(self.fd)
|
||
|
self.fd = self.writable = None
|
||
|
|
||
|
def __del__(self):
|
||
|
self.close()
|
||
|
|
||
|
|
||
|
class FileServer:
|
||
|
|
||
|
async def start(self):
|
||
|
self.alink = AsyncLink()
|
||
|
self.worker = asyncio.get_event_loop().run_in_executor(None, self.worker_thread, alink.to_sync)
|
||
|
|
||
|
async def stop(self):
|
||
|
await self.alink.stop()
|
||
|
await self.worker
|
||
|
|
||
|
def worker_thread(self, slink):
|
||
|
cache = LRUCache(File, capacity=10, maxage=5.0)
|
||
|
for req in slink:
|
||
|
with req as (command, msg, data):
|
||
|
if command == "upload":
|
||
|
req.set_result(self.upload(msg, data))
|
||
|
else:
|
||
|
raise NotImplementedError(f"Unhandled {command=} {msg}")
|
||
|
|
||
|
|
||
|
def upload(self, msg, data):
|
||
|
name = str(msg['name'])
|
||
|
size = int(msg['size'])
|
||
|
start = int(msg['start'])
|
||
|
end = int(msg['end'])
|
||
|
|
||
|
if not 0 <= start < end <= size:
|
||
|
raise OverflowError("Invalid range")
|
||
|
if end - start > 1<<20:
|
||
|
raise OverflowError("Too much data, max 1 MiB.")
|
||
|
if end - start != len(data):
|
||
|
raise ValueError("Data length does not match range")
|
||
|
f = self.cache[name]
|
||
|
f.write(start, data, file_size=size)
|
||
|
|
||
|
return {"written": len(data), **msg}
|