diff --git a/lib/db_columns_def.py b/lib/db_columns_def.py index f612235..5bbd8b6 100644 --- a/lib/db_columns_def.py +++ b/lib/db_columns_def.py @@ -11,6 +11,7 @@ columns_d = { "notes": Column("notes", DataType.BLOB), "path": Column("path", DataType.BLOB, nullable=False), } + all_columns = [ columns_d["entry_id"], columns_d["title"], @@ -21,3 +22,10 @@ all_columns = [ columns_d["notes"], columns_d["path"], ] + +query_columns = [ + columns_d["entry_id"], + columns_d["title"], + columns_d["username"], + columns_d["url"], +] diff --git a/lib/global_config.py b/lib/global_config.py new file mode 100644 index 0000000..091b645 --- /dev/null +++ b/lib/global_config.py @@ -0,0 +1,4 @@ +# coding: utf8 + +table_name = "entries" +button_min_width = 120 diff --git a/lib/kps_operations.py b/lib/kps_operations.py index 3f65bbb..9acc253 100644 --- a/lib/kps_operations.py +++ b/lib/kps_operations.py @@ -2,6 +2,7 @@ from os import PathLike from pykeepass import PyKeePass from .Sqlite3Helper import Sqlite3Worker, BlobType, Column +from .global_config import table_name def trim_str(value): @@ -40,8 +41,8 @@ def read_kps_to_db(kps_file: str | PathLike[str], password: str, extract_otp(entry.otp), blob_fy(trim_str(entry.url)), blob_fy(entry.notes), - blob_fy("::".join(entry.path[:-1])), + blob_fy("::".join([kps_file] + entry.path[:-1])), ]) - sqh.insert_into("entries", columns, values) + sqh.insert_into(table_name, columns, values) return kp diff --git a/src/mw_kps_unifier.py b/src/mw_kps_unifier.py index 51ab7f2..4658ab7 100644 --- a/src/mw_kps_unifier.py +++ b/src/mw_kps_unifier.py @@ -1,14 +1,15 @@ # coding: utf8 import os import sys -from datetime import datetime from pathlib import Path from PySide6 import QtWidgets, QtCore, QtGui from .page_load import PageLoad +from .page_query import PageQuery from .cmbx_styles import StyleComboBox from lib.Sqlite3Helper import Sqlite3Worker from lib.db_columns_def import all_columns +from lib.global_config import table_name def get_default_db_path() -> str: @@ -25,8 +26,7 @@ def get_default_db_path() -> str: if not app_dir.exists(): app_dir.mkdir(parents=True, exist_ok=True) - now_s = datetime.now().strftime("%Y%m%d%H%M%S") - return str(app_dir / f"{now_s}.db") + return str(app_dir / f"default.db") class UiKpsUnifier(object): @@ -63,11 +63,12 @@ class UiKpsUnifier(object): self.page_load = PageLoad(sqh, self.cw) self.sw_m.addWidget(self.page_load) - self.page_query = QtWidgets.QWidget(self.cw) + self.page_query = PageQuery(sqh, self.cw) self.sw_m.addWidget(self.page_query) def update_sqh(self, sqh: Sqlite3Worker): self.page_load.update_sqh(sqh) + self.page_query.update_sqh(sqh) class KpsUnifier(QtWidgets.QMainWindow): @@ -88,7 +89,7 @@ class KpsUnifier(QtWidgets.QMainWindow): def init_db(self) -> Sqlite3Worker: sqh = Sqlite3Worker(self.db_path) - sqh.create_table("entries", all_columns, if_not_exists=True) + sqh.create_table(table_name, all_columns, if_not_exists=True) return sqh def update_db(self, filename: str): diff --git a/src/page_load.py b/src/page_load.py index e05e489..86bf31c 100644 --- a/src/page_load.py +++ b/src/page_load.py @@ -6,6 +6,7 @@ from .gbx_kps_login import GbxKpsLogin from lib.Sqlite3Helper import Sqlite3Worker from lib.db_columns_def import all_columns from lib.kps_operations import read_kps_to_db +from lib.global_config import button_min_width class WgLoadKps(QtWidgets.QWidget): @@ -28,6 +29,7 @@ class WgLoadKps(QtWidgets.QWidget): def on_item_pbn_remove_clicked(self, item: GbxKpsLogin): self.vly_m.removeWidget(item) + self.kps_wgs.remove(item) item.deleteLater() def on_item_pbn_load_clicked(self, item: GbxKpsLogin): @@ -58,19 +60,19 @@ class PageLoad(QtWidgets.QWidget): self.hly_m.addLayout(self.vly_left) self.pbn_add = QtWidgets.QPushButton("添加", self) + self.pbn_add.setMinimumWidth(button_min_width) self.vly_left.addWidget(self.pbn_add) self.pbn_load_all = QtWidgets.QPushButton("加载全部", self) + self.pbn_load_all.setMinimumWidth(button_min_width) + self.pbn_load_all.setDisabled(True) self.vly_left.addWidget(self.pbn_load_all) self.vly_left.addStretch(1) - self.sa = QtWidgets.QScrollArea(self) - self.sa.setWidgetResizable(True) - self.hly_m.addWidget(self.sa) - self.wg_sa = WgLoadKps(sqh, self.sa) - self.sa.setWidget(self.wg_sa) - - self.hly_m.setStretchFactor(self.vly_left, 1) - self.hly_m.setStretchFactor(self.sa, 6) + self.sa_m = QtWidgets.QScrollArea(self) + self.sa_m.setWidgetResizable(True) + self.hly_m.addWidget(self.sa_m) + self.wg_sa = WgLoadKps(sqh, self.sa_m) + self.sa_m.setWidget(self.wg_sa) self.pbn_add.clicked.connect(self.on_pbn_add_clicked) diff --git a/src/page_query.py b/src/page_query.py new file mode 100644 index 0000000..cccafc5 --- /dev/null +++ b/src/page_query.py @@ -0,0 +1,133 @@ +# coding: utf8 +import json + +from PySide6 import QtWidgets, QtCore, QtGui +from lib.Sqlite3Helper import Sqlite3Worker, Operand, Expression +from lib.db_columns_def import query_columns +from lib.global_config import button_min_width, table_name + + +class QueryTableModel(QtCore.QAbstractTableModel): + + def __init__(self, query_results: list[tuple], parent=None): + super().__init__(parent) + self.query_results = query_results + self.headers = ["序号", "标题", "用户名", "URL"] + + def rowCount(self, parent: QtCore.QModelIndex = ...): + return len(self.query_results) + + def columnCount(self, parent: QtCore.QModelIndex = ...): + return len(self.headers) + + def data(self, index: QtCore.QModelIndex, role: int = ...): + if role == QtCore.Qt.ItemDataRole.DisplayRole: + item = self.query_results[index.row()][index.column()] + if isinstance(item, bytes): + return item.decode("utf-8") + return item + + def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = ...): + if orientation == QtCore.Qt.Orientation.Horizontal: + if role == QtCore.Qt.ItemDataRole.DisplayRole: + return self.headers[section] + + +class PageQuery(QtWidgets.QWidget): + def __init__(self, sqh: Sqlite3Worker, parent=None): + super().__init__(parent) + self.sqh = sqh + + self.hly_m = QtWidgets.QHBoxLayout() + self.setLayout(self.hly_m) + + self.sa_left = QtWidgets.QScrollArea(self) + self.sa_left.setWidgetResizable(True) + self.sa_left.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.sa_left.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Ignored) + self.hly_m.addWidget(self.sa_left) + + self.sa_wg = QtWidgets.QWidget(self.sa_left) + self.vly_sa_wg = QtWidgets.QVBoxLayout() + self.sa_wg.setLayout(self.vly_sa_wg) + self.sa_left.setWidget(self.sa_wg) + + self.pbn_all = QtWidgets.QPushButton("全部", self.sa_wg) + self.pbn_all.setMinimumWidth(button_min_width) + self.vly_sa_wg.addWidget(self.pbn_all) + + self.vly_sa_wg.addStretch(1) + + self.pbn_read_filters = QtWidgets.QPushButton("更多过滤", self) + self.pbn_read_filters.setMinimumWidth(button_min_width) + self.vly_sa_wg.addWidget(self.pbn_read_filters) + + self.trv_m = QtWidgets.QTreeView(self) + # self.trv_m.setSortingEnabled(True) + self.hly_m.addWidget(self.trv_m) + + self.pbn_all.clicked.connect(self.on_pbn_all_clicked) + self.pbn_read_filters.clicked.connect(self.on_pbn_read_filters_clicked) + + self.set_default_filters() + + def set_default_filters(self): + default_filters = [ + { + "name": "Gmail 邮箱", + "where": "username LIKE '%@gmail.com'" + }, + { + "name": "Outlook 邮箱", + "where": "username LIKE '%@outlook.com'" + }, + { + "name": "谷歌文档", + "where": "url LIKE 'https://docs.google.com/%' or url LIKE 'https://drive.google.com/%'" + }, + ] + for fil in default_filters: + self.set_filter_button(fil) + + def set_filter_button(self, fil: dict): + pbn_fil = PushButtonWithData(fil, self.sa_wg, fil["name"]) + pbn_fil.setMinimumWidth(button_min_width) + self.vly_sa_wg.insertWidget(self.vly_sa_wg.count() - 2, pbn_fil) + pbn_fil.clicked_with_data.connect(self.on_custom_filters_clicked_with_data) + + def on_pbn_read_filters_clicked(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "打开", "../", + filter="JSON 文件 (*.json);;所有文件 (*)") + if len(filename) == 0: + return + with open(filename, "r", encoding="utf8") as f: + filter_ls: list[dict] = json.load(f) + + for fil in filter_ls: + self.set_filter_button(fil) + + def on_custom_filters_clicked_with_data(self, data: dict): + _, results = self.sqh.select(table_name, query_columns, where=Expression(data["where"])) + model = QueryTableModel(results, self) + self.trv_m.setModel(model) + + def update_sqh(self, sqh: Sqlite3Worker): + self.sqh = sqh + + def on_pbn_all_clicked(self): + _, results = self.sqh.select(table_name, query_columns) + model = QueryTableModel(results, self) + self.trv_m.setModel(model) + + +class PushButtonWithData(QtWidgets.QPushButton): + + clicked_with_data = QtCore.Signal(dict) + + def __init__(self, data: dict, parent: QtWidgets.QWidget = None, title: str = ""): + super().__init__(title, parent) + self.data = data + self.clicked.connect(self.on_self_clicked) + + def on_self_clicked(self): + self.clicked_with_data.emit(self.data)