1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2025-05-08 09:49:51 +00:00

Compare commits

..

No commits in common. "091cae0cc69f00fb80f667e1e9a48a9bfe4d94fa" and "1db93fd1422bf4df291c55bf20febfd62502ec74" have entirely different histories.

9 changed files with 38 additions and 129 deletions

View File

@ -154,8 +154,6 @@ class DupeGuru(Broadcaster):
"ignore_hardlink_matches": False, "ignore_hardlink_matches": False,
"copymove_dest_type": DestType.RELATIVE, "copymove_dest_type": DestType.RELATIVE,
"picture_cache_type": self.PICTURE_CACHE_TYPE, "picture_cache_type": self.PICTURE_CACHE_TYPE,
"include_exists_check": True,
"rehash_ignore_mtime": False,
} }
self.selected_dupes = [] self.selected_dupes = []
self.details_panel = DetailsPanel(self) self.details_panel = DetailsPanel(self)
@ -557,9 +555,7 @@ class DupeGuru(Broadcaster):
# a workaround to make the damn thing work. # a workaround to make the damn thing work.
exepath, args = match.groups() exepath, args = match.groups()
path, exename = op.split(exepath) path, exename = op.split(exepath)
p = subprocess.Popen( p = subprocess.Popen(exename + args, shell=True, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
exename + args, shell=True, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
output = p.stdout.read() output = p.stdout.read()
logging.info("Custom command %s %s: %s", exename, args, output) logging.info("Custom command %s %s: %s", exename, args, output)
else: else:
@ -796,7 +792,6 @@ class DupeGuru(Broadcaster):
Scans folders selected in :attr:`directories` and put the results in :attr:`results` Scans folders selected in :attr:`directories` and put the results in :attr:`results`
""" """
scanner = self.SCANNER_CLASS() scanner = self.SCANNER_CLASS()
fs.filesdb.ignore_mtime = self.options["rehash_ignore_mtime"] is True
if not self.directories.has_any_file(): if not self.directories.has_any_file():
self.view.show_message(tr("The selected directories contain no scannable file.")) self.view.show_message(tr("The selected directories contain no scannable file."))
return return

View File

@ -100,14 +100,11 @@ class FilesDB:
create_table_query = "CREATE TABLE IF NOT EXISTS files (path TEXT PRIMARY KEY, size INTEGER, mtime_ns INTEGER, entry_dt DATETIME, digest BLOB, digest_partial BLOB, digest_samples BLOB)" create_table_query = "CREATE TABLE IF NOT EXISTS files (path TEXT PRIMARY KEY, size INTEGER, mtime_ns INTEGER, entry_dt DATETIME, digest BLOB, digest_partial BLOB, digest_samples BLOB)"
drop_table_query = "DROP TABLE IF EXISTS files;" drop_table_query = "DROP TABLE IF EXISTS files;"
select_query = "SELECT {key} FROM files WHERE path=:path AND size=:size and mtime_ns=:mtime_ns" select_query = "SELECT {key} FROM files WHERE path=:path AND size=:size and mtime_ns=:mtime_ns"
select_query_ignore_mtime = "SELECT {key} FROM files WHERE path=:path AND size=:size"
insert_query = """ insert_query = """
INSERT INTO files (path, size, mtime_ns, entry_dt, {key}) VALUES (:path, :size, :mtime_ns, datetime('now'), :value) INSERT INTO files (path, size, mtime_ns, entry_dt, {key}) VALUES (:path, :size, :mtime_ns, datetime('now'), :value)
ON CONFLICT(path) DO UPDATE SET size=:size, mtime_ns=:mtime_ns, entry_dt=datetime('now'), {key}=:value; ON CONFLICT(path) DO UPDATE SET size=:size, mtime_ns=:mtime_ns, entry_dt=datetime('now'), {key}=:value;
""" """
ignore_mtime = False
def __init__(self): def __init__(self):
self.conn = None self.conn = None
self.cur = None self.cur = None
@ -149,12 +146,9 @@ class FilesDB:
mtime_ns = stat.st_mtime_ns mtime_ns = stat.st_mtime_ns
try: try:
with self.lock: with self.lock:
if self.ignore_mtime: self.cur.execute(
self.cur.execute(self.select_query_ignore_mtime.format(key=key), {"path": str(path), "size": size}) self.select_query.format(key=key), {"path": str(path), "size": size, "mtime_ns": mtime_ns}
else: )
self.cur.execute(
self.select_query.format(key=key), {"path": str(path), "size": size, "mtime_ns": mtime_ns}
)
result = self.cur.fetchone() result = self.cur.fetchone()
if result: if result:

View File

@ -171,8 +171,7 @@ class Scanner:
matches = [m for m in matches if m.first.path not in toremove or m.second.path not in toremove] matches = [m for m in matches if m.first.path not in toremove or m.second.path not in toremove]
if not self.mix_file_kind: if not self.mix_file_kind:
matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)] matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)]
if self.include_exists_check: matches = [m for m in matches if m.first.path.exists() and m.second.path.exists()]
matches = [m for m in matches if m.first.path.exists() and m.second.path.exists()]
matches = [m for m in matches if not (m.first.is_ref and m.second.is_ref)] matches = [m for m in matches if not (m.first.is_ref and m.second.is_ref)]
if ignore_list: if ignore_list:
matches = [m for m in matches if not ignore_list.are_ignored(str(m.first.path), str(m.second.path))] matches = [m for m in matches if not ignore_list.are_ignored(str(m.first.path), str(m.second.path))]
@ -213,4 +212,3 @@ class Scanner:
large_size_threshold = 0 large_size_threshold = 0
big_file_size_threshold = 0 big_file_size_threshold = 0
word_weighting = False word_weighting = False
include_exists_check = True

