Drive service.

This commit is contained in:
retoor 2025-02-04 23:38:13 +01:00
parent 084f8dba20
commit 6f9adfe67f
16 changed files with 168 additions and 23 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ snek.d*
*.zip
*.db*
cache
drive
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@ -26,8 +26,8 @@ from snek.view.register import RegisterView
from snek.view.rpc import RPCView
from snek.view.status import StatusView
from snek.view.web import WebView
from snek.view.upload import UploadView
# base64.urlsafe_b64encode(
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@ -81,6 +81,8 @@ class Application(BaseApplication):
self.router.add_view("/login.json", LoginView)
self.router.add_view("/register.html", RegisterView)
self.router.add_view("/register.json", RegisterView)
self.router.add_view("/drive.bin", UploadView)
self.router.add_view("/drive.bin/{uid}", UploadView)
self.router.add_get("/http-get", self.handle_http_get)
self.router.add_get("/http-photo", self.handle_http_photo)
self.router.add_get("/rpc.ws", RPCView)

View File

@ -5,6 +5,8 @@ from snek.mapper.channel_member import ChannelMemberMapper
from snek.mapper.channel_message import ChannelMessageMapper
from snek.mapper.notification import NotificationMapper
from snek.mapper.user import UserMapper
from snek.mapper.drive import DriveMapper
from snek.mapper.drive_item import DriveItemMapper
from snek.system.object import Object
@ -17,6 +19,8 @@ def get_mappers(app=None):
"channel": ChannelMapper(app=app),
"channel_message": ChannelMessageMapper(app=app),
"notification": NotificationMapper(app=app),
"drive_item": DriveItemMapper(app=app),
"drive": DriveMapper(app=app),
}
)

7
src/snek/mapper/drive.py Normal file
View File

@ -0,0 +1,7 @@
from snek.model.drive import DriveModel
from snek.system.mapper import BaseMapper
class DriveMapper(BaseMapper):
table_name = 'drive'
model_class = DriveModel

View File

@ -0,0 +1,7 @@
from snek.system.mapper import BaseMapper
from snek.model.drive_item import DriveItemModel
class DriveItemMapper(BaseMapper):
model_class = DriveItemModel
table_name = 'drive_item'

7
src/snek/model/drive.py Normal file
View File

@ -0,0 +1,7 @@
from snek.system.model import BaseModel,ModelField
class DriveModel(BaseModel):
user_uid = ModelField(name="user_uid", required=True)

View File

@ -0,0 +1,9 @@
from snek.system.model import BaseModel,ModelField
class DriveItemModel(BaseModel):
drive_uid = ModelField(name="drive_uid", required=True,kind=str)
name = ModelField(name="name", required=True,kind=str)
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)

View File

@ -8,6 +8,8 @@ from snek.service.notification import NotificationService
from snek.service.socket import SocketService
from snek.service.user import UserService
from snek.service.util import UtilService
from snek.service.drive import DriveService
from snek.service.drive_item import DriveItemService
from snek.system.object import Object
@ -23,6 +25,8 @@ def get_services(app):
"socket": SocketService(app=app),
"notification": NotificationService(app=app),
"util": UtilService(app=app),
"drive": DriveService(app=app),
"drive_item": DriveItemService(app=app)
}
)

View File

@ -29,10 +29,7 @@ class ChannelMessageService(BaseService):
model["html"] = template.render(**context)
except Exception as ex:
print(ex,flush=True)
print("RENDER",flush=True)
print("RECORD",context,flush=True)
print("AFTER RENDER",flush=True)
if await self.save(model):
return model
raise Exception(f"Failed to create channel message: {model.errors}.")

21
src/snek/service/drive.py Normal file
View File

@ -0,0 +1,21 @@
from snek.system.service import BaseService
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
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]

View File

@ -0,0 +1,18 @@
from snek.system.service import BaseService
class DriveItemService(BaseService):
mapper_name = "drive_item"
async def create(self, drive_uid, name, path, type_,size):
model = await self.new()
model['drive_uid'] = drive_uid
model['name'] = name
model['path'] = str(path)
model['file_type'] = type_
model['file_size'] = size
if await self.save(model):
return model
errors = await model.errors
raise Exception(f"Failed to create drive item: {errors}.")

View File

@ -189,24 +189,24 @@ class Socket extends EventHandler {
}
this.isConnecting = true;
return new Promise((resolve) => {
me.connectPromises.push(resolve);
this.connectPromises.push(resolve);
console.debug("Connecting..");
const ws = new WebSocket(me.url);
const ws = new WebSocket(this.url);
ws.onopen = () => {
me.ws = ws;
me.isConnected = true;
me.isConnecting = false;
this.ws = ws;
this.isConnected = true;
this.isConnecting = false;
ws.onmessage = (event) => {
me.onData(JSON.parse(event.data));
this.onData(JSON.parse(event.data));
};
ws.onclose = () => {
me.onClose();
this.onClose();
};
ws.onerror = () => {
me.onClose();
this.onClose();
};
me.connectPromises.forEach(resolver => resolver(me));
this.connectPromises.forEach(resolver => resolver(this));
};
});
}

