From 7b32a7eba4d5944142a3b40616d37b0862087371 Mon Sep 17 00:00:00 2001
From: retoor <retoor@molodetz.nl>
Date: Sun, 23 Mar 2025 13:57:20 +0100
Subject: [PATCH] Update drive.

---
 Makefile                       |  2 +-
 src/snek/app.py                | 16 +++++--
 src/snek/model/drive.py        |  8 +++-
 src/snek/model/drive_item.py   | 11 ++++-
 src/snek/service/drive.py      | 84 ++++++++++++++++++++++++++++------
 src/snek/service/drive_item.py |  1 +
 src/snek/system/terminal.py    | 63 +++++++++++++++++++++----
 src/snek/view/drive.py         | 30 ++++++++++++
 8 files changed, 187 insertions(+), 28 deletions(-)
 create mode 100644 src/snek/view/drive.py

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)