View File

@ -36,83 +36,83 @@ msgstr ""
msgid "Sending to Trash" msgid "Sending to Trash"
msgstr "" msgstr ""
#: core\app.py:293 #: core\app.py:291
msgid "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." msgid "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
msgstr "" msgstr ""
#: core\app.py:304 #: core\app.py:302
msgid "No duplicates found." msgid "No duplicates found."
msgstr "" msgstr ""
#: core\app.py:319 #: core\app.py:317
msgid "All marked files were copied successfully." msgid "All marked files were copied successfully."
msgstr "" msgstr ""
#: core\app.py:321 #: core\app.py:319
msgid "All marked files were moved successfully." msgid "All marked files were moved successfully."
msgstr "" msgstr ""
#: core\app.py:323 #: core\app.py:321
msgid "All marked files were deleted successfully." msgid "All marked files were deleted successfully."
msgstr "" msgstr ""
#: core\app.py:325 #: core\app.py:323
msgid "All marked files were successfully sent to Trash." msgid "All marked files were successfully sent to Trash."
msgstr "" msgstr ""
#: core\app.py:330 #: core\app.py:328
msgid "Could not load file: {}" msgid "Could not load file: {}"
msgstr "" msgstr ""
#: core\app.py:386 #: core\app.py:384
msgid "'{}' already is in the list." msgid "'{}' already is in the list."
msgstr "" msgstr ""
#: core\app.py:388 #: core\app.py:386
msgid "'{}' does not exist." msgid "'{}' does not exist."
msgstr "" msgstr ""
#: core\app.py:396 #: core\app.py:394
msgid "All selected %d matches are going to be ignored in all subsequent scans. Continue?" msgid "All selected %d matches are going to be ignored in all subsequent scans. Continue?"
msgstr "" msgstr ""
#: core\app.py:473 #: core\app.py:471
msgid "Select a directory to copy marked files to" msgid "Select a directory to copy marked files to"
msgstr "" msgstr ""
#: core\app.py:475 #: core\app.py:473
msgid "Select a directory to move marked files to" msgid "Select a directory to move marked files to"
msgstr "" msgstr ""
#: core\app.py:514 #: core\app.py:512
msgid "Select a destination for your exported CSV" msgid "Select a destination for your exported CSV"
msgstr "" msgstr ""
#: core\app.py:520 core\app.py:781 core\app.py:791 #: core\app.py:518 core\app.py:773 core\app.py:783
msgid "Couldn't write to file: {}" msgid "Couldn't write to file: {}"
msgstr "" msgstr ""
#: core\app.py:543 #: core\app.py:541
msgid "You have no custom command set up. Set it up in your preferences." msgid "You have no custom command set up. Set it up in your preferences."
msgstr "" msgstr ""
#: core\app.py:705 core\app.py:717 #: core\app.py:697 core\app.py:709
msgid "You are about to remove %d files from results. Continue?" msgid "You are about to remove %d files from results. Continue?"
msgstr "" msgstr ""
#: core\app.py:753 #: core\app.py:745
msgid "{} duplicate groups were changed by the re-prioritization." msgid "{} duplicate groups were changed by the re-prioritization."
msgstr "" msgstr ""
#: core\app.py:801 #: core\app.py:792
msgid "The selected directories contain no scannable file." msgid "The selected directories contain no scannable file."
msgstr "" msgstr ""
#: core\app.py:817 #: core\app.py:808
msgid "Collecting files to scan" msgid "Collecting files to scan"
msgstr "" msgstr ""
#: core\app.py:867 #: core\app.py:858
msgid "%s (%d discarded)" msgid "%s (%d discarded)"
msgstr "" msgstr ""

