diff --git a/Makefile b/Makefile index 62d24ca..c9a7a28 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ run: $(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload install: - python3 -m venv .venv + python3.12 -m venv .venv $(PIP) install -e . diff --git a/src/snek/app.py b/src/snek/app.py index eb26333..daefbd0 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -38,6 +38,7 @@ from snek.view.search_user import SearchUserView from snek.view.avatar import AvatarView from snek.system.profiler import profiler_handler from snek.view.terminal import TerminalView, TerminalSocketView +from snek.view.drive import DriveView from concurrent.futures import ThreadPoolExecutor SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" @@ -67,6 +68,13 @@ class Application(BaseApplication): self.jinja2_env.add_extension(EmojiExtension) self.setup_router() + + self.cache = Cache(self) + self.services = get_services(app=self) + self.mappers = get_mappers(app=self) + self.on_startup.append(self.prepare_database) + + async def prepare_database(self,app): self.db.query("PRAGMA journal_mode=WAL") self.db.query("PRAGMA syncnorm=off") @@ -79,10 +87,8 @@ class Application(BaseApplication): self.db["channel_message"].create_index(["channel_uid","user_uid"]) except: pass - - self.cache = Cache(self) - self.services = get_services(app=self) - self.mappers = get_mappers(app=self) + + await app.services.drive.prepare_all() def setup_router(self): self.router.add_get("/", IndexView) @@ -117,6 +123,8 @@ class Application(BaseApplication): self.router.add_view("/threads.html", ThreadsView) self.router.add_view("/terminal.ws", TerminalSocketView) self.router.add_view("/terminal.html", TerminalView) + self.router.add_view("/drive.json", DriveView) + self.router.add_view("/drive/{drive}.json", DriveView) self.add_subapp( "/docs", diff --git a/src/snek/model/drive.py b/src/snek/model/drive.py index a310bbd..3936d97 100644 --- a/src/snek/model/drive.py +++ b/src/snek/model/drive.py @@ -4,4 +4,10 @@ from snek.system.model import BaseModel,ModelField class DriveModel(BaseModel): user_uid = ModelField(name="user_uid", required=True) - + name = ModelField(name='name', required=False, type=str) + + @property + async def items(self): + async for drive_item in self.app.services.drive_item.find(drive_uid=self['uid']): + yield drive_item + diff --git a/src/snek/model/drive_item.py b/src/snek/model/drive_item.py index 74b8deb..728cd89 100644 --- a/src/snek/model/drive_item.py +++ b/src/snek/model/drive_item.py @@ -1,5 +1,5 @@ from snek.system.model import BaseModel,ModelField - +import mimetypes class DriveItemModel(BaseModel): drive_uid = ModelField(name="drive_uid", required=True,kind=str) @@ -7,3 +7,12 @@ class DriveItemModel(BaseModel): path = ModelField(name="path", required=True,kind=str) file_type = ModelField(name="file_type", required=True,kind=str) file_size = ModelField(name="file_size", required=True,kind=int) + + @property + def extension(self): + return self['name'].split('.')[-1] + + @property + def mime_type(self): + mimetype,_ = mimetypes.guess_type(self['name']) + return mimetype diff --git a/src/snek/service/drive.py b/src/snek/service/drive.py index 9d409a8..b90a959 100644 --- a/src/snek/service/drive.py +++ b/src/snek/service/drive.py @@ -5,17 +5,75 @@ class DriveService(BaseService): mapper_name = "drive" - async def get_by_user(self, user_uid): - drives = [] - async for model in self.find(user_uid=user_uid): - drives.append(model) - return drives + EXTENSIONS_PICTURES = ["jpg","jpeg","png","gif","svg","webp","tiff"] + EXTENSIONS_VIDEOS = ["mp4","m4v","mov","wmv","webm","mkv","mpg","mpeg","avi","ogv","ogg","flv","3gp","3g2"] + EXTENSIONS_ARCHIVES = ["zip","rar","7z","tar","tar.gz","tar.xz","tar.bz2","tar.lzma","tar.lz"] + EXTENSIONS_AUDIO = ["mp3","wav","ogg","flac","m4a","wma","aac","opus","aiff","au","mid","midi"] + EXTENSIONS_DOCS = ["pdf","doc","docx","xls","xlsx","ppt","pptx","txt","md","json","csv","xml","html","css","js","py","sql","rs","toml","yml","yaml","ini","conf","config","log","csv","tsv","java","cs","csproj","scss","less","sass","json","lock","lock.json","jsonl"] - async def get_or_create(self, user_uid): - drives = await self.get_by_user(user_uid=user_uid) - if len(drives) == 0: - model = await self.new() - model['user_uid'] = user_uid - await self.save(model) - return model - return drives[0] + async def get_drive_name_by_extension(self, extension): + if extension.startswith("."): + extension = extension[1:] + if extension in self.EXTENSIONS_PICTURES: + return "Pictures" + if extension in self.EXTENSIONS_VIDEOS: + return "Videos" + if extension in self.EXTENSIONS_ARCHIVES: + return "Archives" + if extension in self.EXTENSIONS_AUDIO: + return "Audio" + if extension in self.EXTENSIONS_DOCS: + return "Documents" + return "My Drive" + + async def get_drive_by_extension(self,user_uid, extension): + name = await self.get_drive_name_by_extension(extension) + return await self.get_or_create(user_uid=user_uid,name=name) + + async def get_by_user(self, user_uid,name=None): + kwargs = dict( + user_uid = user_uid + ) + async for model in self.find(**kwargs): + if not name: + yield model + elif model['name'] == name: + yield model + elif not model['name'] and name == 'My Drive': + model['name'] = 'My Drive' + await self.save(model) + yield model + + async def get_or_create(self, user_uid,name=None,extensions=None): + kwargs = dict(user_uid=user_uid) + if name: + kwargs['name'] = name + async for model in self.get_by_user(**kwargs): + return model + + model = await self.new() + model['user_uid'] = user_uid + model['name'] = name + await self.save(model) + return model + + async def prepare_default_drives(self): + async for drive_item in self.services.drive_item.find(): + extension = drive_item.extension + drive = await self.get_drive_by_extension(drive_item['user_uid'],extension) + if not drive_item['drive_uid'] == drive['uid']: + drive_item['drive_uid'] = drive['uid'] + await self.services.drive_item.save(drive_item) + + async def prepare_default_drives_for_user(self, user_uid): + await self.get_or_create(user_uid=user_uid,name="My Drive") + await self.get_or_create(user_uid=user_uid,name="Shared Drive") + await self.get_or_create(user_uid=user_uid,name="Pictures") + await self.get_or_create(user_uid=user_uid,name="Videos") + await self.get_or_create(user_uid=user_uid,name="Archives") + await self.get_or_create(user_uid=user_uid,name="Documents") + + async def prepare_all(self): + await self.prepare_default_drives() + async for user in self.services.user.find(): + await self.prepare_default_drives_for_user(user['uid']) diff --git a/src/snek/service/drive_item.py b/src/snek/service/drive_item.py index 058f55e..05a7da8 100644 --- a/src/snek/service/drive_item.py +++ b/src/snek/service/drive_item.py @@ -10,6 +10,7 @@ class DriveItemService(BaseService): model['drive_uid'] = drive_uid model['name'] = name model['path'] = str(path) + model['extension'] = str(name).split(".")[-1] model['file_type'] = type_ model['file_size'] = size if await self.save(model): diff --git a/src/snek/system/terminal.py b/src/snek/system/terminal.py index 0b012ba..3495bef 100644 --- a/src/snek/system/terminal.py +++ b/src/snek/system/terminal.py @@ -13,20 +13,66 @@ commands = { } class TerminalSession: + + async def ensure_process(self): + if self.process: + return + self.process = await asyncio.create_subprocess_exec( + *self.command.split(" "), + stdin=self.slave, + stdout=self.slave, + stderr=self.slave,bufsize=0, + universal_newlines=True + ) + + def __init__(self,command): self.master, self.slave = pty.openpty() self.sockets =[] self.buffer = b'' - self.process = subprocess.Popen( - command.split(" "), - stdin=self.slave, - stdout=self.slave, - stderr=self.slave, - bufsize=0, - universal_newlines=True - ) + self.process = None + + #self.process = subprocess.Popen( + # command.split(" "), + # stdin=self.slave, + # stdout=self.slave, + # stderr=self.slave, + # bufsize=0, + # universal_newlines=True + #) + async def read_output(self, ws): + await self.ensure_process() + self.sockets.append(ws) + if len(self.sockets) > 1: + start = self.buffer.index(b'\n') + await ws.send_bytes(self.buffer[start:]) + return + while True: + try: + async for data in self.process.stdout: + if not data: + break + self.buffer += data + if len(self.buffer) > 10000: + self.buffer = self.buffer[:-10000] + try: + for ws in self.sockets: await ws.send_bytes(data) # Send raw bytes for ANSI support + except: + self.sockets.remove(ws) + except: + print("Terminating process") + self.process.terminate() + print("Terminated process") + for ws in self.sockets: + try: + await ws.close() + except Exception: + pass + break + + async def read_outputa(self, ws): loop = asyncio.get_event_loop() self.sockets.append(ws) if len(self.sockets) > 1: @@ -57,6 +103,7 @@ class TerminalSession: break async def write_input(self, data): + await self.ensure_process() os.write(self.master, data.encode()) diff --git a/src/snek/view/drive.py b/src/snek/view/drive.py new file mode 100644 index 0000000..3e10c90 --- /dev/null +++ b/src/snek/view/drive.py @@ -0,0 +1,30 @@ +from snek.system.view import BaseView +from aiohttp import web + +class DriveView(BaseView): + + login_required = True + + async def get(self): + + drive_uid = self.request.match_info.get("drive") + + if drive_uid: + drive = await self.services.drive.get(uid=drive_uid) + drive_items = [] + async for item in drive.items: + drive_items.append(item.record) + return web.json_response(drive_items) + + user = await self.services.user.get(uid=self.session.get("uid")) + + + drives = [] + async for drive in self.services.drive.get_by_user(user['uid']): + record = drive.record + record['items'] = [] + async for item in drive.items: + record['items'].append(item.record) + drives.append(record) + + return web.json_response(drives)