Drive service.
This commit is contained in:
parent
084f8dba20
commit
6f9adfe67f
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,8 @@ snek.d*
|
||||
*.zip
|
||||
*.db*
|
||||
cache
|
||||
drive
|
||||
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
@ -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)
|
||||
|
@ -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
7
src/snek/mapper/drive.py
Normal 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
|
7
src/snek/mapper/drive_item.py
Normal file
7
src/snek/mapper/drive_item.py
Normal 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
7
src/snek/model/drive.py
Normal file
@ -0,0 +1,7 @@
|
||||
from snek.system.model import BaseModel,ModelField
|
||||
|
||||
|
||||
class DriveModel(BaseModel):
|
||||
|
||||
user_uid = ModelField(name="user_uid", required=True)
|
||||
|
9
src/snek/model/drive_item.py
Normal file
9
src/snek/model/drive_item.py
Normal 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)
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -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
21
src/snek/service/drive.py
Normal 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]
|
18
src/snek/service/drive_item.py
Normal file
18
src/snek/service/drive_item.py
Normal 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}.")
|
@ -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));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
56
src/snek/view/upload.py
Normal 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})
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user