View File

@ -1092,25 +1092,3 @@ msgstr ""
#: qt\search_edit.py:78 #: qt\search_edit.py:78
msgid "Search..." msgid "Search..."
msgstr "" msgstr ""
#: qt\preferences_dialog.py:219
msgid ""
"These options are for advanced users or for very specific situations, most "
"users should not have to modify these."
msgstr ""
#: qt\preferences_dialog.py:225
msgid "Include existence check after scan completion"
msgstr ""
#: qt\preferences_dialog.py:227
msgid "Ignore difference in mtime when loading cached digests"
msgstr ""
#: qt\progress_window.py:64
msgid "Cancel?"
msgstr ""
#: qt\progress_window.py:65
msgid "Are you sure you want to cancel? All progress will be lost."
msgstr ""

View File

@ -193,8 +193,6 @@ class DupeGuru(QObject):
self.model.options["scanned_tags"] = scanned_tags self.model.options["scanned_tags"] = scanned_tags
self.model.options["match_scaled"] = self.prefs.match_scaled self.model.options["match_scaled"] = self.prefs.match_scaled
self.model.options["picture_cache_type"] = self.prefs.picture_cache_type self.model.options["picture_cache_type"] = self.prefs.picture_cache_type
self.model.options["include_exists_check"] = self.prefs.include_exists_check
self.model.options["rehash_ignore_mtime"] = self.prefs.rehash_ignore_mtime
if self.details_dialog: if self.details_dialog:
self.details_dialog.update_options() self.details_dialog.update_options()

View File

@ -161,8 +161,6 @@ class Preferences(PreferencesBase):
self.ignore_hardlink_matches = get("IgnoreHardlinkMatches", self.ignore_hardlink_matches) self.ignore_hardlink_matches = get("IgnoreHardlinkMatches", self.ignore_hardlink_matches)
self.use_regexp = get("UseRegexp", self.use_regexp) self.use_regexp = get("UseRegexp", self.use_regexp)
self.remove_empty_folders = get("RemoveEmptyFolders", self.remove_empty_folders) self.remove_empty_folders = get("RemoveEmptyFolders", self.remove_empty_folders)
self.rehash_ignore_mtime = get("RehashIgnoreMTime", self.rehash_ignore_mtime)
self.include_exists_check = get("IncludeExistsCheck", self.include_exists_check)
self.debug_mode = get("DebugMode", self.debug_mode) self.debug_mode = get("DebugMode", self.debug_mode)
self.profile_scan = get("ProfileScan", self.profile_scan) self.profile_scan = get("ProfileScan", self.profile_scan)
self.destination_type = get("DestinationType", self.destination_type) self.destination_type = get("DestinationType", self.destination_type)
@ -233,8 +231,6 @@ class Preferences(PreferencesBase):
self.use_regexp = False self.use_regexp = False
self.ignore_hardlink_matches = False self.ignore_hardlink_matches = False
self.remove_empty_folders = False self.remove_empty_folders = False
self.rehash_ignore_mtime = False
self.include_exists_check = True
self.debug_mode = False self.debug_mode = False
self.profile_scan = False self.profile_scan = False
self.destination_type = 1 self.destination_type = 1
@ -287,8 +283,6 @@ class Preferences(PreferencesBase):
set_("IgnoreHardlinkMatches", self.ignore_hardlink_matches) set_("IgnoreHardlinkMatches", self.ignore_hardlink_matches)
set_("UseRegexp", self.use_regexp) set_("UseRegexp", self.use_regexp)
set_("RemoveEmptyFolders", self.remove_empty_folders) set_("RemoveEmptyFolders", self.remove_empty_folders)
set_("RehashIgnoreMTime", self.rehash_ignore_mtime)
set_("IncludeExistsCheck", self.include_exists_check)
set_("DebugMode", self.debug_mode) set_("DebugMode", self.debug_mode)
set_("ProfileScan", self.profile_scan) set_("ProfileScan", self.profile_scan)
set_("DestinationType", self.destination_type) set_("DestinationType", self.destination_type)

View File

