diff --git a/lib/Sqlite3Helper.py b/lib/Sqlite3Helper.py index abf5dfa..0202284 100644 --- a/lib/Sqlite3Helper.py +++ b/lib/Sqlite3Helper.py @@ -134,8 +134,11 @@ class Expression(object): def and_(self, expression: Expression): return Expression(f"{self._expr} AND {expression}") - def or_(self, expression: Expression): - return Expression(f"{self._expr} OR {expression}") + def or_(self, expression: Expression, high_priority: bool = False): + statement = f"{self._expr} OR {expression}" + if high_priority: + statement = f"({statement})" + return Expression(statement) def exists(self, not_: bool = False): mark = "EXISTS" diff --git a/main.py b/main.py index 2ff521a..78eadfd 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ from lib.config_utils import ( from src.mw_kps_unifier import KpsUnifier import src.rc_kps_unifier -__version__ = '0.2.0' +__version__ = '1.0.0' __version_info__ = tuple(map(int, __version__.split('.'))) ORG_NAME = "JnPrograms" diff --git a/src/da_target_login.py b/src/da_target_login.py index 2d4ccd5..5d8b2ff 100644 --- a/src/da_target_login.py +++ b/src/da_target_login.py @@ -43,8 +43,6 @@ class UiDaTargetLogin(object): self.vly_m.addStretch(1) - self.pbn_ok.setFocus() - class DaTargetLogin(QtWidgets.QDialog): def __init__(self, parent=None): diff --git a/src/page_load.py b/src/page_load.py index 72a5d5a..d2f481f 100644 --- a/src/page_load.py +++ b/src/page_load.py @@ -5,7 +5,6 @@ from PySide6 import QtWidgets from pykeepass import PyKeePass from .gbx_kps_login import GbxKpsLogin -from .da_target_login import DaTargetLogin from .utils import accept_warning, HorizontalLine from lib.Sqlite3Helper import Sqlite3Worker @@ -82,12 +81,6 @@ class PageLoad(QtWidgets.QWidget): self.vly_left = QtWidgets.QVBoxLayout() self.hly_m.addLayout(self.vly_left) - self.pbn_set_target = QtWidgets.QPushButton("设置目标文件", self) - # 暂时没用 - self.pbn_set_target.setDisabled(True) - self.pbn_set_target.setMinimumWidth(config["button_min_width"]) - self.vly_left.addWidget(self.pbn_set_target) - self.hln_1 = HorizontalLine(self) self.vly_left.addWidget(self.hln_1) @@ -111,8 +104,6 @@ class PageLoad(QtWidgets.QWidget): self.wg_sa = WgLoadKps(config, file_kp, sqh, sec_sqh, self.sa_m) self.sa_m.setWidget(self.wg_sa) - self.pbn_set_target.clicked.connect(self.on_pbn_set_target_clicked) - self.pbn_add_kps.clicked.connect(self.on_pbn_add_kps_clicked) self.pbn_clear_db.clicked.connect(self.on_pbn_clear_db_clicked) self.pbn_clear_loaded_mem.clicked.connect(self.on_pbn_clear_loaded_mem_clicked) @@ -160,7 +151,3 @@ class PageLoad(QtWidgets.QWidget): # 更新kps加载状态 for wg in self.wg_sa.kps_wgs: self.wg_sa.update_load_status(wg) - - def on_pbn_set_target_clicked(self): - da_target_login = DaTargetLogin(self) - da_target_login.exec() diff --git a/src/page_query.py b/src/page_query.py index 13b68ec..15a190a 100644 --- a/src/page_query.py +++ b/src/page_query.py @@ -5,6 +5,7 @@ from PySide6 import QtWidgets, QtCore, QtGui from pykeepass import PyKeePass from .da_entry_info import DaEntryInfo +from .da_target_login import DaTargetLogin from .utils import HorizontalLine, get_filepath_uuids_map, accept_warning from lib.Sqlite3Helper import Sqlite3Worker, Expression, Operand from lib.db_columns_def import ( @@ -93,6 +94,10 @@ class UiPageQuery(object): self.sa_wg.setLayout(self.vly_sa_wg) self.sa_left.setWidget(self.sa_wg) + 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.pbn_execute = QtWidgets.QPushButton("执行操作", window) self.pbn_execute.setMinimumWidth(config["button_min_width"]) self.vly_sa_wg.addWidget(self.pbn_execute) @@ -135,12 +140,14 @@ class PageQuery(QtWidgets.QWidget): self.sec_sqh = sec_sqh self.config = config self.file_kp = file_kp + self.tar_kp: PyKeePass | None = None 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_set_target.clicked.connect(self.on_pbn_set_target_clicked) self.ui.pbn_execute.clicked.connect(self.on_pbn_execute_clicked) self.ui.pbn_all.clicked.connect(self.on_pbn_all_clicked) @@ -225,37 +232,47 @@ class PageQuery(QtWidgets.QWidget): self.sqh.update("entries", [(status_col, info)], where=Operand(entry_id_col).in_(entry_ids)) - def on_pbn_execute_clicked(self): - if accept_warning(self, True, "警告", "你确定要执行转移和删除操作吗?"): - return + def get_kp(self, filepath: str) -> PyKeePass | None: + if filepath not in self.file_kp: + _, results = self.sec_sqh.select("secrets", [sec_password_col], + where=Operand(sec_filepath_col).equal_to(filepath)) + if len(results) == 0: + QtWidgets.QMessageBox.critical(self, "错误", f"请尝试重新加载\n{filepath}") + return None + password = results[-1][0].decode("utf-8") + kp = PyKeePass(filepath, password) + else: + kp = self.file_kp[filepath] + return kp - # 删除功能 - _, results = self.sqh.select("entries", sim_columns, - where=Operand(status_col).equal_to("delete")) + def delete_the_delete_and_transfer(self, transfer: bool = False): + cond = Operand(status_col).equal_to("delete") + if transfer is True: + cond = cond.or_(Operand(status_col).equal_to("transfer"), high_priority=True) + cond = cond.and_(Operand(deleted_col).equal_to(0)) + + _, results = self.sqh.select("entries", sim_columns, where=cond) 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] + kp = self.get_kp(file) + if kp is None: + total += len(file_uuids[file]) + invalid += len(file_uuids[file]) + continue for u in file_uuids[file]: total += 1 - self.sqh.update("entries", [(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) + self.sqh.update("entries", [(deleted_col, 1)], + where=Operand(uuid_col).equal_to(u).and_( + Operand(filepath_col).equal_to(blob_fy(file)))) success += 1 kp.save() @@ -263,6 +280,67 @@ class PageQuery(QtWidgets.QWidget): QtWidgets.QMessageBox.information(self, "提示", f"共 {total} 条标记的条目,已删除 {success} 条,无效 {invalid} 条。") + def transfer_the_transfer(self): + # 只找标记为转移且还未删除的 + _, results = self.sqh.select("entries", sim_columns, + where=Operand(status_col).equal_to("transfer") + .and_(Operand(deleted_col).equal_to(0))) + file_uuids = get_filepath_uuids_map(results) + + total, success, invalid = 0, 0, 0 + for i, file in enumerate(file_uuids, start=1): + kp = self.get_kp(file) + if kp is None: + total += len(file_uuids[file]) + invalid += len(file_uuids[file]) + continue + + dest_group = self.tar_kp.find_groups(name=file, first=True, + group=self.tar_kp.root_group, + recursive=False) + if dest_group is None: + dest_group = self.tar_kp.add_group(self.tar_kp.root_group, file) + for u in file_uuids[file]: + total += 1 + entry = kp.find_entries(uuid=UUID(u), first=True) + if entry is None: + invalid += 1 + continue + + self.tar_kp.add_entry( + dest_group, + entry.title or "", + entry.username or "", + entry.password or "", + entry.url, + entry.notes, + otp=entry.otp, + force_creation=True + ) + success += 1 + self.tar_kp.save() + QtWidgets.QMessageBox.information(self, "提示", + f"共 {total} 条转移条目,成功 {success} 条,失败 {invalid} 条。") + + def on_pbn_execute_clicked(self): + if accept_warning(self, True, "警告", "你确定要执行转移和删除操作吗?"): + return + + transfer = self.tar_kp is not None + if accept_warning(self, self.tar_kp is None, "警告", + "还没有设置目标文件,继续则只会执行删除操作,继续吗?"): + return + + if transfer: + self.transfer_the_transfer() + + self.delete_the_delete_and_transfer(transfer) + + def on_pbn_set_target_clicked(self): + da_target_login = DaTargetLogin(self) + da_target_login.exec() + self.tar_kp = da_target_login.tar_kp + class PushButtonWithData(QtWidgets.QPushButton): diff --git a/src/page_similar.py b/src/page_similar.py index 6da55e5..c18b689 100644 --- a/src/page_similar.py +++ b/src/page_similar.py @@ -3,7 +3,7 @@ from itertools import combinations from PySide6 import QtWidgets, QtCore from PySide6.QtCore import QAbstractTableModel -from .utils import accept_warning, get_filepath_uuids_map +from .utils import accept_warning, get_filepath_uuids_map, HorizontalLine from lib.Sqlite3Helper import Sqlite3Worker, Operand from lib.db_columns_def import sim_columns, filepath_col from lib.config_utils import path_not_exist @@ -52,6 +52,9 @@ class PageSimilar(QtWidgets.QWidget): self.vly_left = QtWidgets.QVBoxLayout() self.hly_m.addLayout(self.vly_left) + self.hln_1 = HorizontalLine(self) + self.vly_left.addWidget(self.hln_1) + self.pbn_read_db = QtWidgets.QPushButton("读取数据库", self) self.pbn_read_db.setMinimumWidth(config["button_min_width"]) self.vly_left.addWidget(self.pbn_read_db)