270 lines
7.5 KiB
Python
270 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import uuid
|
|
import threading
|
|
from typing import Protocol, Self
|
|
|
|
import create_folder_widget
|
|
import pydantic
|
|
import state
|
|
import sync
|
|
import user
|
|
from file import File
|
|
from folder import Folder
|
|
from PyQt6.QtCore import QPoint, Qt
|
|
from PyQt6.QtGui import (
|
|
QAction,
|
|
QDragEnterEvent,
|
|
QDragMoveEvent,
|
|
QDropEvent,
|
|
QIcon,
|
|
)
|
|
from PyQt6.QtWidgets import (
|
|
QHBoxLayout,
|
|
QListWidget,
|
|
QListWidgetItem,
|
|
QMenu,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
from request_client import RequestClient
|
|
|
|
|
|
class DisplayProtocol(Protocol):
|
|
def name(self) -> str:
|
|
raise NotImplementedError
|
|
|
|
def delete(self) -> None:
|
|
raise NotImplementedError
|
|
|
|
def details(self, list: FileListWidget) -> QWidget:
|
|
raise NotImplementedError
|
|
|
|
def icon(self) -> QIcon:
|
|
raise NotImplementedError
|
|
|
|
def double_click(self, list: FileListWidget) -> None:
|
|
raise NotImplementedError
|
|
|
|
|
|
class ResponseProtocol(Protocol):
|
|
def items(self) -> list[DisplayProtocol]:
|
|
raise NotImplementedError
|
|
|
|
def update(self) -> ResponseProtocol:
|
|
raise NotImplementedError
|
|
|
|
def create_folder(self) -> QWidget | None:
|
|
raise NotImplementedError
|
|
|
|
|
|
class ListResponse(pydantic.BaseModel):
|
|
folder_id: uuid.UUID
|
|
files: list[File]
|
|
folders: list[Folder]
|
|
|
|
def items(self) -> list[DisplayProtocol]:
|
|
return self.files + self.folders
|
|
|
|
@classmethod
|
|
def get(cls, folder_id: uuid.UUID | None = None) -> Self:
|
|
params = {}
|
|
if folder_id:
|
|
params["folder_id"] = folder_id
|
|
return ListResponse.model_validate_json(
|
|
RequestClient().client.get("/folders", params=params).text
|
|
)
|
|
|
|
def update(self) -> ResponseProtocol:
|
|
return self.get(self.folder_id)
|
|
|
|
def create_folder(self, file_list: FileListWidget):
|
|
return create_folder_widget.CreateFolderWidget(
|
|
self.folder_id, file_list
|
|
)
|
|
|
|
|
|
@dataclasses.dataclass(slots=True)
|
|
class TlpResponse:
|
|
folders: list[Folder]
|
|
|
|
@staticmethod
|
|
def get() -> Self:
|
|
url = "/permissions/get_top_level_permitted_folders"
|
|
return TlpResponse(
|
|
pydantic.TypeAdapter(list[Folder]).validate_json(
|
|
RequestClient().client.get(url).text
|
|
)
|
|
)
|
|
|
|
def items(self) -> list[DisplayProtocol]:
|
|
return self.folders
|
|
|
|
def update(self) -> ResponseProtocol:
|
|
return self.get()
|
|
|
|
def create_folder(self, _: FileListWidget):
|
|
return # Not much to do
|
|
|
|
|
|
class FileListWidget(QListWidget):
|
|
def __init__(self, state: state.State):
|
|
super().__init__()
|
|
self.state = state
|
|
self.setAcceptDrops(True)
|
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
|
self.itemDoubleClicked.connect(self.show_item)
|
|
self.details_widget = None
|
|
|
|
self.responses: list[ResponseProtocol] = [ListResponse.get()]
|
|
self.update()
|
|
|
|
def show_item(self, qitem: QListWidgetItem) -> None:
|
|
row = self.row(qitem)
|
|
item = self.current_response().items()[row]
|
|
item.double_click(self)
|
|
|
|
def current_response(self) -> ResponseProtocol:
|
|
if not self.responses:
|
|
self.update_response()
|
|
return self.responses[-1]
|
|
|
|
def update_response(self):
|
|
self.responses[-1] = self.responses[-1].update()
|
|
self.update()
|
|
|
|
def dragEnterEvent(self, event: QDragEnterEvent):
|
|
if event.mimeData().hasUrls():
|
|
event.accept()
|
|
else:
|
|
event.ignore()
|
|
|
|
def dragMoveEvent(self, e: QDragMoveEvent | None) -> None:
|
|
return self.dragEnterEvent(e)
|
|
|
|
def dropEvent(self, event: QDropEvent):
|
|
event.accept()
|
|
for url in event.mimeData().urls():
|
|
file_path = url.toLocalFile()
|
|
self.upload_file(file_path)
|
|
|
|
def upload_file(self, file_path):
|
|
def _inner():
|
|
response = self.current_response()
|
|
if not isinstance(response, ListResponse):
|
|
return
|
|
File.create(file_path, response.folder_id)
|
|
self.update_response()
|
|
|
|
threading.Thread(target=_inner).start()
|
|
|
|
def add_item(self, item: DisplayProtocol):
|
|
widget = QListWidgetItem(item.name())
|
|
widget.setIcon(item.icon())
|
|
self.addItem(widget)
|
|
|
|
def show_context_menu(self, pos: QPoint):
|
|
item = self.itemAt(pos)
|
|
if item:
|
|
menu = QMenu(self)
|
|
details_action = QAction("Details", self)
|
|
details_action.triggered.connect(lambda: self.show_details(item))
|
|
delete_action = QAction("Delete", self)
|
|
delete_action.triggered.connect(lambda: self.delete_item(item))
|
|
menu.addAction(details_action)
|
|
menu.addAction(delete_action)
|
|
menu.exec(self.mapToGlobal(pos))
|
|
|
|
def show_details(self, item):
|
|
row = self.row(item)
|
|
item = self.current_response().items()[row]
|
|
self.details_widget = item.details(self)
|
|
self.details_widget.show()
|
|
|
|
def delete_item(self, item):
|
|
row = self.row(item)
|
|
item = self.current_response().items()[row]
|
|
item.delete()
|
|
self.update_response()
|
|
|
|
def update(self) -> None:
|
|
self.clear()
|
|
last = self.responses[-1]
|
|
for item in last.items():
|
|
self.add_item(item)
|
|
|
|
|
|
class Sidebar(QWidget):
|
|
def __init__(self, state: state.State, file_list: FileListWidget):
|
|
super().__init__()
|
|
self.state = state
|
|
self.file_list = file_list
|
|
self.user_widget = None
|
|
self.folder_widget = None
|
|
layout = QVBoxLayout()
|
|
self.setLayout(layout)
|
|
buttons = [
|
|
("User info", self.get_user),
|
|
("Log out", self.logout),
|
|
("Go back", self.go_back),
|
|
("Go root", self.go_root),
|
|
("Get permitted", self.get_tlp),
|
|
("Refresh", self.refresh),
|
|
("Create folder", self.create_folder),
|
|
("Sync all", self.sync_all),
|
|
]
|
|
for text, func in buttons:
|
|
button = QPushButton(text)
|
|
button.clicked.connect(func)
|
|
layout.addWidget(button)
|
|
layout.addStretch()
|
|
|
|
def get_user(self):
|
|
self.user_widget = user.UserWidget(self.state)
|
|
self.user_widget.show()
|
|
|
|
def logout(self):
|
|
self.state.logout()
|
|
|
|
def go_back(self):
|
|
if len(self.file_list.responses) >= 2:
|
|
self.file_list.responses.pop()
|
|
self.file_list.update()
|
|
|
|
def go_root(self):
|
|
self.file_list.responses.append(ListResponse.get())
|
|
self.file_list.update()
|
|
|
|
def get_tlp(self):
|
|
"""Get top level permitted folders"""
|
|
self.file_list.responses.append(TlpResponse.get())
|
|
self.file_list.update()
|
|
|
|
def refresh(self):
|
|
self.file_list.update_response()
|
|
|
|
def create_folder(self):
|
|
self.folder_widget = self.file_list.current_response().create_folder(
|
|
self.file_list
|
|
)
|
|
if self.folder_widget is not None:
|
|
self.folder_widget.show()
|
|
|
|
def sync_all(self):
|
|
threading.Thread(target=sync.SyncData.sync_all).start()
|
|
|
|
|
|
class MainFileWidget(QWidget):
|
|
def __init__(self, state: state.State):
|
|
super().__init__()
|
|
self.state = state
|
|
layout = QHBoxLayout()
|
|
self.file_list = FileListWidget(state)
|
|
self.sidebar = Sidebar(state, self.file_list)
|
|
layout.addWidget(self.sidebar)
|
|
layout.addWidget(self.file_list)
|
|
self.setLayout(layout)
|