cista-storage/server/fileio.py
2023-10-14 06:07:27 +03:00

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}