God, I hope I'm ready

This commit is contained in:
StNicolay 2024-08-21 05:22:00 +03:00
parent 04a27b592e
commit 5f0130d0d1
Signed by: StNicolay
GPG Key ID: 9693D04DCD962B0D
10 changed files with 193 additions and 98 deletions

View File

@ -3,12 +3,15 @@ from __future__ import annotations
import uuid
import file_widgets
from PyQt6.QtWidgets import QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QWidget
from PyQt6.QtWidgets import (QLineEdit, QMessageBox, QPushButton, QVBoxLayout,
QWidget)
from request_client import RequestClient
class CreateFolderWidget(QWidget):
def __init__(self, folder_id: uuid.UUID, file_list: file_widgets.FileListWidget):
def __init__(
self, folder_id: uuid.UUID, file_list: file_widgets.FileListWidget
):
super().__init__()
self.folder_id = folder_id
self.file_list = file_list
@ -22,16 +25,24 @@ class CreateFolderWidget(QWidget):
self.setLayout(layout)
def submit(self):
response = RequestClient().client.post(
"/folders",
json={
"folder_name": self.edit.text(),
"parent_folder_id": str(self.folder_id),
},
)
if not response.is_success:
QMessageBox.warning(None, "Error creating folder", response.text)
else:
QMessageBox.information(None, "Folder created", "Folder created")
self.file_list.update_response()
self.close()
try:
response = RequestClient().client.post(
"/folders",
json={
"folder_name": self.edit.text(),
"parent_folder_id": str(self.folder_id),
},
)
if not response.is_success:
QMessageBox.warning(
None, "Error creating folder", response.text
)
else:
QMessageBox.information(
None, "Folder created", "Folder created"
)
self.file_list.update_response()
self.close()
except Exception as e:
print(e)
QMessageBox.critical(self, "HTTP error", str(e))

View File

@ -6,6 +6,8 @@ import hashlib
import os
import uuid
import dateutil
import dateutil.tz
import file_widgets
import pydantic
from PyQt6.QtGui import QIcon
@ -32,12 +34,14 @@ class File(pydantic.BaseModel):
def details(self, list: file_widgets.FileListWidget) -> QWidget:
del list
file_size = self._format_bytes(self.file_size)
file_size = self._format_size(self.file_size)
file_size_text = f"{file_size[0]:.2f} {file_size[1]}"
created_at = self._format_date(self.created_at)
updated_at = self._format_date(self.updated_at)
details = (
f"file id: {self.file_id}\nfile_name: {self.file_name}\n"
+ f"file_size: {file_size_text}\n"
+ f"created at: {self.created_at}\nupdated at: {self.updated_at}"
+ f"created at: {created_at}\nupdated at: {updated_at}"
)
label = QLabel()
label.setWindowTitle("File info")
@ -45,7 +49,7 @@ class File(pydantic.BaseModel):
return label
@staticmethod
def _format_bytes(size: int):
def _format_size(size: int):
power = 2**10
n = 0
power_labels = {0: "", 1: "kibi", 2: "mebi", 3: "gibi", 4: "tebi"}
@ -54,6 +58,12 @@ class File(pydantic.BaseModel):
n += 1
return size, power_labels[n] + "bytes"
@staticmethod
def _format_date(date: datetime.datetime) -> str:
date = date.replace(tzinfo=dateutil.tz.tzutc())
date = date.astimezone(dateutil.tz.tzlocal())
return date.strftime("%Y-%m-%d %H:%M:%S")
def icon(self) -> QIcon:
return QIcon(resource_path("assets/file.png"))
@ -76,6 +86,7 @@ class File(pydantic.BaseModel):
def create(path: str, parent_id: uuid.UUID):
"""Upload the file"""
print(path)
file_name = os.path.basename(path)
try:
with open(path, "rb") as f:
@ -94,14 +105,21 @@ class File(pydantic.BaseModel):
QMessageBox.critical(None, "HTTP Error", str(e))
def download(self, path: str):
with open(path, "wb") as f, RequestClient().client.stream(
"GET", "/files", params={"file_id": self.file_id}
) as stream:
if not stream.is_success:
QMessageBox.warning(None, "Error downloading the file")
return
for data in stream.iter_bytes():
f.write(data)
try:
with open(path, "wb") as f, RequestClient().client.stream(
"GET", "/files", params={"file_id": self.file_id}
) as stream:
if not stream.is_success:
QMessageBox.warning(
None,
"Error downloading the file",
"Error downloading the file",
)
return
for data in stream.iter_bytes():
f.write(data)
except Exception as e:
QMessageBox.warning(None, "Error downloading the file", str(e))
def modify(self, path: str):
"""Upload the file"""

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import dataclasses
import uuid
import threading
from typing import Protocol, Self
import create_folder_widget
@ -151,10 +152,14 @@ class FileListWidget(QListWidget):
self.upload_file(file_path)
def upload_file(self, file_path):
response = self.current_response()
if not isinstance(response, ListResponse):
return
File.create(file_path, response.folder_id)
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())
@ -249,7 +254,7 @@ class Sidebar(QWidget):
self.folder_widget.show()
def sync_all(self):
sync.SyncData.sync_all()
threading.Thread(target=sync.SyncData.sync_all).start()
class MainFileWidget(QWidget):

