Drive service.
This commit is contained in:
parent
084f8dba20
commit
6f9adfe67f
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,8 @@ snek.d*
|
|||||||
*.zip
|
*.zip
|
||||||
*.db*
|
*.db*
|
||||||
cache
|
cache
|
||||||
|
drive
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
@ -26,8 +26,8 @@ from snek.view.register import RegisterView
|
|||||||
from snek.view.rpc import RPCView
|
from snek.view.rpc import RPCView
|
||||||
from snek.view.status import StatusView
|
from snek.view.status import StatusView
|
||||||
from snek.view.web import WebView
|
from snek.view.web import WebView
|
||||||
|
from snek.view.upload import UploadView
|
||||||
|
|
||||||
# base64.urlsafe_b64encode(
|
|
||||||
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +81,8 @@ class Application(BaseApplication):
|
|||||||
self.router.add_view("/login.json", LoginView)
|
self.router.add_view("/login.json", LoginView)
|
||||||
self.router.add_view("/register.html", RegisterView)
|
self.router.add_view("/register.html", RegisterView)
|
||||||
self.router.add_view("/register.json", 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-get", self.handle_http_get)
|
||||||
self.router.add_get("/http-photo", self.handle_http_photo)
|
self.router.add_get("/http-photo", self.handle_http_photo)
|
||||||
self.router.add_get("/rpc.ws", RPCView)
|
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.channel_message import ChannelMessageMapper
|
||||||
from snek.mapper.notification import NotificationMapper
|
from snek.mapper.notification import NotificationMapper
|
||||||
from snek.mapper.user import UserMapper
|
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
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +19,8 @@ def get_mappers(app=None):
|
|||||||
"channel": ChannelMapper(app=app),
|
"channel": ChannelMapper(app=app),
|
||||||
"channel_message": ChannelMessageMapper(app=app),
|
"channel_message": ChannelMessageMapper(app=app),
|
||||||
"notification": NotificationMapper(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.socket import SocketService
|
||||||
from snek.service.user import UserService
|
from snek.service.user import UserService
|
||||||
from snek.service.util import UtilService
|
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
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +25,8 @@ def get_services(app):
|
|||||||
"socket": SocketService(app=app),
|
"socket": SocketService(app=app),
|
||||||
"notification": NotificationService(app=app),
|
"notification": NotificationService(app=app),
|
||||||
"util": UtilService(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)
|
model["html"] = template.render(**context)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(ex,flush=True)
|
print(ex,flush=True)
|
||||||
print("RENDER",flush=True)
|
|
||||||
print("RECORD",context,flush=True)
|
|
||||||
|
|
||||||
print("AFTER RENDER",flush=True)
|
|
||||||
if await self.save(model):
|
if await self.save(model):
|
||||||
return model
|
return model
|
||||||
raise Exception(f"Failed to create channel message: {model.errors}.")
|
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;
|
this.isConnecting = true;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
me.connectPromises.push(resolve);
|
this.connectPromises.push(resolve);
|
||||||
console.debug("Connecting..");
|
console.debug("Connecting..");
|
||||||
|
|
||||||
const ws = new WebSocket(me.url);
|
const ws = new WebSocket(this.url);
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
me.ws = ws;
|
this.ws = ws;
|
||||||
me.isConnected = true;
|
this.isConnected = true;
|
||||||
me.isConnecting = false;
|
this.isConnecting = false;
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
me.onData(JSON.parse(event.data));
|
this.onData(JSON.parse(event.data));
|
||||||
};
|
};
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
me.onClose();
|
this.onClose();
|
||||||
};
|
};
|
||||||
ws.onerror = () => {
|
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.
|
// MIT License: This code is open-source and can be reused and distributed under the terms of the MIT License.
|
||||||
|
|
||||||
class ChatInputElement extends HTMLElement {
|
class ChatInputElement extends HTMLElement {
|
||||||
|
_chatWindow = null
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
this.component = document.createElement('div');
|
this.component = document.createElement('div');
|
||||||
this.shadowRoot.appendChild(this.component);
|
this.shadowRoot.appendChild(this.component);
|
||||||
}
|
}
|
||||||
|
set chatWindow(value){
|
||||||
|
this._chatWindow = value
|
||||||
|
|
||||||
|
}
|
||||||
|
get chatWindow(){
|
||||||
|
return this._chatWindow
|
||||||
|
}
|
||||||
|
get channelUid() {
|
||||||
|
return this.chatWindow.channel.uid
|
||||||
|
}
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet';
|
||||||
@ -28,7 +37,8 @@ class ChatInputElement extends HTMLElement {
|
|||||||
<upload-button></upload-button>
|
<upload-button></upload-button>
|
||||||
`;
|
`;
|
||||||
this.textBox = this.container.querySelector('textarea');
|
this.textBox = this.container.querySelector('textarea');
|
||||||
|
this.uploadButton = this.container.querySelector('upload-button');
|
||||||
|
this.uploadButton.chatInput = this
|
||||||
this.textBox.addEventListener('input', (e) => {
|
this.textBox.addEventListener('input', (e) => {
|
||||||
this.dispatchEvent(new CustomEvent('input', { detail: e.target.value, bubbles: true }));
|
this.dispatchEvent(new CustomEvent('input', { detail: e.target.value, bubbles: true }));
|
||||||
const message = e.target.value;
|
const message = e.target.value;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
class ChatWindowElement extends HTMLElement {
|
class ChatWindowElement extends HTMLElement {
|
||||||
receivedHistory = false;
|
receivedHistory = false;
|
||||||
|
channel = null
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
@ -47,6 +47,7 @@ class ChatWindowElement extends HTMLElement {
|
|||||||
|
|
||||||
const channels = await app.rpc.getChannels();
|
const channels = await app.rpc.getChannels();
|
||||||
const channel = channels[0];
|
const channel = channels[0];
|
||||||
|
this.channel = channel;
|
||||||
chatTitle.innerText = channel.name;
|
chatTitle.innerText = channel.name;
|
||||||
|
|
||||||
const channelElement = document.createElement('message-list');
|
const channelElement = document.createElement('message-list');
|
||||||
@ -54,6 +55,7 @@ class ChatWindowElement extends HTMLElement {
|
|||||||
this.container.appendChild(channelElement);
|
this.container.appendChild(channelElement);
|
||||||
|
|
||||||
const chatInput = document.createElement('chat-input');
|
const chatInput = document.createElement('chat-input');
|
||||||
|
chatInput.chatWindow = this;
|
||||||
chatInput.addEventListener("submit", (e) => {
|
chatInput.addEventListener("submit", (e) => {
|
||||||
app.rpc.sendMessage(channel.uid, e.detail);
|
app.rpc.sendMessage(channel.uid, e.detail);
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
}
|
}
|
||||||
|
chatInput = null
|
||||||
async uploadFiles() {
|
async uploadFiles() {
|
||||||
const fileInput = this.container.querySelector('.file-input');
|
const fileInput = this.container.querySelector('.file-input');
|
||||||
const uploadButton = this.container.querySelector('.upload-button');
|
const uploadButton = this.container.querySelector('.upload-button');
|
||||||
@ -21,12 +21,13 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
|
|
||||||
const files = fileInput.files;
|
const files = fileInput.files;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
formData.append('channel_uid', this.chatInput.channelUid);
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
formData.append('files[]', files[i]);
|
formData.append('files[]', files[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.open('POST', '/upload', true);
|
request.open('POST', '/drive.bin', true);
|
||||||
|
|
||||||
request.upload.onprogress = function (event) {
|
request.upload.onprogress = function (event) {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
@ -37,7 +38,6 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
|
|
||||||
request.onload = function () {
|
request.onload = function () {
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
progressBar.style.width = '0%';
|
|
||||||
uploadButton.innerHTML = '📤';
|
uploadButton.innerHTML = '📤';
|
||||||
} else {
|
} else {
|
||||||
alert('Upload failed');
|
alert('Upload failed');
|
||||||
@ -50,7 +50,7 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
|
|
||||||
request.send(formData);
|
request.send(formData);
|
||||||
}
|
}
|
||||||
|
channelUid = null
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.styleElement = document.createElement('style');
|
this.styleElement = document.createElement('style');
|
||||||
this.styleElement.innerHTML = `
|
this.styleElement.innerHTML = `
|
||||||
@ -95,7 +95,6 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
this.shadowRoot.appendChild(this.styleElement);
|
this.shadowRoot.appendChild(this.styleElement);
|
||||||
|
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="upload-container">
|
<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