food-records/app/ui/login.py

160 lines
5.6 KiB
Python
Raw Normal View History

"""Разблокировка существующей базы или создание новой."""
from __future__ import annotations
from pathlib import Path
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QCheckBox,
QDialog,
QFormLayout,
QLabel,
QLineEdit,
QMessageBox,
QVBoxLayout,
)
from .. import crypto, journal
from ..storage import Vault
from . import widgets as w
MIN_PASSWORD_LENGTH = 4
class LoginDialog(QDialog):
"""Диалог входа. После accept() в self.vault лежит открытая база."""
def __init__(self, vault_path: Path, parent=None):
super().__init__(parent)
self.vault_path = Path(vault_path)
self.is_new = not self.vault_path.exists()
self.vault: Vault | None = None
self.setWindowTitle("Булочная")
self.setMinimumWidth(440)
layout = QVBoxLayout(self)
layout.setContentsMargins(24, 20, 24, 20)
layout.setSpacing(12)
layout.addWidget(w.heading("Новая база" if self.is_new else "Булочная"))
layout.addWidget(
w.label(
"Придумай пароль. Им шифруется весь учёт — восстановить его "
"невозможно, так что запиши в надёжном месте."
if self.is_new
else str(self.vault_path),
"dim",
)
)
layout.itemAt(layout.count() - 1).widget().setWordWrap(True)
form = QFormLayout()
form.setSpacing(8)
self.password = QLineEdit()
self.password.setEchoMode(QLineEdit.Password)
self.password.returnPressed.connect(self.accept)
form.addRow("Пароль", self.password)
if self.is_new:
self.confirm = QLineEdit()
self.confirm.setEchoMode(QLineEdit.Password)
self.confirm.returnPressed.connect(self.accept)
form.addRow("Ещё раз", self.confirm)
self.remote = QLineEdit()
self.remote.setPlaceholderText("https://gt.ser.gay/kizya/food-market.git")
form.addRow("Репозиторий", self.remote)
self.token = QLineEdit()
self.token.setEchoMode(QLineEdit.Password)
self.token.setPlaceholderText("необязательно, можно задать позже")
form.addRow("Токен GitLab", self.token)
self.demo = QCheckBox("Завести три булки для примера")
self.demo.setChecked(True)
form.addRow("", self.demo)
layout.addLayout(form)
show = QCheckBox("Показать пароль")
show.toggled.connect(self._toggle_echo)
layout.addWidget(show)
self.error = QLabel("")
self.error.setWordWrap(True)
self.error.setStyleSheet("color: #e0605e;")
layout.addWidget(self.error)
layout.addWidget(
w.row(
None,
w.button("Отмена", on_click=self.reject),
w.button("Создать" if self.is_new else "Открыть", "primary", self.accept),
)
)
self.password.setFocus()
def _toggle_echo(self, visible: bool) -> None:
mode = QLineEdit.Normal if visible else QLineEdit.Password
self.password.setEchoMode(mode)
if self.is_new:
self.confirm.setEchoMode(mode)
def accept(self) -> None:
self.error.setText("")
password = self.password.text()
if self.is_new:
if len(password) < MIN_PASSWORD_LENGTH:
self.error.setText(f"Пароль короче {MIN_PASSWORD_LENGTH} символов.")
return
if password != self.confirm.text():
self.error.setText("Пароли не совпадают.")
return
self._create(password)
else:
self._open(password)
def _create(self, password: str) -> None:
try:
vault = Vault.create(self.vault_path, password)
except OSError as exc:
self.error.setText(f"Не удалось создать файл: {exc}")
return
git_settings = {}
if self.remote.text().strip():
git_settings["remote_url"] = self.remote.text().strip()
if self.token.text():
git_settings["token"] = self.token.text()
if git_settings:
journal.update_settings(vault, git=git_settings)
if self.demo.isChecked():
journal.create_product(vault, "Булка с повидлом", cost_price=30, retail_price=50)
journal.create_product(vault, "Булка с маком", cost_price=20, retail_price=35)
journal.create_product(vault, "Булка с корицей", cost_price=25, retail_price=40)
vault.save()
self.vault = vault
super().accept()
def _open(self, password: str) -> None:
try:
self.vault = Vault.open(self.vault_path, password)
except crypto.WrongPassword:
self.error.setText("Неверный пароль.")
self.password.selectAll()
self.password.setFocus()
return
except crypto.VaultError as exc:
QMessageBox.critical(self, "Не удалось открыть базу", str(exc))
return
except OSError as exc:
QMessageBox.critical(self, "Не удалось прочитать файл", str(exc))
return
super().accept()