View File

@ -33,7 +33,7 @@ class Folder(pydantic.BaseModel):
if not response:
QMessageBox.warning(None, "Error deleting folder", response.text)
def details(self, list: file_widgets.FileListWidget) -> QWidget:
def details(self, _: file_widgets.FileListWidget) -> QWidget:
import folder_info
return folder_info.FolderInfoWidget(self)
@ -49,13 +49,18 @@ class Folder(pydantic.BaseModel):
@staticmethod
def create(name: str, parent_id: uuid.UUID) -> uuid.UUID:
response = RequestClient().client.post(
"/folders",
json={
"folder_name": name,
"parent_folder_id": str(parent_id),
},
)
if not response.is_success:
QMessageBox.warning(None, "Error creating folder", response.text)
return uuid.UUID(response.text.strip('"'))
try:
response = RequestClient().client.post(
"/folders",
json={
"folder_name": name,
"parent_folder_id": str(parent_id),
},
)
if not response.is_success:
QMessageBox.warning(
None, "Error creating folder", response.text
)
return uuid.UUID(response.text.strip('"'))
except Exception as e:
QMessageBox.warning(None, "Error creating the folder", str(e))

View File

@ -30,34 +30,40 @@ class Permission(enum.StrEnum):
manage = enum.auto()
def set(self, folder_id: uuid.UUID, user_id: int):
response = RequestClient().client.post(
"/permissions",
json={
"folder_id": str(folder_id),
"permission_type": str(self),
"user_id": int(user_id),
},
)
if not response.is_success:
QMessageBox.warning(
None, "Error setting permissions", response.text
try:
response = RequestClient().client.post(
"/permissions",
json={
"folder_id": str(folder_id),
"permission_type": str(self),
"user_id": int(user_id),
},
)
if not response.is_success:
QMessageBox.warning(
None, "Error setting permissions", response.text
)
except Exception as e:
QMessageBox.warning(None, "Error setting permissions", str(e))
@staticmethod
def delete(folder_id: uuid.UUID, user_id: int, widget: FolderInfoWidget):
response = RequestClient().client.delete(
"/permissions",
params={
"folder_id": folder_id,
"user_id": user_id,
},
)
if not response.is_success:
QMessageBox.warning(
None, "Error deleting permissions", response.text
try:
response = RequestClient().client.delete(
"/permissions",
params={
"folder_id": folder_id,
"user_id": user_id,
},
)
else:
widget.redraw()
if not response.is_success:
QMessageBox.warning(
None, "Error deleting permissions", response.text
)
else:
widget.redraw()
except Exception as e:
QMessageBox.warning(None, "Error deleting permissions", str(e))
@dataclass(slots=True)
@ -66,12 +72,18 @@ class Permissions:
@staticmethod
def get(folder_id: uuid.UUID) -> Permissions:
mapping = pydantic.TypeAdapter(dict[str, Permission]).validate_json(
RequestClient()
.client.get("/permissions", params={"folder_id": folder_id})
.text
)
return Permissions(mapping)
try:
mapping = pydantic.TypeAdapter(
dict[str, Permission]
).validate_json(
RequestClient()
.client.get("/permissions", params={"folder_id": folder_id})
.text
)
return Permissions(mapping)
except Exception as e:
QMessageBox.warning(None, "Error getting permissions", str(e))
return Permissions({})
class FolderInfoWidget(QWidget):

View File

@ -10,7 +10,7 @@ class State(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Auth App")
self.setWindowTitle("Drive application")
self.stack = QStackedWidget()
self.register_widget = auth.RegisterWidget(self)
@ -39,10 +39,6 @@ class State(QMainWindow):
try:
keyring.set_password("auth_app", "access_token", token)
request_client.RequestClient().set_token(token)
sync.SyncData.sync_all(
# "/home/stnicolay/backups",
# uuid.UUID("0191397f-ae77-7b2a-bed7-9d28ed56a90a"),
)
self.file_widget = file_widgets.MainFileWidget(self)
self.stack.addWidget(self.file_widget)
self.stack.setCurrentWidget(self.file_widget)
@ -56,4 +52,5 @@ class State(QMainWindow):
def logout(self):
keyring.delete_password("auth_app", "access_token")
request_client.RequestClient().delete_token()
sync.SyncData.delete_all()
self.switch_to_login()

View File

@ -3,13 +3,15 @@ from __future__ import annotations
import datetime
import os
import uuid
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
from time import ctime
from file import File
from folder import Folder
from request_client import RequestClient
from sqlmodel import Field, Session, SQLModel, create_engine, delete, select
import multiprocessing
from PyQt6.QtWidgets import QMessageBox
class FolderStructure(Folder):
@ -41,20 +43,29 @@ class SyncData(SQLModel, table=True):
path: str
last_updated: datetime.datetime
def sync_one(self):
try:
merge_folder(
self.path,
FolderStructure.get_structure(self.folder_id),
self.last_updated,
)
except Exception as e:
SyncData.delete(self.folder_id)
QMessageBox.warning(
None,
"Error syncing folder",
f"Error syncing {self.path!r} folder:\n{e}",
)
@staticmethod
def sync_all():
with Session(engine) as s:
syncs = s.exec(select(SyncData)).fetchall()
with ThreadPoolExecutor(3) as pool:
map_ = pool.map(
lambda sync: merge_folder(
sync.path,
FolderStructure.get_structure(sync.folder_id),
sync.last_updated,
),
syncs,
)
tuple(map_)
with ProcessPoolExecutor(3) as pool:
tuple(pool.map(SyncData.sync_one, syncs))
@staticmethod
def new_sync(path: str, folder_id: uuid.UUID):
@ -66,7 +77,9 @@ class SyncData(SQLModel, table=True):
)
s.add(sync)
s.commit()
upload_folder(path, folder_id)
multiprocessing.Process(
target=lambda: upload_folder(path, folder_id)
).start()
@staticmethod
def get_for_folder(folder_id: uuid.UUID) -> SyncData | None:
@ -81,6 +94,12 @@ class SyncData(SQLModel, table=True):
s.exec(delete(SyncData).where(SyncData.folder_id == folder_id))
s.commit()
@staticmethod
def delete_all():
with Session(engine) as s:
s.exec(delete(SyncData))
s.commit()
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

View File

@ -29,7 +29,9 @@ class User(pydantic.BaseModel):
url = "/users/current"
response = RequestClient().client.get(url, params=params)
if not response.is_success:
QMessageBox.warning(None, "Error getting permissions", response.text)
QMessageBox.warning(
None, "Error getting permissions", response.text
)
return
return User.model_validate_json(response.text)

43
poetry.lock generated
View File

@ -427,18 +427,18 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena
[[package]]
name = "jaraco-context"
version = "5.3.0"
version = "6.0.1"
description = "Useful decorators and context managers"
optional = false
python-versions = ">=3.8"
files = [
{file = "jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266"},
{file = "jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"},
{file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"},
{file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "jaraco-functools"
@ -788,6 +788,20 @@ files = [
{file = "PyQt6_sip-13.8.0.tar.gz", hash = "sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4"},
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "1.0.1"
@ -804,13 +818,13 @@ cli = ["click (>=5.0)"]
[[package]]
name = "pywin32-ctypes"
version = "0.2.2"
version = "0.2.3"
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
optional = false
python-versions = ">=3.6"
files = [
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
{file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"},
{file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"},
]
[[package]]
@ -828,6 +842,17 @@ files = [
cryptography = ">=2.0"
jeepney = ">=0.6"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
@ -955,4 +980,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "6e84cef360a53faa7bba2d9bb57f01093ff0291c28c02aff4727b6c21fd6d715"
content-hash = "fd70e86969d9632a562a6755e8ff2f7e9d38e795da2f3c5c04eabe77d5723fdb"

View File

@ -13,6 +13,7 @@ pyqt6 = "^6.7.1"
keyring = "^25.3.0"
python-dotenv = "^1.0.1"
sqlmodel = "^0.0.21"
python-dateutil = "^2.9.0.post0"
[tool.poetry.group.dev.dependencies]