Split up into multiple files
This commit is contained in:
parent
16475a27e8
commit
ff629b8903
@ -1,424 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Protocol, Self
|
||||
import datetime
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
import dotenv
|
||||
import httpx
|
||||
import keyring
|
||||
import pydantic
|
||||
from PyQt6.QtCore import Qt, QPoint
|
||||
from PyQt6.QtGui import (
|
||||
QAction,
|
||||
QDragEnterEvent,
|
||||
QDragMoveEvent,
|
||||
QDropEvent,
|
||||
QIcon,
|
||||
)
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QMainWindow,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QStackedWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
|
||||
class RequestClient:
|
||||
_client: httpx.Client = None
|
||||
|
||||
def __new__(cls) -> Self:
|
||||
if cls._client is None:
|
||||
url = os.environ.get("DRIVE_HOST_URL").strip()
|
||||
if not url:
|
||||
url = "localhost:3000"
|
||||
cls._client = httpx.Client(base_url=url)
|
||||
return super().__new__(cls)
|
||||
|
||||
@classmethod
|
||||
def set_token(cls, token: str):
|
||||
cls._client.headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.Client:
|
||||
return self._client
|
||||
|
||||
|
||||
class RegisterWidget(QWidget):
|
||||
def __init__(self, switcher: State):
|
||||
super().__init__()
|
||||
|
||||
self.switcher = switcher
|
||||
|
||||
self.username_label = QLabel("Username:")
|
||||
self.username_input = QLineEdit()
|
||||
|
||||
self.email_label = QLabel("Email:")
|
||||
self.email_input = QLineEdit()
|
||||
|
||||
self.password_label = QLabel("Password:")
|
||||
self.password_input = QLineEdit()
|
||||
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
|
||||
self.register_button = QPushButton("Register")
|
||||
self.register_button.clicked.connect(self.register)
|
||||
|
||||
self.login_button = QPushButton("Switch to Login")
|
||||
self.login_button.clicked.connect(self.switcher.switch_to_login)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.username_label)
|
||||
layout.addWidget(self.username_input)
|
||||
layout.addWidget(self.email_label)
|
||||
layout.addWidget(self.email_input)
|
||||
layout.addWidget(self.password_label)
|
||||
layout.addWidget(self.password_input)
|
||||
layout.addWidget(self.register_button)
|
||||
layout.addWidget(self.login_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def register(self):
|
||||
username = self.username_input.text()
|
||||
email = self.email_input.text()
|
||||
password = self.password_input.text()
|
||||
|
||||
if not username or not email or not password:
|
||||
QMessageBox.warning(self, "Input Error", "All fields are required")
|
||||
return
|
||||
|
||||
try:
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/users/register",
|
||||
data={
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
if response.is_success:
|
||||
token = response.json().get("access_token")
|
||||
if not token:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error getting the token",
|
||||
"Error getting the token",
|
||||
)
|
||||
self.switcher.login(token)
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", f"Registration failed: {response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||
|
||||
|
||||
class LoginWidget(QWidget):
|
||||
def __init__(self, switcher: State):
|
||||
super().__init__()
|
||||
|
||||
self.switcher = switcher
|
||||
|
||||
self.username_label = QLabel("Username:")
|
||||
self.username_input = QLineEdit()
|
||||
|
||||
self.password_label = QLabel("Password:")
|
||||
self.password_input = QLineEdit()
|
||||
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
|
||||
self.login_button = QPushButton("Login")
|
||||
self.login_button.clicked.connect(self.login)
|
||||
|
||||
self.register_button = QPushButton("Switch to Register")
|
||||
self.register_button.clicked.connect(self.switcher.switch_to_register)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.username_label)
|
||||
layout.addWidget(self.username_input)
|
||||
layout.addWidget(self.password_label)
|
||||
layout.addWidget(self.password_input)
|
||||
layout.addWidget(self.login_button)
|
||||
layout.addWidget(self.register_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def login(self):
|
||||
username = self.username_input.text()
|
||||
password = self.password_input.text()
|
||||
|
||||
if not username or not password:
|
||||
QMessageBox.warning(
|
||||
self, "Input Error", "Email and Password are required"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/users/authorize",
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
if response.is_success:
|
||||
access_token = response.json().get("access_token")
|
||||
if access_token:
|
||||
self.switcher.login(access_token)
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", "No access token received"
|
||||
)
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", f"Login failed: {response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||
|
||||
|
||||
class FileListWidget(QListWidget):
|
||||
def __init__(self, state: State):
|
||||
super().__init__()
|
||||
self.state = state
|
||||
self.setAcceptDrops(True)
|
||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||
|
||||
self.responses: list[ListResponse] = []
|
||||
self.update_response()
|
||||
|
||||
def current_response(self) -> ListResponse:
|
||||
if not self.responses:
|
||||
self.update_response()
|
||||
return self.responses[-1]
|
||||
|
||||
def update_response(self):
|
||||
if not self.responses:
|
||||
response = ListResponse.model_validate_json(
|
||||
RequestClient().client.get("/folders").text
|
||||
)
|
||||
self.responses.append(response)
|
||||
print(response.files)
|
||||
self.responses[-1] = ListResponse.model_validate_json(
|
||||
RequestClient()
|
||||
.client.get(
|
||||
"/folders", params={"folder_id": self.responses[-1].folder_id}
|
||||
)
|
||||
.text
|
||||
)
|
||||
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()
|
||||
print("hi")
|
||||
for url in event.mimeData().urls():
|
||||
file_path = url.toLocalFile()
|
||||
self.upload_file(file_path)
|
||||
|
||||
def upload_file(self, file_path):
|
||||
file_name = os.path.basename(file_path)
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
files = {"file": (file_name, f)}
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/files",
|
||||
files=files,
|
||||
params={
|
||||
"parent_folder": self.current_response().folder_id
|
||||
},
|
||||
)
|
||||
if response.is_success:
|
||||
QMessageBox.information(
|
||||
self, "Success", "File uploaded successfully"
|
||||
)
|
||||
self.update_response()
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", f"Upload failed: {response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||
|
||||
def add_file_item(self, file_name):
|
||||
item = QListWidgetItem(file_name)
|
||||
item.setIcon(QIcon.fromTheme("text-x-generic")) # File icon
|
||||
self.addItem(item)
|
||||
|
||||
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):
|
||||
file_name = item.text()
|
||||
QMessageBox.information(self, "Details", f"Details for {file_name}")
|
||||
|
||||
def delete_item(self, item):
|
||||
row = self.row(item)
|
||||
item = self.current_response().items()[row]
|
||||
item.delete()
|
||||
self.update_response()
|
||||
QMessageBox.information(self, "Delete", f"{item.name()} deleted")
|
||||
|
||||
def update(self) -> None:
|
||||
self.clear()
|
||||
last = self.responses[-1]
|
||||
for item in last.items():
|
||||
self.add_file_item(item.name())
|
||||
|
||||
|
||||
class Sidebar(QWidget):
|
||||
def __init__(self, state: State):
|
||||
super().__init__()
|
||||
self.state = state
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
# Add your sidebar buttons here
|
||||
for i in range(5): # Example buttons
|
||||
btn = QPushButton(f"Button {i+1}")
|
||||
layout.addWidget(btn)
|
||||
layout.addStretch()
|
||||
|
||||
|
||||
class MainFileWidget(QWidget):
|
||||
def __init__(self, state: State):
|
||||
super().__init__()
|
||||
self.state = state
|
||||
layout = QHBoxLayout()
|
||||
self.sidebar = Sidebar(state)
|
||||
self.file_list = FileListWidget(state)
|
||||
layout.addWidget(self.sidebar)
|
||||
layout.addWidget(self.file_list)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class DisplayProtocol(Protocol):
|
||||
def name(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def details(self) -> QWidget:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class File(pydantic.BaseModel):
|
||||
file_id: uuid.UUID
|
||||
file_name: str
|
||||
file_size: int
|
||||
sha512: str
|
||||
created_at: datetime.datetime
|
||||
updated_at: datetime.datetime
|
||||
|
||||
def name(self) -> str:
|
||||
return self.file_name
|
||||
|
||||
def delete(self) -> None:
|
||||
RequestClient().client.delete(
|
||||
"/files", params={"file_id": self.file_id}
|
||||
)
|
||||
|
||||
def details_button(self) -> QPushButton:
|
||||
# TODO
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Folder(pydantic.BaseModel):
|
||||
folder_id: uuid.UUID
|
||||
owner_id: int
|
||||
folder_name: str
|
||||
created_at: datetime.datetime
|
||||
|
||||
def name(self) -> str:
|
||||
return self.folder_name
|
||||
|
||||
def delete(self) -> None:
|
||||
RequestClient().client.delete(
|
||||
"/folders", params={"folder_id": self.folder_id}
|
||||
)
|
||||
|
||||
def details_button(self) -> QPushButton:
|
||||
# TODO
|
||||
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
|
||||
|
||||
|
||||
class State(QMainWindow):
|
||||
def __init__(self, url: str):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("Auth App")
|
||||
|
||||
self.stack = QStackedWidget()
|
||||
self.client = httpx.Client(base_url=url)
|
||||
self.register_widget = RegisterWidget(self)
|
||||
self.login_widget = LoginWidget(self)
|
||||
|
||||
self.stack.addWidget(
|
||||
self.login_widget
|
||||
) # Login widget is the first in the stack
|
||||
self.stack.addWidget(self.register_widget)
|
||||
|
||||
self.setCentralWidget(self.stack)
|
||||
|
||||
password = keyring.get_credential("auth_app", "access_token")
|
||||
if password is None:
|
||||
self.switch_to_login() # Start with the login widget
|
||||
|
||||
self.login(password.password)
|
||||
|
||||
def switch_to_login(self):
|
||||
self.stack.setCurrentWidget(self.login_widget)
|
||||
|
||||
def switch_to_register(self):
|
||||
self.stack.setCurrentWidget(self.register_widget)
|
||||
|
||||
def login(self, token: str):
|
||||
keyring.set_password("auth_app", "access_token", token)
|
||||
RequestClient().set_token(token)
|
||||
self.file_widget = MainFileWidget(self)
|
||||
self.stack.addWidget(self.file_widget)
|
||||
self.stack.setCurrentWidget(self.file_widget)
|
||||
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from state import State
|
||||
|
||||
if __name__ == "__main__":
|
||||
dotenv.load_dotenv()
|
||||
url = os.environ.get("DRIVE_HOST_URL").strip()
|
||||
url = os.environ.get("DRIVE_HOST_URL")
|
||||
if not url:
|
||||
url = "localhost:3000"
|
||||
else:
|
||||
url = url.strip()
|
||||
app = QApplication(sys.argv)
|
||||
window = State(url)
|
||||
window.show()
|
||||
|
136
desktop_client/auth.py
Normal file
136
desktop_client/auth.py
Normal file
@ -0,0 +1,136 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
import state
|
||||
from PyQt6.QtWidgets import (
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from request_client import RequestClient
|
||||
|
||||
|
||||
class RegisterWidget(QWidget):
|
||||
def __init__(self, switcher: state.State):
|
||||
super().__init__()
|
||||
|
||||
self.switcher = switcher
|
||||
|
||||
self.username_label = QLabel("Username:")
|
||||
self.username_input = QLineEdit()
|
||||
|
||||
self.email_label = QLabel("Email:")
|
||||
self.email_input = QLineEdit()
|
||||
|
||||
self.password_label = QLabel("Password:")
|
||||
self.password_input = QLineEdit()
|
||||
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
|
||||
self.register_button = QPushButton("Register")
|
||||
self.register_button.clicked.connect(self.register)
|
||||
|
||||
self.login_button = QPushButton("Switch to Login")
|
||||
self.login_button.clicked.connect(self.switcher.switch_to_login)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.username_label)
|
||||
layout.addWidget(self.username_input)
|
||||
layout.addWidget(self.email_label)
|
||||
layout.addWidget(self.email_input)
|
||||
layout.addWidget(self.password_label)
|
||||
layout.addWidget(self.password_input)
|
||||
layout.addWidget(self.register_button)
|
||||
layout.addWidget(self.login_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def register(self):
|
||||
username = self.username_input.text()
|
||||
email = self.email_input.text()
|
||||
password = self.password_input.text()
|
||||
|
||||
if not username or not email or not password:
|
||||
QMessageBox.warning(self, "Input Error", "All fields are required")
|
||||
return
|
||||
|
||||
try:
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/users/register",
|
||||
data={
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
if response.is_success:
|
||||
token = response.json().get("access_token")
|
||||
if not token:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error getting the token",
|
||||
"Error getting the token",
|
||||
)
|
||||
self.switcher.login(token)
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", f"Registration failed: {response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||
|
||||
|
||||
class LoginWidget(QWidget):
|
||||
def __init__(self, switcher: State):
|
||||
super().__init__()
|
||||
|
||||
self.switcher = switcher
|
||||
|
||||
self.username_label = QLabel("Username:")
|
||||
self.username_input = QLineEdit()
|
||||
|
||||
self.password_label = QLabel("Password:")
|
||||
self.password_input = QLineEdit()
|
||||
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
|
||||
self.login_button = QPushButton("Login")
|
||||
self.login_button.clicked.connect(self.login)
|
||||
|
||||
self.register_button = QPushButton("Switch to Register")
|
||||
self.register_button.clicked.connect(self.switcher.switch_to_register)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.username_label)
|
||||
layout.addWidget(self.username_input)
|
||||
layout.addWidget(self.password_label)
|
||||
layout.addWidget(self.password_input)
|
||||
layout.addWidget(self.login_button)
|
||||
layout.addWidget(self.register_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def login(self):
|
||||
username = self.username_input.text()
|
||||
password = self.password_input.text()
|
||||
|
||||
if not username or not password:
|
||||
QMessageBox.warning(self, "Input Error", "Email and Password are required")
|
||||
return
|
||||
|
||||
try:
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/users/authorize",
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
if response.is_success:
|
||||
access_token = response.json().get("access_token")
|
||||
if access_token:
|
||||
self.switcher.login(access_token)
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", "No access token received")
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", f"Login failed: {response.text}")
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
207
desktop_client/file_widgets.py
Normal file
207
desktop_client/file_widgets.py
Normal file
@ -0,0 +1,207 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import uuid
|
||||
from typing import Protocol
|
||||
|
||||
import httpx
|
||||
import pydantic
|
||||
import state
|
||||
from PyQt6.QtCore import QPoint, Qt
|
||||
from PyQt6.QtGui import QAction, QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon
|
||||
from PyQt6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
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) -> QWidget:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class File(pydantic.BaseModel):
|
||||
file_id: uuid.UUID
|
||||
file_name: str
|
||||
file_size: int
|
||||
sha512: str
|
||||
created_at: datetime.datetime
|
||||
updated_at: datetime.datetime
|
||||
|
||||
def name(self) -> str:
|
||||
return self.file_name
|
||||
|
||||
def delete(self) -> None:
|
||||
RequestClient().client.delete("/files", params={"file_id": self.file_id})
|
||||
|
||||
def details_button(self) -> QPushButton:
|
||||
# TODO
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Folder(pydantic.BaseModel):
|
||||
folder_id: uuid.UUID
|
||||
owner_id: int
|
||||
folder_name: str
|
||||
created_at: datetime.datetime
|
||||
|
||||
def name(self) -> str:
|
||||
return self.folder_name
|
||||
|
||||
def delete(self) -> None:
|
||||
RequestClient().client.delete("/folders", params={"folder_id": self.folder_id})
|
||||
|
||||
def details_button(self) -> QPushButton:
|
||||
# TODO
|
||||
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
|
||||
|
||||
|
||||
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.responses: list[ListResponse] = []
|
||||
self.update_response()
|
||||
|
||||
def current_response(self) -> ListResponse:
|
||||
if not self.responses:
|
||||
self.update_response()
|
||||
return self.responses[-1]
|
||||
|
||||
def update_response(self):
|
||||
if not self.responses:
|
||||
response = ListResponse.model_validate_json(
|
||||
RequestClient().client.get("/folders").text
|
||||
)
|
||||
self.responses.append(response)
|
||||
print(response.files)
|
||||
self.responses[-1] = ListResponse.model_validate_json(
|
||||
RequestClient()
|
||||
.client.get("/folders", params={"folder_id": self.responses[-1].folder_id})
|
||||
.text
|
||||
)
|
||||
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()
|
||||
print("hi")
|
||||
for url in event.mimeData().urls():
|
||||
file_path = url.toLocalFile()
|
||||
self.upload_file(file_path)
|
||||
|
||||
def upload_file(self, file_path):
|
||||
file_name = os.path.basename(file_path)
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
files = {"file": (file_name, f)}
|
||||
response = RequestClient().client.post(
|
||||
"http://localhost:3000/files",
|
||||
files=files,
|
||||
params={"parent_folder": self.current_response().folder_id},
|
||||
)
|
||||
if response.is_success:
|
||||
QMessageBox.information(
|
||||
self, "Success", "File uploaded successfully"
|
||||
)
|
||||
self.update_response()
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self, "Error", f"Upload failed: {response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
QMessageBox.critical(self, "HTTP Error", str(e))
|
||||
|
||||
def add_file_item(self, file_name):
|
||||
item = QListWidgetItem(file_name)
|
||||
item.setIcon(QIcon.fromTheme("text-x-generic")) # File icon
|
||||
self.addItem(item)
|
||||
|
||||
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):
|
||||
file_name = item.text()
|
||||
QMessageBox.information(self, "Details", f"Details for {file_name}")
|
||||
|
||||
def delete_item(self, item):
|
||||
row = self.row(item)
|
||||
item = self.current_response().items()[row]
|
||||
item.delete()
|
||||
self.update_response()
|
||||
QMessageBox.information(self, "Delete", f"{item.name()} deleted")
|
||||
|
||||
def update(self) -> None:
|
||||
self.clear()
|
||||
last = self.responses[-1]
|
||||
for item in last.items():
|
||||
self.add_file_item(item.name())
|
||||
|
||||
|
||||
class Sidebar(QWidget):
|
||||
def __init__(self, state: state.State):
|
||||
super().__init__()
|
||||
self.state = state
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
# Add your sidebar buttons here
|
||||
for i in range(5): # Example buttons
|
||||
btn = QPushButton(f"Button {i+1}")
|
||||
layout.addWidget(btn)
|
||||
layout.addStretch()
|
||||
|
||||
|
||||
class MainFileWidget(QWidget):
|
||||
def __init__(self, state: state.State):
|
||||
super().__init__()
|
||||
self.state = state
|
||||
layout = QHBoxLayout()
|
||||
self.sidebar = Sidebar(state)
|
||||
self.file_list = FileListWidget(state)
|
||||
layout.addWidget(self.sidebar)
|
||||
layout.addWidget(self.file_list)
|
||||
self.setLayout(layout)
|
24
desktop_client/request_client.py
Normal file
24
desktop_client/request_client.py
Normal file
@ -0,0 +1,24 @@
|
||||
import os
|
||||
from typing import Self
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class RequestClient:
|
||||
_client: httpx.Client = None
|
||||
|
||||
def __new__(cls) -> Self:
|
||||
if cls._client is None:
|
||||
url = os.environ.get("DRIVE_HOST_URL").strip()
|
||||
if not url:
|
||||
url = "localhost:3000"
|
||||
cls._client = httpx.Client(base_url=url)
|
||||
return super().__new__(cls)
|
||||
|
||||
@classmethod
|
||||
def set_token(cls, token: str):
|
||||
cls._client.headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.Client:
|
||||
return self._client
|
44
desktop_client/state.py
Normal file
44
desktop_client/state.py
Normal file
@ -0,0 +1,44 @@
|
||||
import auth
|
||||
import file_widgets
|
||||
import httpx
|
||||
import keyring
|
||||
import request_client
|
||||
from PyQt6.QtWidgets import QMainWindow, QStackedWidget
|
||||
|
||||
|
||||
class State(QMainWindow):
|
||||
def __init__(self, url: str):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("Auth App")
|
||||
|
||||
self.stack = QStackedWidget()
|
||||
self.client = httpx.Client(base_url=url)
|
||||
self.register_widget = auth.RegisterWidget(self)
|
||||
self.login_widget = auth.LoginWidget(self)
|
||||
|
||||
self.stack.addWidget(
|
||||
self.login_widget
|
||||
) # Login widget is the first in the stack
|
||||
self.stack.addWidget(self.register_widget)
|
||||
|
||||
self.setCentralWidget(self.stack)
|
||||
|
||||
password = keyring.get_credential("auth_app", "access_token")
|
||||
if password is None:
|
||||
self.switch_to_login() # Start with the login widget
|
||||
|
||||
self.login(password.password)
|
||||
|
||||
def switch_to_login(self):
|
||||
self.stack.setCurrentWidget(self.login_widget)
|
||||
|
||||
def switch_to_register(self):
|
||||
self.stack.setCurrentWidget(self.register_widget)
|
||||
|
||||
def login(self, token: str):
|
||||
keyring.set_password("auth_app", "access_token", token)
|
||||
request_client.RequestClient().set_token(token)
|
||||
self.file_widget = file_widgets.MainFileWidget(self)
|
||||
self.stack.addWidget(self.file_widget)
|
||||
self.stack.setCurrentWidget(self.file_widget)
|
Reference in New Issue
Block a user