dev: 增加删除功能

This commit is contained in:
Julian Freeman
2024-07-19 22:51:03 -04:00
parent dc96e1a747
commit 1fcb707472
11 changed files with 233 additions and 61 deletions

View File

@@ -363,7 +363,7 @@ class Sqlite3Worker(object):
return ", ".join(columns_str_ls) return ", ".join(columns_str_ls)
def insert_into(self, table_name: str, columns: list[Column | str], def insert_into(self, table_name: str, columns: list[Column | str],
values: list[list[str | int | float]], values: list[list[str | int | float | BlobType]],
*, execute: bool = True, commit: bool = True) -> str: *, execute: bool = True, commit: bool = True) -> str:
col_count = len(columns) col_count = len(columns)
columns_str = self._columns_to_string(columns) columns_str = self._columns_to_string(columns)

View File

@@ -42,8 +42,8 @@ def get_app_dir(org_name: str, app_name: str) -> Path:
def get_config_path(org_name: str, app_name: str) -> Path: def get_config_path(org_name: str, app_name: str) -> Path:
data_dir = get_app_dir(org_name, app_name) app_dir = get_app_dir(org_name, app_name)
return Path(data_dir, "config.json") return Path(app_dir, "config.json")
def read_config(org_name: str, app_name: str) -> dict: def read_config(org_name: str, app_name: str) -> dict:
@@ -71,3 +71,8 @@ def get_default_db_path(config: dict, org_name: str, app_name: str) -> str:
app_dir = get_app_dir(org_name, app_name) app_dir = get_app_dir(org_name, app_name)
return str(app_dir / f"default.db") return str(app_dir / f"default.db")
return config["last_db_path"] return config["last_db_path"]
def get_secrets_path(org_name: str, app_name: str) -> str:
app_dir = get_app_dir(org_name, app_name)
return str(app_dir / "secrets.db")

View File