@ -47,9 +47,8 @@ class Sections(Flag):
GENERAL = auto() GENERAL = auto()
DISPLAY = auto() DISPLAY = auto()
ADVANCED = auto()
DEBUG = auto() DEBUG = auto()
ALL = GENERAL | DISPLAY | ADVANCED | DEBUG ALL = GENERAL | DISPLAY | DEBUG
class PreferencesDialogBase(QDialog): class PreferencesDialogBase(QDialog):
@ -214,19 +213,6 @@ use the modifier key to drag the floating window around"
details_groupbox.setLayout(self.details_groupbox_layout) details_groupbox.setLayout(self.details_groupbox_layout)
self.displayVLayout.addWidget(details_groupbox) self.displayVLayout.addWidget(details_groupbox)
def _setup_advanced_page(self):
tab_label = QLabel(
tr(
"These options are for advanced users or for very specific situations, most users should not have to modify these."
),
wordWrap=True,
)
self.advanced_vlayout.addWidget(tab_label)
self._setupAddCheckbox("include_exists_check_box", tr("Include existence check after scan completion"))
self.advanced_vlayout.addWidget(self.include_exists_check_box)
self._setupAddCheckbox("rehash_ignore_mtime_box", tr("Ignore difference in mtime when loading cached digests"))
self.advanced_vlayout.addWidget(self.rehash_ignore_mtime_box)
def _setupDebugPage(self): def _setupDebugPage(self):
self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)")) self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"))
self._setupAddCheckbox("profile_scan_box", tr("Profile scan operation")) self._setupAddCheckbox("profile_scan_box", tr("Profile scan operation"))
@ -258,20 +244,16 @@ use the modifier key to drag the floating window around"
self.tabwidget = QTabWidget() self.tabwidget = QTabWidget()
self.page_general = QWidget() self.page_general = QWidget()
self.page_display = QWidget() self.page_display = QWidget()
self.page_advanced = QWidget()
self.page_debug = QWidget() self.page_debug = QWidget()
self.widgetsVLayout = QVBoxLayout() self.widgetsVLayout = QVBoxLayout()
self.page_general.setLayout(self.widgetsVLayout) self.page_general.setLayout(self.widgetsVLayout)
self.displayVLayout = QVBoxLayout() self.displayVLayout = QVBoxLayout()
self.displayVLayout.setSpacing(5) # arbitrary value, might conflict with style self.displayVLayout.setSpacing(5) # arbitrary value, might conflict with style
self.page_display.setLayout(self.displayVLayout) self.page_display.setLayout(self.displayVLayout)
self.advanced_vlayout = QVBoxLayout()
self.page_advanced.setLayout(self.advanced_vlayout)
self.debugVLayout = QVBoxLayout() self.debugVLayout = QVBoxLayout()
self.page_debug.setLayout(self.debugVLayout) self.page_debug.setLayout(self.debugVLayout)
self._setupPreferenceWidgets() self._setupPreferenceWidgets()
self._setupDisplayPage() self._setupDisplayPage()
self._setup_advanced_page()
self._setupDebugPage() self._setupDebugPage()
# self.mainVLayout.addLayout(self.widgetsVLayout) # self.mainVLayout.addLayout(self.widgetsVLayout)
self.buttonBox = QDialogButtonBox(self) self.buttonBox = QDialogButtonBox(self)
@ -283,11 +265,9 @@ use the modifier key to drag the floating window around"
self.layout().setSizeConstraint(QLayout.SetFixedSize) self.layout().setSizeConstraint(QLayout.SetFixedSize)
self.tabwidget.addTab(self.page_general, tr("General")) self.tabwidget.addTab(self.page_general, tr("General"))
self.tabwidget.addTab(self.page_display, tr("Display")) self.tabwidget.addTab(self.page_display, tr("Display"))
self.tabwidget.addTab(self.page_advanced, tr("Advanced"))
self.tabwidget.addTab(self.page_debug, tr("Debug")) self.tabwidget.addTab(self.page_debug, tr("Debug"))
self.displayVLayout.addStretch(0) self.displayVLayout.addStretch(0)
self.widgetsVLayout.addStretch(0) self.widgetsVLayout.addStretch(0)
self.advanced_vlayout.addStretch(0)
self.debugVLayout.addStretch(0) self.debugVLayout.addStretch(0)
def _load(self, prefs, setchecked, section): def _load(self, prefs, setchecked, section):
@ -338,9 +318,6 @@ use the modifier key to drag the floating window around"
except KeyError: except KeyError:
selected_lang = self.supportedLanguages["en"] selected_lang = self.supportedLanguages["en"]
self.languageComboBox.setCurrentText(selected_lang) self.languageComboBox.setCurrentText(selected_lang)
if section & Sections.ADVANCED:
setchecked(self.rehash_ignore_mtime_box, prefs.rehash_ignore_mtime)
setchecked(self.include_exists_check_box, prefs.include_exists_check)
if section & Sections.DEBUG: if section & Sections.DEBUG:
setchecked(self.debugModeBox, prefs.debug_mode) setchecked(self.debugModeBox, prefs.debug_mode)
setchecked(self.profile_scan_box, prefs.profile_scan) setchecked(self.profile_scan_box, prefs.profile_scan)
@ -357,8 +334,6 @@ use the modifier key to drag the floating window around"
prefs.use_regexp = ischecked(self.useRegexpBox) prefs.use_regexp = ischecked(self.useRegexpBox)
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox) prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches) prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches)
prefs.rehash_ignore_mtime = ischecked(self.rehash_ignore_mtime_box)
prefs.include_exists_check = ischecked(self.include_exists_check_box)
prefs.debug_mode = ischecked(self.debugModeBox) prefs.debug_mode = ischecked(self.debugModeBox)
prefs.profile_scan = ischecked(self.profile_scan_box) prefs.profile_scan = ischecked(self.profile_scan_box)
prefs.reference_bold_font = ischecked(self.reference_bold_font) prefs.reference_bold_font = ischecked(self.reference_bold_font)

