dev: 增加删除功能
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
19
lib/sec_db_columns_def.py
Normal 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"]
|
||||||
9
main.py
9
main.py
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
19
src/utils.py
19
src/utils.py
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user