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
This commit is contained in:
Andrew Senetar 2022-03-31 00:16:37 -05:00
parent a470a8de25
commit c5818b1d1f
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
10 changed files with 96 additions and 50 deletions

View File

@ -4,6 +4,8 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html # http://www.gnu.org/licenses/gpl-3.0.html
import cProfile
import datetime
import os import os
import os.path as op import os.path as op
import logging import logging
@ -780,7 +782,7 @@ class DupeGuru(Broadcaster):
except OSError as e: except OSError as e:
self.view.show_message(tr("Couldn't write to file: {}").format(str(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. """Starts an async job to scan for duplicates.
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`
@ -800,6 +802,9 @@ class DupeGuru(Broadcaster):
self._results_changed() self._results_changed()
def do(j): def do(j):
if profile_scan:
pr = cProfile.Profile()
pr.enable()
j.set_progress(0, tr("Collecting files to scan")) j.set_progress(0, tr("Collecting files to scan"))
if scanner.scan_type == ScanType.FOLDERS: if scanner.scan_type == ScanType.FOLDERS:
files = list(self.directories.get_folders(folderclass=se.fs.Folder, j=j)) 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)) logging.info("Scanning %d files" % len(files))
self.results.groups = scanner.get_dupe_groups(files, self.ignore_list, j) self.results.groups = scanner.get_dupe_groups(files, self.ignore_list, j)
self.discarded_file_count = scanner.discarded_file_count 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) self._start_job(JobType.SCAN, do)

View File

@ -25,7 +25,7 @@ msgstr ""
msgid "Samplerate" msgid "Samplerate"
msgstr "" 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 #: core\se\result_table.py:19
msgid "Filename" msgid "Filename"
msgstr "" msgstr ""
@ -53,7 +53,7 @@ msgid "Kind"
msgstr "" msgstr ""
#: core\me\result_table.py:26 core\pe\result_table.py:25 #: 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" msgid "Modification"
msgstr "" msgstr ""
@ -111,7 +111,7 @@ msgstr ""
msgid "EXIF Timestamp" msgid "EXIF Timestamp"
msgstr "" msgstr ""
#: core\prioritize.py:156 #: core\prioritize.py:158
msgid "Size" msgid "Size"
msgstr "" msgstr ""

View File

@ -4,115 +4,115 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 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." msgid "There are no marked duplicates. Nothing has been done."
msgstr "" msgstr ""
#: core\app.py:43 #: core\app.py:45
msgid "There are no selected duplicates. Nothing has been done." msgid "There are no selected duplicates. Nothing has been done."
msgstr "" 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?" 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 "" msgstr ""
#: core\app.py:71 #: core\app.py:73
msgid "Scanning for duplicates" msgid "Scanning for duplicates"
msgstr "" msgstr ""
#: core\app.py:72 #: core\app.py:74
msgid "Loading" msgid "Loading"
msgstr "" msgstr ""
#: core\app.py:73 #: core\app.py:75
msgid "Moving" msgid "Moving"
msgstr "" msgstr ""
#: core\app.py:74 #: core\app.py:76
msgid "Copying" msgid "Copying"
msgstr "" msgstr ""
#: core\app.py:75 #: core\app.py:77
msgid "Sending to Trash" msgid "Sending to Trash"
msgstr "" 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." 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:300 #: core\app.py:302
msgid "No duplicates found." msgid "No duplicates found."
msgstr "" msgstr ""
#: core\app.py:315 #: core\app.py:317
msgid "All marked files were copied successfully." msgid "All marked files were copied successfully."
msgstr "" msgstr ""
#: core\app.py:317 #: core\app.py:319
msgid "All marked files were moved successfully." msgid "All marked files were moved successfully."
msgstr "" msgstr ""
#: core\app.py:319 #: core\app.py:321
msgid "All marked files were deleted successfully." msgid "All marked files were deleted successfully."
msgstr "" msgstr ""
#: core\app.py:321 #: 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:326 #: core\app.py:328
msgid "Could not load file: {}" msgid "Could not load file: {}"
msgstr "" msgstr ""
#: core\app.py:382 #: core\app.py:384
msgid "'{}' already is in the list." msgid "'{}' already is in the list."
msgstr "" msgstr ""
#: core\app.py:384 #: core\app.py:386
msgid "'{}' does not exist." msgid "'{}' does not exist."
msgstr "" msgstr ""
#: core\app.py:392 #: 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:469 #: 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:471 #: 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:510 #: 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: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: {}" msgid "Couldn't write to file: {}"
msgstr "" msgstr ""
#: core\app.py:539 #: 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:695 core\app.py:707 #: 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:743 #: 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:790 #: 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:803 #: core\app.py:808
msgid "Collecting files to scan" msgid "Collecting files to scan"
msgstr "" msgstr ""
#: core\app.py:850 #: core\app.py:858
msgid "%s (%d discarded)" msgid "%s (%d discarded)"
msgstr "" msgstr ""
@ -188,35 +188,35 @@ msgstr ""
msgid "None" msgid "None"
msgstr "" msgstr ""
#: core\prioritize.py:100 #: core\prioritize.py:102
msgid "Ends with number" msgid "Ends with number"
msgstr "" msgstr ""
#: core\prioritize.py:101 #: core\prioritize.py:103
msgid "Doesn't end with number" msgid "Doesn't end with number"
msgstr "" msgstr ""
#: core\prioritize.py:102 #: core\prioritize.py:104
msgid "Longest" msgid "Longest"
msgstr "" msgstr ""
#: core\prioritize.py:103 #: core\prioritize.py:105
msgid "Shortest" msgid "Shortest"
msgstr "" msgstr ""
#: core\prioritize.py:140 #: core\prioritize.py:142
msgid "Highest" msgid "Highest"
msgstr "" msgstr ""
#: core\prioritize.py:140 #: core\prioritize.py:142
msgid "Lowest" msgid "Lowest"
msgstr "" msgstr ""
#: core\prioritize.py:169 #: core\prioritize.py:171
msgid "Newest" msgid "Newest"
msgstr "" msgstr ""
#: core\prioritize.py:169 #: core\prioritize.py:171
msgid "Oldest" msgid "Oldest"
msgstr "" msgstr ""

View File

@ -945,3 +945,19 @@ msgstr ""
#: qt\preferences_dialog.py:173 #: qt\preferences_dialog.py:173
msgid "Use dark style" msgid "Use dark style"
msgstr "" 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: <a href=\"{}\">{}</a>"
msgstr ""
#: qt\preferences_dialog.py:291
msgid "Debug"
msgstr ""

View File

@ -356,7 +356,7 @@ class DirectoriesDialog(QMainWindow):
msg = tr("You have unsaved results, do you really want to continue?") msg = tr("You have unsaved results, do you really want to continue?")
if not self.app.confirm(title, msg): if not self.app.confirm(title, msg):
return return
self.app.model.start_scanning() self.app.model.start_scanning(self.app.prefs.profile_scan)
def scanTypeChanged(self, index): def scanTypeChanged(self, index):
scan_options = self.app.model.SCANNER_CLASS.get_scan_options() scan_options = self.app.model.SCANNER_CLASS.get_scan_options()

View File

@ -68,8 +68,6 @@ class PreferencesDialog(PreferencesDialogBase):
tr("Ignore duplicates hardlinking to the same file"), tr("Ignore duplicates hardlinking to the same file"),
) )
self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches) self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches)
self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"))
self.widgetsVLayout.addWidget(self.debugModeBox)
self._setupBottomPart() self._setupBottomPart()
def _load(self, prefs, setchecked, section): def _load(self, prefs, setchecked, section):

View File

@ -34,8 +34,6 @@ class PreferencesDialog(PreferencesDialogBase):
tr("Ignore duplicates hardlinking to the same file"), tr("Ignore duplicates hardlinking to the same file"),
) )
self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches) 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) self.cacheTypeRadio = RadioBox(self, items=["Sqlite", "Shelve"], spread=False)
cache_form = QFormLayout() cache_form = QFormLayout()

View File

@ -24,6 +24,7 @@ class Preferences(PreferencesBase):
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.debug_mode = get("DebugMode", self.debug_mode) 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.destination_type = get("DestinationType", self.destination_type)
self.custom_command = get("CustomCommand", self.custom_command) self.custom_command = get("CustomCommand", self.custom_command)
self.language = get("Language", self.language) self.language = get("Language", self.language)
@ -93,6 +94,7 @@ class Preferences(PreferencesBase):
self.ignore_hardlink_matches = False self.ignore_hardlink_matches = False
self.remove_empty_folders = False self.remove_empty_folders = False
self.debug_mode = False self.debug_mode = False
self.profile_scan = False
self.destination_type = 1 self.destination_type = 1
self.custom_command = "" self.custom_command = ""
self.language = trans.installed_lang if trans.installed_lang else "" self.language = trans.installed_lang if trans.installed_lang else ""
@ -144,6 +146,7 @@ class Preferences(PreferencesBase):
set_("UseRegexp", self.use_regexp) set_("UseRegexp", self.use_regexp)
set_("RemoveEmptyFolders", self.remove_empty_folders) set_("RemoveEmptyFolders", self.remove_empty_folders)
set_("DebugMode", self.debug_mode) set_("DebugMode", self.debug_mode)
set_("ProfileScan", self.profile_scan)
set_("DestinationType", self.destination_type) set_("DestinationType", self.destination_type)
set_("CustomCommand", self.custom_command) set_("CustomCommand", self.custom_command)
set_("Language", self.language) set_("Language", self.language)

View File

@ -29,7 +29,7 @@ from PyQt5.QtWidgets import (
QFormLayout, QFormLayout,
) )
from PyQt5.QtGui import QPixmap, QIcon from PyQt5.QtGui import QPixmap, QIcon
from hscommon import plat from hscommon import desktop, plat
from hscommon.trans import trget from hscommon.trans import trget
from hscommon.plat import ISLINUX from hscommon.plat import ISLINUX
@ -69,7 +69,8 @@ class Sections(Flag):
GENERAL = auto() GENERAL = auto()
DISPLAY = auto() DISPLAY = auto()
ALL = GENERAL | DISPLAY DEBUG = auto()
ALL = GENERAL | DISPLAY | DEBUG
class PreferencesDialogBase(QDialog): class PreferencesDialogBase(QDialog):
@ -82,6 +83,7 @@ class PreferencesDialogBase(QDialog):
self._setupUi() self._setupUi()
self.filterHardnessSlider.valueChanged["int"].connect(self.filterHardnessLabel.setNum) 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.clicked.connect(self.buttonClicked)
self.buttonBox.accepted.connect(self.accept) self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject) 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) details_groupbox.setLayout(self.details_groupbox_layout)
self.displayVLayout.addWidget(details_groupbox) 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: <a href="{}">{}</a>').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): def _setupAddCheckbox(self, name, label, parent=None):
if parent is None: if parent is None:
parent = self parent = self
@ -253,13 +267,17 @@ 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_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.debugVLayout = QVBoxLayout()
self.page_debug.setLayout(self.debugVLayout)
self._setupPreferenceWidgets() self._setupPreferenceWidgets()
self._setupDisplayPage() self._setupDisplayPage()
self._setupDebugPage()
# self.mainVLayout.addLayout(self.widgetsVLayout) # self.mainVLayout.addLayout(self.widgetsVLayout)
self.buttonBox = QDialogButtonBox(self) self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setStandardButtons( self.buttonBox.setStandardButtons(
@ -270,8 +288,10 @@ 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_debug, tr("Debug"))
self.displayVLayout.addStretch(0) self.displayVLayout.addStretch(0)
self.widgetsVLayout.addStretch(0) self.widgetsVLayout.addStretch(0)
self.debugVLayout.addStretch(0)
def _load(self, prefs, setchecked, section): def _load(self, prefs, setchecked, section):
# Edition-specific # Edition-specific
@ -295,7 +315,6 @@ use the modifier key to drag the floating window around"
setchecked(self.useRegexpBox, prefs.use_regexp) setchecked(self.useRegexpBox, prefs.use_regexp)
setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders) setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches) setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches)
setchecked(self.debugModeBox, prefs.debug_mode)
self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type) self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
self.customCommandEdit.setText(prefs.custom_command) self.customCommandEdit.setText(prefs.custom_command)
if section & Sections.DISPLAY: if section & Sections.DISPLAY:
@ -322,6 +341,9 @@ use the modifier key to drag the floating window around"
except ValueError: except ValueError:
langindex = 0 langindex = 0
self.languageComboBox.setCurrentIndex(langindex) 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) self._load(prefs, setchecked, section)
def save(self): 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.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches) prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches)
prefs.debug_mode = ischecked(self.debugModeBox) prefs.debug_mode = ischecked(self.debugModeBox)
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)
prefs.details_dialog_titlebar_enabled = ischecked(self.details_dialog_titlebar_enabled) prefs.details_dialog_titlebar_enabled = ischecked(self.details_dialog_titlebar_enabled)
prefs.details_dialog_vertical_titlebar = ischecked(self.details_dialog_vertical_titlebar) 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 section_to_update = Sections.GENERAL
if current_tab is self.page_display: if current_tab is self.page_display:
section_to_update = Sections.DISPLAY section_to_update = Sections.DISPLAY
if current_tab is self.page_debug:
section_to_update = Sections.DEBUG
self.resetToDefaults(section_to_update) self.resetToDefaults(section_to_update)
def showEvent(self, event): def showEvent(self, event):

View File

@ -103,8 +103,6 @@ class PreferencesDialog(PreferencesDialogBase):
self.widget, self.widget,
) )
self.verticalLayout_4.addWidget(self.ignoreHardlinkMatches) 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.widgetsVLayout.addWidget(self.widget)
self._setupBottomPart() self._setupBottomPart()