@@ -13,6 +13,7 @@ columns_d = {
"filepath": Column("filepath", DataType.BLOB, nullable=False), "filepath": Column("filepath", DataType.BLOB, nullable=False),
"path": Column("path", DataType.BLOB), "path": Column("path", DataType.BLOB),
"status": Column("status", DataType.TEXT), # 只有三种状态keep, transfer, delete "status": Column("status", DataType.TEXT), # 只有三种状态keep, transfer, delete
"deleted": Column("deleted", DataType.INTEGER, has_default=True, default=0), # 布尔,只有 1 或者 0
} }
all_columns = [ all_columns = [
@@ -27,10 +28,11 @@ all_columns = [
columns_d["filepath"], columns_d["filepath"],
columns_d["path"], columns_d["path"],
columns_d["status"], columns_d["status"],
columns_d["deleted"],
] ]
# 插入数据时使用的列 # 插入数据时使用的列
insert_columns = all_columns[1:-1] insert_columns = all_columns[1:-2]
# 查询数据时使用的列 # 查询数据时使用的列
query_columns = [ query_columns = [
@@ -47,6 +49,8 @@ sim_columns = [
columns_d["filepath"], columns_d["filepath"],
] ]
uuid_col = columns_d["uuid"]
filepath_col = columns_d["filepath"] filepath_col = columns_d["filepath"]
entry_id_col = columns_d["entry_id"] entry_id_col = columns_d["entry_id"]
status_col = columns_d["status"] status_col = columns_d["status"]
deleted_col = columns_d["deleted"]

19
lib/sec_db_columns_def.py Normal file
View File

@@ -0,0 +1,19 @@
# coding: utf8
from .Sqlite3Helper import Column, DataType
sec_columns_d = {
"secret_id": Column("secret_id", DataType.INTEGER, primary_key=True, unique=True),
"filepath": Column("filepath", DataType.BLOB),
"password": Column("password", DataType.BLOB),
}
sec_all_columns = [
sec_columns_d["secret_id"],
sec_columns_d["filepath"],
sec_columns_d["password"],
]
insert_sec_columns = sec_all_columns[1:]
sec_filepath_col = sec_columns_d["filepath"]
sec_password_col = sec_columns_d["password"]

View File

@@ -3,7 +3,11 @@ import sys
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
from lib.config_utils import get_default_db_path, read_config from lib.config_utils import (
get_default_db_path,
read_config,
get_secrets_path,
)
from src.mw_kps_unifier import KpsUnifier from src.mw_kps_unifier import KpsUnifier
import src.rc_kps_unifier import src.rc_kps_unifier
@@ -21,8 +25,9 @@ def main():
config = read_config(ORG_NAME, APP_NAME) config = read_config(ORG_NAME, APP_NAME)
db_path = get_default_db_path(config, ORG_NAME, APP_NAME) db_path = get_default_db_path(config, ORG_NAME, APP_NAME)
secrets_path = get_secrets_path(ORG_NAME, APP_NAME)
win = KpsUnifier(db_path, config, __version__) win = KpsUnifier(db_path, secrets_path, config, __version__)
win.show() win.show()
return app.exec() return app.exec()

View File

@@ -1,24 +1,30 @@
# coding: utf8 # coding: utf8
from pathlib import Path from pathlib import Path
from PySide6 import QtWidgets, QtGui, QtCore from PySide6 import QtWidgets, QtGui, QtCore
from pykeepass import PyKeePass
from pykeepass.exceptions import CredentialsError from pykeepass.exceptions import CredentialsError
from lib.Sqlite3Helper import Sqlite3Worker from lib.Sqlite3Helper import Sqlite3Worker
from lib.kps_operations import read_kps_to_db from lib.kps_operations import read_kps_to_db, blob_fy
from lib.sec_db_columns_def import insert_sec_columns
class GbxKpsLogin(QtWidgets.QGroupBox): class GbxKpsLogin(QtWidgets.QGroupBox):
def __init__( def __init__(
self, self,
path: str, path: str,
sqh: Sqlite3Worker,
config: dict, config: dict,
file_kp: dict[str, PyKeePass],
sqh: Sqlite3Worker,
sec_sqh: Sqlite3Worker,
parent: QtWidgets.QWidget = None parent: QtWidgets.QWidget = None
): ):
super().__init__(parent) super().__init__(parent)
self.sqh = sqh
self.config = config
self.path = path self.path = path
self.config = config
self.file_kp = file_kp
self.sqh = sqh
self.sec_sqh = sec_sqh
self.icon_eye = QtGui.QIcon(":/asset/img/eye.svg") self.icon_eye = QtGui.QIcon(":/asset/img/eye.svg")
self.icon_eye_off = QtGui.QIcon(":/asset/img/eye-off.svg") self.icon_eye_off = QtGui.QIcon(":/asset/img/eye-off.svg")
@@ -81,7 +87,7 @@ class GbxKpsLogin(QtWidgets.QGroupBox):
def on_pbn_load_clicked(self): def on_pbn_load_clicked(self):
try: try:
read_kps_to_db( kp = read_kps_to_db(
kps_file=self.lne_path.text(), kps_file=self.lne_path.text(),
password=self.lne_password.text(), password=self.lne_password.text(),
table_name=self.config["table_name"], table_name=self.config["table_name"],
@@ -92,6 +98,11 @@ class GbxKpsLogin(QtWidgets.QGroupBox):
f"{self.lne_path.text()}\n密码错误") f"{self.lne_path.text()}\n密码错误")
return return
self.file_kp[self.lne_path.text()] = kp
self.sec_sqh.insert_into("secrets", insert_sec_columns, [
[blob_fy(self.lne_path.text()), blob_fy(self.lne_password.text())]
])
self.lne_password.clear() self.lne_password.clear()
self.set_loaded(True) self.set_loaded(True)
loaded_mem = self.config["loaded_memory"] loaded_mem = self.config["loaded_memory"]

View File

@@ -1,5 +1,6 @@
# coding: utf8 # coding: utf8
from PySide6 import QtWidgets, QtCore, QtGui from PySide6 import QtWidgets, QtCore, QtGui
from pykeepass import PyKeePass
from .page_load import PageLoad from .page_load import PageLoad
from .page_query import PageQuery from .page_query import PageQuery
@@ -8,6 +9,7 @@ from .page_similar import PageSimilar
from .cmbx_styles import StyleComboBox from .cmbx_styles import StyleComboBox
from lib.Sqlite3Helper import Sqlite3Worker from lib.Sqlite3Helper import Sqlite3Worker
from lib.db_columns_def import all_columns from lib.db_columns_def import all_columns
from lib.sec_db_columns_def import sec_all_columns
from lib.config_utils import write_config from lib.config_utils import write_config
@@ -16,7 +18,9 @@ class UiKpsUnifier(object):
self, self,
default_db_path: str, default_db_path: str,
config: dict, config: dict,
file_kp: dict[str, PyKeePass],
sqh: Sqlite3Worker, sqh: Sqlite3Worker,
sec_sqh: Sqlite3Worker,
window: QtWidgets.QMainWindow window: QtWidgets.QMainWindow
): ):
window.setWindowTitle('KeePassXC 多合一') window.setWindowTitle('KeePassXC 多合一')
@@ -56,9 +60,9 @@ class UiKpsUnifier(object):
self.sw_m = QtWidgets.QStackedWidget(self.cw) self.sw_m = QtWidgets.QStackedWidget(self.cw)
self.vly_m.addWidget(self.sw_m) self.vly_m.addWidget(self.sw_m)
self.page_load = PageLoad(sqh, config, self.cw) self.page_load = PageLoad(config, file_kp, sqh, sec_sqh, self.cw)
self.sw_m.addWidget(self.page_load) self.sw_m.addWidget(self.page_load)
self.page_query = PageQuery(sqh, config, self.cw) self.page_query = PageQuery(config, file_kp, sqh, sec_sqh, self.cw)
self.sw_m.addWidget(self.page_query) self.sw_m.addWidget(self.page_query)
self.page_similar = PageSimilar(sqh, config, self.cw) self.page_similar = PageSimilar(sqh, config, self.cw)
self.sw_m.addWidget(self.page_similar) self.sw_m.addWidget(self.page_similar)
@@ -69,14 +73,24 @@ class UiKpsUnifier(object):
class KpsUnifier(QtWidgets.QMainWindow): class KpsUnifier(QtWidgets.QMainWindow):
def __init__(self, db_path: str, config: dict, version: str, parent=None): def __init__(
self,
db_path: str,
secrets_path: str,
config: dict,
version: str,
parent: QtWidgets.QWidget = None,
):
super().__init__(parent) super().__init__(parent)
self.db_path = db_path self.db_path = db_path
self.secrets_path = secrets_path
self.config = config self.config = config
self.version = version self.version = version
self.file_kp: dict[str, PyKeePass] = {}
self.sqh = self.init_db() self.sqh = self.init_db()
self.sec_sqh = self.init_secrets_db()
self.ui = UiKpsUnifier(self.db_path, self.config, self.sqh, self) self.ui = UiKpsUnifier(self.db_path, self.config, self.file_kp, self.sqh, self.sec_sqh, self)
self.ui.act_new.triggered.connect(self.on_act_new_triggered) self.ui.act_new.triggered.connect(self.on_act_new_triggered)
self.ui.act_open.triggered.connect(self.on_act_open_triggered) self.ui.act_open.triggered.connect(self.on_act_open_triggered)
@@ -139,3 +153,8 @@ class KpsUnifier(QtWidgets.QMainWindow):
def on_act_about_qt_triggered(self): def on_act_about_qt_triggered(self):
QtWidgets.QMessageBox.aboutQt(self, "关于 Qt") QtWidgets.QMessageBox.aboutQt(self, "关于 Qt")
def init_secrets_db(self) -> Sqlite3Worker:
sec_sqh = Sqlite3Worker(self.secrets_path)
sec_sqh.create_table("secrets", sec_all_columns, if_not_exists=True)
return sec_sqh

