160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
|
|
"""Разблокировка существующей базы или создание новой."""
|
|||
|
|
|
|||
|
|
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()
|