From c5818b1d1f78be9201c5e3164177361fea0bf629 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Thu, 31 Mar 2022 00:16:37 -0500 Subject: [PATCH] Add option to profile scans - Add preference for profiling scans - Move debug options to tab in preferences - Add label with clickable link to debug output (appdata) to debug tab in preferences - Update translation source files --- core/app.py | 10 +++++- locale/columns.pot | 6 ++-- locale/core.pot | 72 ++++++++++++++++++------------------- locale/ui.pot | 16 +++++++++ qt/directories_dialog.py | 2 +- qt/me/preferences_dialog.py | 2 -- qt/pe/preferences_dialog.py | 2 -- qt/preferences.py | 3 ++ qt/preferences_dialog.py | 31 ++++++++++++++-- qt/se/preferences_dialog.py | 2 -- 10 files changed, 96 insertions(+), 50 deletions(-) diff --git a/core/app.py b/core/app.py index 449458b8..786e57f3 100644 --- a/core/app.py +++ b/core/app.py @@ -4,6 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html +import cProfile +import datetime import os import os.path as op import logging @@ -780,7 +782,7 @@ class DupeGuru(Broadcaster): except OSError as e: self.view.show_message(tr("Couldn't write to file: {}").format(str(e))) - def start_scanning(self): + def start_scanning(self, profile_scan=False): """Starts an async job to scan for duplicates. Scans folders selected in :attr:`directories` and put the results in :attr:`results` @@ -800,6 +802,9 @@ class DupeGuru(Broadcaster): self._results_changed() def do(j): + if profile_scan: + pr = cProfile.Profile() + pr.enable() j.set_progress(0, tr("Collecting files to scan")) if scanner.scan_type == ScanType.FOLDERS: files = list(self.directories.get_folders(folderclass=se.fs.Folder, j=j)) @@ -810,6 +815,9 @@ class DupeGuru(Broadcaster): logging.info("Scanning %d files" % len(files)) self.results.groups = scanner.get_dupe_groups(files, self.ignore_list, j) self.discarded_file_count = scanner.discarded_file_count + if profile_scan: + pr.disable() + pr.dump_stats(op.join(self.appdata, f"{datetime.datetime.now():%Y-%m-%d_%H-%M-%S}.profile")) self._start_job(JobType.SCAN, do) diff --git a/locale/columns.pot b/locale/columns.pot index 888dba83..eddb5b80 100644 --- a/locale/columns.pot +++ b/locale/columns.pot @@ -25,7 +25,7 @@ msgstr "" msgid "Samplerate" msgstr "" -#: core\me\result_table.py:19 core\pe\result_table.py:19 core\prioritize.py:92 +#: core\me\result_table.py:19 core\pe\result_table.py:19 core\prioritize.py:94 #: core\se\result_table.py:19 msgid "Filename" msgstr "" @@ -53,7 +53,7 @@ msgid "Kind" msgstr "" #: core\me\result_table.py:26 core\pe\result_table.py:25 -#: core\prioritize.py:163 core\se\result_table.py:23 +#: core\prioritize.py:165 core\se\result_table.py:23 msgid "Modification" msgstr "" @@ -111,7 +111,7 @@ msgstr "" msgid "EXIF Timestamp" msgstr "" -#: core\prioritize.py:156 +#: core\prioritize.py:158 msgid "Size" msgstr "" diff --git a/locale/core.pot b/locale/core.pot index 30211b6b..4ee496d9 100644 --- a/locale/core.pot +++ b/locale/core.pot @@ -4,115 +4,115 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: utf-8\n" -#: core\app.py:42 +#: core\app.py:44 msgid "There are no marked duplicates. Nothing has been done." msgstr "" -#: core\app.py:43 +#: core\app.py:45 msgid "There are no selected duplicates. Nothing has been done." msgstr "" -#: core\app.py:44 +#: core\app.py:46 msgid "You're about to open many files at once. Depending on what those files are opened with, doing so can create quite a mess. Continue?" msgstr "" -#: core\app.py:71 +#: core\app.py:73 msgid "Scanning for duplicates" msgstr "" -#: core\app.py:72 +#: core\app.py:74 msgid "Loading" msgstr "" -#: core\app.py:73 +#: core\app.py:75 msgid "Moving" msgstr "" -#: core\app.py:74 +#: core\app.py:76 msgid "Copying" msgstr "" -#: core\app.py:75 +#: core\app.py:77 msgid "Sending to Trash" msgstr "" -#: core\app.py:289 +#: 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." msgstr "" -#: core\app.py:300 +#: core\app.py:302 msgid "No duplicates found." msgstr "" -#: core\app.py:315 +#: core\app.py:317 msgid "All marked files were copied successfully." msgstr "" -#: core\app.py:317 +#: core\app.py:319 msgid "All marked files were moved successfully." msgstr "" -#: core\app.py:319 +#: core\app.py:321 msgid "All marked files were deleted successfully." msgstr "" -#: core\app.py:321 +#: core\app.py:323 msgid "All marked files were successfully sent to Trash." msgstr "" -#: core\app.py:326 +#: core\app.py:328 msgid "Could not load file: {}" msgstr "" -#: core\app.py:382 +#: core\app.py:384 msgid "'{}' already is in the list." msgstr "" -#: core\app.py:384 +#: core\app.py:386 msgid "'{}' does not exist." msgstr "" -#: core\app.py:392 +#: core\app.py:394 msgid "All selected %d matches are going to be ignored in all subsequent scans. Continue?" msgstr "" -#: core\app.py:469 +#: core\app.py:471 msgid "Select a directory to copy marked files to" msgstr "" -#: core\app.py:471 +#: core\app.py:473 msgid "Select a directory to move marked files to" msgstr "" -#: core\app.py:510 +#: core\app.py:512 msgid "Select a destination for your exported CSV" msgstr "" -#: core\app.py:516 core\app.py:771 core\app.py:781 +#: core\app.py:518 core\app.py:773 core\app.py:783 msgid "Couldn't write to file: {}" msgstr "" -#: core\app.py:539 +#: core\app.py:541 msgid "You have no custom command set up. Set it up in your preferences." msgstr "" -#: core\app.py:695 core\app.py:707 +#: core\app.py:697 core\app.py:709 msgid "You are about to remove %d files from results. Continue?" msgstr "" -#: core\app.py:743 +#: core\app.py:745 msgid "{} duplicate groups were changed by the re-prioritization." msgstr "" -#: core\app.py:790 +#: core\app.py:792 msgid "The selected directories contain no scannable file." msgstr "" -#: core\app.py:803 +#: core\app.py:808 msgid "Collecting files to scan" msgstr "" -#: core\app.py:850 +#: core\app.py:858 msgid "%s (%d discarded)" msgstr "" @@ -188,35 +188,35 @@ msgstr "" msgid "None" msgstr "" -#: core\prioritize.py:100 +#: core\prioritize.py:102 msgid "Ends with number" msgstr "" -#: core\prioritize.py:101 +#: core\prioritize.py:103 msgid "Doesn't end with number" msgstr "" -#: core\prioritize.py:102 +#: core\prioritize.py:104 msgid "Longest" msgstr "" -#: core\prioritize.py:103 +#: core\prioritize.py:105 msgid "Shortest" msgstr "" -#: core\prioritize.py:140 +#: core\prioritize.py:142 msgid "Highest" msgstr "" -#: core\prioritize.py:140 +#: core\prioritize.py:142 msgid "Lowest" msgstr "" -#: core\prioritize.py:169 +#: core\prioritize.py:171 msgid "Newest" msgstr "" -#: core\prioritize.py:169 +#: core\prioritize.py:171 msgid "Oldest" msgstr "" diff --git a/locale/ui.pot b/locale/ui.pot index 59b45ec4..fe273a7e 100644 --- a/locale/ui.pot +++ b/locale/ui.pot @@ -945,3 +945,19 @@ msgstr "" #: qt\preferences_dialog.py:173 msgid "Use dark style" msgstr "" + +#: qt\preferences_dialog.py:241 +msgid "Profile scan operation" +msgstr "" + +#: qt\preferences_dialog.py:242 +msgid "Profile the scan operation and save logs for optimization." +msgstr "" + +#: qt\preferences_dialog.py:246 +msgid "Logs located in: {}" +msgstr "" + +#: qt\preferences_dialog.py:291 +msgid "Debug" +msgstr "" diff --git a/qt/directories_dialog.py b/qt/directories_dialog.py index 56a938ba..617ac868 100644 --- a/qt/directories_dialog.py +++ b/qt/directories_dialog.py @@ -356,7 +356,7 @@ class DirectoriesDialog(QMainWindow): msg = tr("You have unsaved results, do you really want to continue?") if not self.app.confirm(title, msg): return - self.app.model.start_scanning() + self.app.model.start_scanning(self.app.prefs.profile_scan) def scanTypeChanged(self, index): scan_options = self.app.model.SCANNER_CLASS.get_scan_options() diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index a580c0df..fee4409b 100644 --- a/qt/me/preferences_dialog.py +++ b/qt/me/preferences_dialog.py @@ -68,8 +68,6 @@ class PreferencesDialog(PreferencesDialogBase): tr("Ignore duplicates hardlinking to the same file"), ) self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches) - self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)")) - self.widgetsVLayout.addWidget(self.debugModeBox) self._setupBottomPart() def _load(self, prefs, setchecked, section): diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 98398463..1a246637 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -34,8 +34,6 @@ class PreferencesDialog(PreferencesDialogBase): tr("Ignore duplicates hardlinking to the same file"), ) self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches) - self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)")) - self.widgetsVLayout.addWidget(self.debugModeBox) self.cacheTypeRadio = RadioBox(self, items=["Sqlite", "Shelve"], spread=False) cache_form = QFormLayout() diff --git a/qt/preferences.py b/qt/preferences.py index 7e25daef..37538fa4 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -24,6 +24,7 @@ class Preferences(PreferencesBase): self.use_regexp = get("UseRegexp", self.use_regexp) self.remove_empty_folders = get("RemoveEmptyFolders", self.remove_empty_folders) self.debug_mode = get("DebugMode", self.debug_mode) + self.profile_scan = get("ProfileScan", self.profile_scan) self.destination_type = get("DestinationType", self.destination_type) self.custom_command = get("CustomCommand", self.custom_command) self.language = get("Language", self.language) @@ -93,6 +94,7 @@ class Preferences(PreferencesBase): self.ignore_hardlink_matches = False self.remove_empty_folders = False self.debug_mode = False + self.profile_scan = False self.destination_type = 1 self.custom_command = "" self.language = trans.installed_lang if trans.installed_lang else "" @@ -144,6 +146,7 @@ class Preferences(PreferencesBase): set_("UseRegexp", self.use_regexp) set_("RemoveEmptyFolders", self.remove_empty_folders) set_("DebugMode", self.debug_mode) + set_("ProfileScan", self.profile_scan) set_("DestinationType", self.destination_type) set_("CustomCommand", self.custom_command) set_("Language", self.language) diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index 77040045..b70a1a1c 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -29,7 +29,7 @@ from PyQt5.QtWidgets import ( QFormLayout, ) from PyQt5.QtGui import QPixmap, QIcon -from hscommon import plat +from hscommon import desktop, plat from hscommon.trans import trget from hscommon.plat import ISLINUX @@ -69,7 +69,8 @@ class Sections(Flag): GENERAL = auto() DISPLAY = auto() - ALL = GENERAL | DISPLAY + DEBUG = auto() + ALL = GENERAL | DISPLAY | DEBUG class PreferencesDialogBase(QDialog): @@ -82,6 +83,7 @@ class PreferencesDialogBase(QDialog): self._setupUi() self.filterHardnessSlider.valueChanged["int"].connect(self.filterHardnessLabel.setNum) + self.debug_location_label.linkActivated.connect(desktop.open_path) self.buttonBox.clicked.connect(self.buttonClicked) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) @@ -234,6 +236,18 @@ use the modifier key to drag the floating window around" details_groupbox.setLayout(self.details_groupbox_layout) self.displayVLayout.addWidget(details_groupbox) + def _setupDebugPage(self): + self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)")) + self._setupAddCheckbox("profile_scan_box", tr("Profile scan operation")) + self.profile_scan_box.setToolTip(tr("Profile the scan operation and save logs for optimization.")) + self.debugVLayout.addWidget(self.debugModeBox) + self.debugVLayout.addWidget(self.profile_scan_box) + self.debug_location_label = QLabel( + tr('Logs located in: {}').format(self.app.model.appdata, self.app.model.appdata), + wordWrap=True, + ) + self.debugVLayout.addWidget(self.debug_location_label) + def _setupAddCheckbox(self, name, label, parent=None): if parent is None: parent = self @@ -253,13 +267,17 @@ use the modifier key to drag the floating window around" self.tabwidget = QTabWidget() self.page_general = QWidget() self.page_display = QWidget() + self.page_debug = QWidget() self.widgetsVLayout = QVBoxLayout() self.page_general.setLayout(self.widgetsVLayout) self.displayVLayout = QVBoxLayout() self.displayVLayout.setSpacing(5) # arbitrary value, might conflict with style self.page_display.setLayout(self.displayVLayout) + self.debugVLayout = QVBoxLayout() + self.page_debug.setLayout(self.debugVLayout) self._setupPreferenceWidgets() self._setupDisplayPage() + self._setupDebugPage() # self.mainVLayout.addLayout(self.widgetsVLayout) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons( @@ -270,8 +288,10 @@ use the modifier key to drag the floating window around" self.layout().setSizeConstraint(QLayout.SetFixedSize) self.tabwidget.addTab(self.page_general, tr("General")) self.tabwidget.addTab(self.page_display, tr("Display")) + self.tabwidget.addTab(self.page_debug, tr("Debug")) self.displayVLayout.addStretch(0) self.widgetsVLayout.addStretch(0) + self.debugVLayout.addStretch(0) def _load(self, prefs, setchecked, section): # Edition-specific @@ -295,7 +315,6 @@ use the modifier key to drag the floating window around" setchecked(self.useRegexpBox, prefs.use_regexp) setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders) setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches) - setchecked(self.debugModeBox, prefs.debug_mode) self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type) self.customCommandEdit.setText(prefs.custom_command) if section & Sections.DISPLAY: @@ -322,6 +341,9 @@ use the modifier key to drag the floating window around" except ValueError: langindex = 0 self.languageComboBox.setCurrentIndex(langindex) + if section & Sections.DEBUG: + setchecked(self.debugModeBox, prefs.debug_mode) + setchecked(self.profile_scan_box, prefs.profile_scan) self._load(prefs, setchecked, section) def save(self): @@ -336,6 +358,7 @@ use the modifier key to drag the floating window around" prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox) prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches) prefs.debug_mode = ischecked(self.debugModeBox) + prefs.profile_scan = ischecked(self.profile_scan_box) prefs.reference_bold_font = ischecked(self.reference_bold_font) prefs.details_dialog_titlebar_enabled = ischecked(self.details_dialog_titlebar_enabled) prefs.details_dialog_vertical_titlebar = ischecked(self.details_dialog_vertical_titlebar) @@ -376,6 +399,8 @@ use the modifier key to drag the floating window around" section_to_update = Sections.GENERAL if current_tab is self.page_display: section_to_update = Sections.DISPLAY + if current_tab is self.page_debug: + section_to_update = Sections.DEBUG self.resetToDefaults(section_to_update) def showEvent(self, event): diff --git a/qt/se/preferences_dialog.py b/qt/se/preferences_dialog.py index 004925a1..e6a9f537 100644 --- a/qt/se/preferences_dialog.py +++ b/qt/se/preferences_dialog.py @@ -103,8 +103,6 @@ class PreferencesDialog(PreferencesDialogBase): self.widget, ) self.verticalLayout_4.addWidget(self.ignoreHardlinkMatches) - self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"), self.widget) - self.verticalLayout_4.addWidget(self.debugModeBox) self.widgetsVLayout.addWidget(self.widget) self._setupBottomPart()