View File

@@ -2,6 +2,7 @@
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
from PySide6 import QtWidgets from PySide6 import QtWidgets
from pykeepass import PyKeePass
from .gbx_kps_login import GbxKpsLogin from .gbx_kps_login import GbxKpsLogin
from .utils import accept_warning from .utils import accept_warning
@@ -11,13 +12,17 @@ from lib.Sqlite3Helper import Sqlite3Worker
class WgLoadKps(QtWidgets.QWidget): class WgLoadKps(QtWidgets.QWidget):
def __init__( def __init__(
self, self,
sqh: Sqlite3Worker,
config: dict, config: dict,
file_kp: dict[str, PyKeePass],
sqh: Sqlite3Worker,
sec_sqh: Sqlite3Worker,
parent: QtWidgets.QWidget = None parent: QtWidgets.QWidget = None
): ):
super().__init__(parent) super().__init__(parent)
self.sqh = sqh self.sqh = sqh
self.sec_sqh = sec_sqh
self.config = config self.config = config
self.file_kp = file_kp
self.kps_wgs: list[GbxKpsLogin] = [] self.kps_wgs: list[GbxKpsLogin] = []
self.vly_m = QtWidgets.QVBoxLayout() self.vly_m = QtWidgets.QVBoxLayout()
@@ -38,7 +43,7 @@ class WgLoadKps(QtWidgets.QWidget):
QtWidgets.QMessageBox.warning(self, "警告", "该 KPS 文件已添加。") QtWidgets.QMessageBox.warning(self, "警告", "该 KPS 文件已添加。")
return return
wg = GbxKpsLogin(path, self.sqh, self.config, self) wg = GbxKpsLogin(path, self.config, self.file_kp, self.sqh, self.sec_sqh, self)
wg.pbn_remove.clicked_with_item.connect(self.on_item_pbn_remove_clicked) wg.pbn_remove.clicked_with_item.connect(self.on_item_pbn_remove_clicked)
# 从倒数第二个位置插入,保证弹簧始终在最后 # 从倒数第二个位置插入,保证弹簧始终在最后
self.vly_m.insertWidget(self.vly_m.count() - 1, wg) self.vly_m.insertWidget(self.vly_m.count() - 1, wg)
@@ -61,9 +66,11 @@ class WgLoadKps(QtWidgets.QWidget):
class PageLoad(QtWidgets.QWidget): class PageLoad(QtWidgets.QWidget):
def __init__( def __init__(
self, self,
sqh: Sqlite3Worker,
config: dict, config: dict,
parent: QtWidgets.QWidget = None file_kp: dict[str, PyKeePass],
sqh: Sqlite3Worker,
sec_sqh: Sqlite3Worker,
parent: QtWidgets.QWidget = None,
): ):
super().__init__(parent) super().__init__(parent)
self.sqh = sqh self.sqh = sqh
@@ -91,7 +98,7 @@ class PageLoad(QtWidgets.QWidget):
self.sa_m = QtWidgets.QScrollArea(self) self.sa_m = QtWidgets.QScrollArea(self)
self.sa_m.setWidgetResizable(True) self.sa_m.setWidgetResizable(True)
self.hly_m.addWidget(self.sa_m) self.hly_m.addWidget(self.sa_m)
self.wg_sa = WgLoadKps(sqh, config, self.sa_m) self.wg_sa = WgLoadKps(config, file_kp, sqh, sec_sqh, self.sa_m)
self.sa_m.setWidget(self.wg_sa) self.sa_m.setWidget(self.wg_sa)
self.pbn_add_kps.clicked.connect(self.on_pbn_add_kps_clicked) self.pbn_add_kps.clicked.connect(self.on_pbn_add_kps_clicked)