View File

@ -7,14 +7,23 @@
// MIT License: This code is open-source and can be reused and distributed under the terms of the MIT License.
class ChatInputElement extends HTMLElement {
_chatWindow = null
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.component = document.createElement('div');
this.shadowRoot.appendChild(this.component);
}
set chatWindow(value){
this._chatWindow = value
}
get chatWindow(){
return this._chatWindow
}
get channelUid() {
return this.chatWindow.channel.uid
}
connectedCallback() {
const link = document.createElement('link');
link.rel = 'stylesheet';
@ -28,7 +37,8 @@ class ChatInputElement extends HTMLElement {
<upload-button></upload-button>
`;
this.textBox = this.container.querySelector('textarea');
this.uploadButton = this.container.querySelector('upload-button');
this.uploadButton.chatInput = this
this.textBox.addEventListener('input', (e) => {
this.dispatchEvent(new CustomEvent('input', { detail: e.target.value, bubbles: true }));
const message = e.target.value;
@ -56,4 +66,4 @@ class ChatInputElement extends HTMLElement {
}
}
customElements.define('chat-input', ChatInputElement);
customElements.define('chat-input', ChatInputElement);

View File

@ -13,7 +13,7 @@
class ChatWindowElement extends HTMLElement {
receivedHistory = false;
channel = null
constructor() {
super();
this.attachShadow({ mode: 'open' });
@ -47,6 +47,7 @@ class ChatWindowElement extends HTMLElement {
const channels = await app.rpc.getChannels();
const channel = channels[0];
this.channel = channel;
chatTitle.innerText = channel.name;
const channelElement = document.createElement('message-list');
@ -54,6 +55,7 @@ class ChatWindowElement extends HTMLElement {
this.container.appendChild(channelElement);
const chatInput = document.createElement('chat-input');
chatInput.chatWindow = this;
chatInput.addEventListener("submit", (e) => {
app.rpc.sendMessage(channel.uid, e.detail);
});
@ -75,4 +77,4 @@ class ChatWindowElement extends HTMLElement {
}
}
customElements.define('chat-window', ChatWindowElement);
customElements.define('chat-window', ChatWindowElement);

View File

@ -10,7 +10,7 @@ class UploadButtonElement extends HTMLElement {
super();
this.attachShadow({ mode: 'open' });
}
chatInput = null
async uploadFiles() {
const fileInput = this.container.querySelector('.file-input');
const uploadButton = this.container.querySelector('.upload-button');
@ -21,12 +21,13 @@ class UploadButtonElement extends HTMLElement {
const files = fileInput.files;
const formData = new FormData();
formData.append('channel_uid', this.chatInput.channelUid);
for (let i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
const request = new XMLHttpRequest();
request.open('POST', '/upload', true);
request.open('POST', '/drive.bin', true);
request.upload.onprogress = function (event) {
if (event.lengthComputable) {
@ -37,7 +38,6 @@ class UploadButtonElement extends HTMLElement {
request.onload = function () {
if (request.status === 200) {
progressBar.style.width = '0%';
uploadButton.innerHTML = '📤';
} else {
alert('Upload failed');
@ -50,7 +50,7 @@ class UploadButtonElement extends HTMLElement {
request.send(formData);
}
channelUid = null
connectedCallback() {
this.styleElement = document.createElement('style');
this.styleElement.innerHTML = `
@ -95,7 +95,6 @@ class UploadButtonElement extends HTMLElement {
}
`;
this.shadowRoot.appendChild(this.styleElement);
this.container = document.createElement('div');
this.container.innerHTML = `
<div class="upload-container">

56
src/snek/view/upload.py Normal file
View File

@ -0,0 +1,56 @@
from snek.system.view import BaseView
import aiofiles
import pathlib
from aiohttp import web
import uuid
UPLOAD_DIR = pathlib.Path("./drive")
class UploadView(BaseView):
async def get(self):
uid = self.request.match_info.get("uid")
drive_item = await self.services.drive_item.get(uid)
print(await drive_item.to_json(),flush=True)
return web.FileResponse(drive_item["path"])
async def post(self):
reader = await self.request.multipart()
files = []
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
channel_uid = None
drive = await self.services.drive.get_or_create(user_uid=self.request.session.get("uid"))
print(str(drive),flush=True)
while field := await reader.next():
if field.name == "channel_uid":
channel_uid = await field.text()
continue
filename = field.filename
if not filename:
continue
file_path = pathlib.Path(UPLOAD_DIR).joinpath(filename.strip("/").strip("."))
files.append(file_path)
async with aiofiles.open(str(file_path.absolute()), 'wb') as f:
while chunk := await field.read_chunk():
await f.write(chunk)
drive_item = await self.services.drive_item.create(drive["uid"],filename,str(file_path.absolute()),file_path.stat().st_size,file_path.suffix)
await self.services.chat.send(self.request.session.get("uid"),channel_uid,f"![{filename}](/drive.bin/{drive_item['uid']})")
print(drive_item,flush=True)
return web.json_response({"message": "Files uploaded successfully", "files": [str(file) for file in files],"channel_uid":channel_uid})