View File

@ -5,7 +5,7 @@
# http://www.gnu.org/licenses/gpl-3.0.html # http://www.gnu.org/licenses/gpl-3.0.html
from PyQt5.QtCore import Qt, QTimer from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QDialog, QMessageBox, QVBoxLayout, QLabel, QProgressBar, QPushButton from PyQt5.QtWidgets import QProgressDialog
from hscommon.trans import tr from hscommon.trans import tr
@ -25,60 +25,37 @@ class ProgressWindow:
def refresh(self): # Labels def refresh(self): # Labels
if self._window is not None: if self._window is not None:
self._window.setWindowTitle(self.model.jobdesc_textfield.text) self._window.setWindowTitle(self.model.jobdesc_textfield.text)
self._label.setText(self.model.progressdesc_textfield.text) self._window.setLabelText(self.model.progressdesc_textfield.text)
def set_progress(self, last_progress): def set_progress(self, last_progress):
if self._window is not None: if self._window is not None:
if last_progress < 0: if last_progress < 0:
self._progress_bar.setRange(0, 0) self._window.setRange(0, 0)
else: else:
self._progress_bar.setRange(0, 100) self._window.setRange(0, 100)
self._progress_bar.setValue(last_progress) self._window.setValue(last_progress)
def show(self): def show(self):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
self._window = QDialog(self.parent, flags) self._window = QProgressDialog("", tr("Cancel"), 0, 100, self.parent, flags)
self._setup_ui()
self._window.setModal(True) self._window.setModal(True)
self._window.setAutoReset(False)
self._window.setAutoClose(False)
self._timer = QTimer(self._window) self._timer = QTimer(self._window)
self._timer.timeout.connect(self.model.pulse) self._timer.timeout.connect(self.model.pulse)
self._window.show() self._window.show()
self._window.canceled.connect(self.model.cancel)
self._timer.start(500) self._timer.start(500)
def _setup_ui(self):
self._window.setWindowTitle(tr("Cancel"))
vertical_layout = QVBoxLayout(self._window)
self._label = QLabel("", self._window)
vertical_layout.addWidget(self._label)
self._progress_bar = QProgressBar(self._window)
self._progress_bar.setRange(0, 100)
vertical_layout.addWidget(self._progress_bar)
self._cancel_button = QPushButton(tr("Cancel"), self._window)
self._cancel_button.clicked.connect(self.cancel)
vertical_layout.addWidget(self._cancel_button)
def cancel(self):
if self._window is not None:
confirm_dialog = QMessageBox(
QMessageBox.Icon.Question,
tr("Cancel?"),
tr("Are you sure you want to cancel? All progress will be lost."),
QMessageBox.StandardButton.No | QMessageBox.StandardButton.Yes,
self._window,
)
confirm_dialog.setDefaultButton(QMessageBox.StandardButton.No)
result = confirm_dialog.exec_()
if result != QMessageBox.StandardButton.Yes:
return
self.close()
def close(self): def close(self):
# it seems it is possible for close to be called without a corresponding # it seems it is possible for close to be called without a corresponding
# show, only perform a close if there is a window to close # show, only perform a close if there is a window to close
if self._window is not None: if self._window is not None:
self._timer.stop() self._timer.stop()
del self._timer del self._timer
# For some weird reason, canceled() signal is sent upon close, whether the user canceled
# or not. If we don't want a false cancellation, we have to disconnect it.
self._window.canceled.disconnect()
self._window.close() self._window.close()
self._window.setParent(None) self._window.setParent(None)
self._window = None self._window = None
self.model.cancel()