View File

@@ -1,10 +1,18 @@
# coding: utf8 # coding: utf8
import json import json
from uuid import UUID
from PySide6 import QtWidgets, QtCore, QtGui from PySide6 import QtWidgets, QtCore, QtGui
from pykeepass import PyKeePass
from .da_entry_info import DaEntryInfo from .da_entry_info import DaEntryInfo
from .utils import HorizontalLine, get_filepath_uuids_map, accept_warning
from lib.Sqlite3Helper import Sqlite3Worker, Expression, Operand from lib.Sqlite3Helper import Sqlite3Worker, Expression, Operand
from lib.db_columns_def import query_columns, status_col, entry_id_col from lib.db_columns_def import (
query_columns, status_col, entry_id_col,
sim_columns, deleted_col, uuid_col, filepath_col,
)
from lib.sec_db_columns_def import sec_password_col, sec_filepath_col
from lib.kps_operations import blob_fy
class QueryTableModel(QtCore.QAbstractTableModel): class QueryTableModel(QtCore.QAbstractTableModel):
@@ -21,9 +29,9 @@ class QueryTableModel(QtCore.QAbstractTableModel):
"none": QtGui.QBrush(QtCore.Qt.GlobalColor.transparent) "none": QtGui.QBrush(QtCore.Qt.GlobalColor.transparent)
} }
self.dark_status_colors = { self.dark_status_colors = {
"keep": QtGui.QBrush(QtGui.QColor("green")), "keep": QtGui.QBrush(QtGui.QColor("forestgreen")),
"transfer": QtGui.QBrush(QtGui.QColor("orange")), "transfer": QtGui.QBrush(QtGui.QColor("darksalmon")),
"delete": QtGui.QBrush(QtGui.QColor("orangered")), "delete": QtGui.QBrush(QtGui.QColor("darkred")),
"none": QtGui.QBrush(QtCore.Qt.GlobalColor.transparent) "none": QtGui.QBrush(QtCore.Qt.GlobalColor.transparent)
} }
if QtWidgets.QApplication.style().name() == "windowsvista": if QtWidgets.QApplication.style().name() == "windowsvista":
@@ -60,29 +68,21 @@ class QueryTableModel(QtCore.QAbstractTableModel):
return self.headers[section] return self.headers[section]
class PageQuery(QtWidgets.QWidget): class UiPageQuery(object):
def __init__(self, sqh: Sqlite3Worker, config: dict, parent=None): def __init__(self, config: dict, window: QtWidgets.QWidget):
super().__init__(parent)
self.sqh = sqh
self.config = config
# 右键菜单 # 右键菜单
self.menu_ctx = QtWidgets.QMenu(self) self.menu_ctx = QtWidgets.QMenu(window)
self.act_keep = ActionWithStr("keep", "保留", self) self.act_keep = ActionWithStr("keep", "保留", window)
self.act_transfer = ActionWithStr("transfer", "转移", self) self.act_transfer = ActionWithStr("transfer", "转移", window)
self.act_delete = ActionWithStr("delete", "删除", self) self.act_delete = ActionWithStr("delete", "删除", window)
self.menu_ctx.addActions([self.act_keep, self.act_transfer, self.act_delete]) self.menu_ctx.addActions([self.act_keep, self.act_transfer, self.act_delete])
self.act_keep.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
self.act_transfer.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
self.act_delete.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
# 主布局 # 主布局
self.hly_m = QtWidgets.QHBoxLayout() self.hly_m = QtWidgets.QHBoxLayout()
self.setLayout(self.hly_m) window.setLayout(self.hly_m)
self.sa_left = QtWidgets.QScrollArea(self) self.sa_left = QtWidgets.QScrollArea(window)
self.sa_left.setWidgetResizable(True) self.sa_left.setWidgetResizable(True)
self.sa_left.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.sa_left.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.sa_left.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Ignored) self.sa_left.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Ignored)
@@ -93,26 +93,66 @@ class PageQuery(QtWidgets.QWidget):
self.sa_wg.setLayout(self.vly_sa_wg) self.sa_wg.setLayout(self.vly_sa_wg)
self.sa_left.setWidget(self.sa_wg) self.sa_left.setWidget(self.sa_wg)
self.pbn_execute = QtWidgets.QPushButton("执行操作", window)
self.pbn_execute.setMinimumWidth(config["button_min_width"])
self.vly_sa_wg.addWidget(self.pbn_execute)
self.pbn_set_target = QtWidgets.QPushButton("目标文件", window)
self.pbn_set_target.setMinimumWidth(config["button_min_width"])
self.vly_sa_wg.addWidget(self.pbn_set_target)
self.hln_1 = HorizontalLine(window)
self.vly_sa_wg.addWidget(self.hln_1)
self.pbn_all = QtWidgets.QPushButton("全部", self.sa_wg) self.pbn_all = QtWidgets.QPushButton("全部", self.sa_wg)
self.pbn_all.setMinimumWidth(config["button_min_width"]) self.pbn_all.setMinimumWidth(config["button_min_width"])
self.vly_sa_wg.addWidget(self.pbn_all) self.vly_sa_wg.addWidget(self.pbn_all)
self.pbn_deleted = QtWidgets.QPushButton("已删除", self.sa_wg)
self.pbn_deleted.setMinimumWidth(config["button_min_width"])
self.vly_sa_wg.addWidget(self.pbn_deleted)
self.vly_sa_wg.addStretch(1) self.vly_sa_wg.addStretch(1)
self.pbn_read_filters = QtWidgets.QPushButton("更多过滤", self) self.pbn_read_filters = QtWidgets.QPushButton("更多过滤", window)
self.pbn_read_filters.setMinimumWidth(config["button_min_width"]) self.pbn_read_filters.setMinimumWidth(config["button_min_width"])
self.vly_sa_wg.addWidget(self.pbn_read_filters) self.vly_sa_wg.addWidget(self.pbn_read_filters)
self.trv_m = QtWidgets.QTreeView(self) self.trv_m = QtWidgets.QTreeView(window)
self.trv_m.setSelectionMode(QtWidgets.QTreeView.SelectionMode.ExtendedSelection) self.trv_m.setSelectionMode(QtWidgets.QTreeView.SelectionMode.ExtendedSelection)
self.trv_m.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) self.trv_m.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
# self.trv_m.setSortingEnabled(True) # self.trv_m.setSortingEnabled(True)
self.hly_m.addWidget(self.trv_m) 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) class PageQuery(QtWidgets.QWidget):
self.trv_m.doubleClicked.connect(self.on_trv_m_double_clicked) def __init__(
self.trv_m.customContextMenuRequested.connect(self.on_trv_m_custom_context_menu_requested) self,
config: dict,
file_kp: dict[str, PyKeePass],
sqh: Sqlite3Worker,
sec_sqh: Sqlite3Worker,
parent: QtWidgets.QWidget = None,
):
super().__init__(parent)
self.sqh = sqh
self.sec_sqh = sec_sqh
self.config = config
self.file_kp = file_kp
self.ui = UiPageQuery(config, self)
self.ui.act_keep.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
self.ui.act_transfer.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
self.ui.act_delete.triggered_with_str.connect(self.on_act_mark_triggered_with_str)
self.ui.pbn_execute.clicked.connect(self.on_pbn_execute_clicked)
self.ui.pbn_set_target.clicked.connect(self.on_pbn_set_target_clicked)
self.ui.pbn_all.clicked.connect(self.on_pbn_all_clicked)
self.ui.pbn_deleted.clicked.connect(self.on_pbn_deleted_clicked)
self.ui.pbn_read_filters.clicked.connect(self.on_pbn_read_filters_clicked)
self.ui.trv_m.doubleClicked.connect(self.on_trv_m_double_clicked)
self.ui.trv_m.customContextMenuRequested.connect(self.on_trv_m_custom_context_menu_requested)
self.set_default_filters() self.set_default_filters()
@@ -135,9 +175,9 @@ class PageQuery(QtWidgets.QWidget):
self.set_filter_button(fil) self.set_filter_button(fil)
def set_filter_button(self, fil: dict): def set_filter_button(self, fil: dict):
pbn_fil = PushButtonWithData(fil, self.sa_wg, fil["name"]) pbn_fil = PushButtonWithData(fil, self.ui.sa_wg, fil["name"])
pbn_fil.setMinimumWidth(self.config["button_min_width"]) pbn_fil.setMinimumWidth(self.config["button_min_width"])
self.vly_sa_wg.insertWidget(self.vly_sa_wg.count() - 2, pbn_fil) self.ui.vly_sa_wg.insertWidget(self.ui.vly_sa_wg.count() - 2, pbn_fil)
pbn_fil.clicked_with_data.connect(self.on_custom_filters_clicked_with_data) pbn_fil.clicked_with_data.connect(self.on_custom_filters_clicked_with_data)
def on_pbn_read_filters_clicked(self): def on_pbn_read_filters_clicked(self):
@@ -153,17 +193,24 @@ class PageQuery(QtWidgets.QWidget):
def on_custom_filters_clicked_with_data(self, data: dict): def on_custom_filters_clicked_with_data(self, data: dict):
_, results = self.sqh.select(self.config["table_name"], query_columns, _, results = self.sqh.select(self.config["table_name"], query_columns,
where=Expression(data["where"])) where=Expression(data["where"]).and_(Operand(deleted_col).equal_to(0)))
model = QueryTableModel(results, self) model = QueryTableModel(results, self)
self.trv_m.setModel(model) self.ui.trv_m.setModel(model)
def update_sqh(self, sqh: Sqlite3Worker): def update_sqh(self, sqh: Sqlite3Worker):
self.sqh = sqh self.sqh = sqh
def on_pbn_all_clicked(self): def on_pbn_all_clicked(self):
_, results = self.sqh.select(self.config["table_name"], query_columns) _, results = self.sqh.select(self.config["table_name"], query_columns,
where=Operand(deleted_col).equal_to(0))
model = QueryTableModel(results, self) model = QueryTableModel(results, self)
self.trv_m.setModel(model) self.ui.trv_m.setModel(model)
def on_pbn_deleted_clicked(self):
_, results = self.sqh.select(self.config["table_name"], query_columns,
where=Operand(deleted_col).equal_to(1))
model = QueryTableModel(results, self)
self.ui.trv_m.setModel(model)
def on_trv_m_double_clicked(self, index: QtCore.QModelIndex): def on_trv_m_double_clicked(self, index: QtCore.QModelIndex):
entry_id = index.siblingAtColumn(0).data(QtCore.Qt.ItemDataRole.DisplayRole) entry_id = index.siblingAtColumn(0).data(QtCore.Qt.ItemDataRole.DisplayRole)
@@ -171,10 +218,10 @@ class PageQuery(QtWidgets.QWidget):
da_entry_info.exec() da_entry_info.exec()
def on_trv_m_custom_context_menu_requested(self, pos: QtCore.QPoint): def on_trv_m_custom_context_menu_requested(self, pos: QtCore.QPoint):
self.menu_ctx.exec(self.trv_m.viewport().mapToGlobal(pos)) self.ui.menu_ctx.exec(self.ui.trv_m.viewport().mapToGlobal(pos))
def on_act_mark_triggered_with_str(self, info: str): def on_act_mark_triggered_with_str(self, info: str):
indexes = self.trv_m.selectedIndexes() indexes = self.ui.trv_m.selectedIndexes()
entry_ids = [ entry_ids = [
index.data(QtCore.Qt.ItemDataRole.DisplayRole) index.data(QtCore.Qt.ItemDataRole.DisplayRole)
for index in indexes if index.column() == 0 for index in indexes if index.column() == 0
@@ -183,6 +230,47 @@ class PageQuery(QtWidgets.QWidget):
self.sqh.update(self.config["table_name"], [(status_col, info)], self.sqh.update(self.config["table_name"], [(status_col, info)],
where=Operand(entry_id_col).in_(entry_ids)) where=Operand(entry_id_col).in_(entry_ids))
def on_pbn_execute_clicked(self):
if accept_warning(self, True, "警告", "你确定要执行转移和删除操作吗?"):
return
# 删除功能
_, results = self.sqh.select(self.config["table_name"], sim_columns,
where=Operand(status_col).equal_to("delete"))
file_uuids = get_filepath_uuids_map(results)
total, success, invalid = 0, 0, 0
for file in file_uuids:
if file not in self.file_kp:
_, results = self.sec_sqh.select("secrets", [sec_password_col],
where=Operand(sec_filepath_col).equal_to(blob_fy(file)))
password = results[-1][0].decode("utf-8")
kp = PyKeePass(file, password)
else:
kp = self.file_kp[file]
for u in file_uuids[file]:
total += 1
self.sqh.update(self.config["table_name"], [(deleted_col, 1)],
where=Operand(uuid_col).equal_to(u).and_(
Operand(filepath_col).equal_to(blob_fy(file))))
entry = kp.find_entries(uuid=UUID(u), first=True)
if entry is None:
invalid += 1
continue
kp.delete_entry(entry)
success += 1
kp.save()
QtWidgets.QMessageBox.information(self, "提示",
f"{total} 条标记的条目,已删除 {success} 条,无效 {invalid} 条。")
def on_pbn_set_target_clicked(self):
pass
class PushButtonWithData(QtWidgets.QPushButton): class PushButtonWithData(QtWidgets.QPushButton):

View File

@@ -4,7 +4,7 @@ from uuid import UUID
from PySide6 import QtWidgets, QtCore from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import QAbstractTableModel from PySide6.QtCore import QAbstractTableModel
from .utils import accept_warning from .utils import accept_warning, get_filepath_uuids_map
from lib.Sqlite3Helper import Sqlite3Worker, Operand from lib.Sqlite3Helper import Sqlite3Worker, Operand
from lib.db_columns_def import sim_columns, filepath_col from lib.db_columns_def import sim_columns, filepath_col
from lib.config_utils import path_not_exist from lib.config_utils import path_not_exist
@@ -71,12 +71,7 @@ class PageSimilar(QtWidgets.QWidget):
def on_pbn_read_db_clicked(self): def on_pbn_read_db_clicked(self):
_, results = self.sqh.select(self.config["table_name"], sim_columns) _, results = self.sqh.select(self.config["table_name"], sim_columns)
file_uuids: dict[str, list[UUID]] = {} file_uuids = get_filepath_uuids_map(results)
for u, filepath in results:
filepath = filepath.decode("utf8")
if filepath not in file_uuids:
file_uuids[filepath] = []
file_uuids[filepath].append(u)
files = file_uuids.keys() files = file_uuids.keys()
if len(files) < 2: if len(files) < 2:

View File

@@ -9,3 +9,22 @@ def accept_warning(widget: QtWidgets.QWidget, condition: bool,
if b == QtWidgets.QMessageBox.StandardButton.No: if b == QtWidgets.QMessageBox.StandardButton.No:
return True return True
return False return False
class HorizontalLine(QtWidgets.QFrame):
def __init__(self, parent: QtWidgets.QWidget = None):
super().__init__(parent)
self.setFrameShape(QtWidgets.QFrame.Shape.HLine)
self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
def get_filepath_uuids_map(query_results: list[tuple]) -> dict[str, list[str]]:
file_uuids: dict[str, list[str]] = {}
for u, filepath in query_results:
filepath = filepath.decode("utf8")
if filepath not in file_uuids:
file_uuids[filepath] = []
file_uuids[filepath].append(u)
return file_uuids