1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2025-03-10 05:34:36 +00:00

Instantiate Scanner on-the-fly

Previously, it would be instantiated on startup.

This will make our job easier for an upcoming SE/ME/PE merge.
This commit is contained in:
Virgil Dupras 2016-05-29 16:52:07 -04:00
parent dc76f9744e
commit 5c57a2a8fc
11 changed files with 69 additions and 60 deletions

View File

@ -23,8 +23,9 @@ from hscommon.trans import tr
from hscommon.plat import ISWINDOWS from hscommon.plat import ISWINDOWS
from hscommon import desktop from hscommon import desktop
from . import directories, results, scanner, export, fs from . import directories, results, export, fs
from .ignore import IgnoreList from .ignore import IgnoreList
from .scanner import ScanType, Scanner
from .gui.deletion_options import DeletionOptions from .gui.deletion_options import DeletionOptions
from .gui.details_panel import DetailsPanel from .gui.details_panel import DetailsPanel
from .gui.directory_tree import DirectoryTree from .gui.directory_tree import DirectoryTree
@ -113,7 +114,7 @@ class DupeGuru(Broadcaster):
"""Holds everything together. """Holds everything together.
Instantiated once per running application, it holds a reference to every high-level object Instantiated once per running application, it holds a reference to every high-level object
whose reference needs to be held: :class:`~core.results.Results`, :class:`Scanner`, whose reference needs to be held: :class:`~core.results.Results`,
:class:`~core.directories.Directories`, :mod:`core.gui` instances, etc.. :class:`~core.directories.Directories`, :mod:`core.gui` instances, etc..
It also hosts high level methods and acts as a coordinator for all those elements. This is why It also hosts high level methods and acts as a coordinator for all those elements. This is why
@ -154,7 +155,7 @@ class DupeGuru(Broadcaster):
# select_dest_file(prompt: str, ext: str) --> str # select_dest_file(prompt: str, ext: str) --> str
PROMPT_NAME = "dupeGuru" PROMPT_NAME = "dupeGuru"
SCANNER_CLASS = scanner.Scanner SCANNER_CLASS = Scanner
def __init__(self, view): def __init__(self, view):
if view.get_default(DEBUG_MODE_PREFERENCE): if view.get_default(DEBUG_MODE_PREFERENCE):
@ -165,10 +166,13 @@ class DupeGuru(Broadcaster):
self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME) self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME)
if not op.exists(self.appdata): if not op.exists(self.appdata):
os.makedirs(self.appdata) os.makedirs(self.appdata)
self.discarded_file_count = 0
self.directories = directories.Directories() self.directories = directories.Directories()
self.results = results.Results(self) self.results = results.Results(self)
self.ignore_list = IgnoreList() self.ignore_list = IgnoreList()
self.scanner = self.SCANNER_CLASS() # In addition to "app-level" options, this dictionary also holds options that will be
# sent to the scanner. They don't have default values because those defaults values are
# defined in the scanner class.
self.options = { self.options = {
'escape_filter_regexp': True, 'escape_filter_regexp': True,
'clean_empty_dirs': False, 'clean_empty_dirs': False,
@ -360,7 +364,7 @@ class DupeGuru(Broadcaster):
self.view.show_message(tr("'{}' does not exist.").format(d)) self.view.show_message(tr("'{}' does not exist.").format(d))
def add_selected_to_ignore_list(self): def add_selected_to_ignore_list(self):
"""Adds :attr:`selected_dupes` to :attr:`scanner`'s ignore list. """Adds :attr:`selected_dupes` to :attr:`ignore_list`.
""" """
dupes = self.without_ref(self.selected_dupes) dupes = self.without_ref(self.selected_dupes)
if not dupes: if not dupes:
@ -731,22 +735,29 @@ 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()
if not self.directories.has_any_file():
self.view.show_message(tr("The selected directories contain no scannable file."))
return
# Send relevant options down to the scanner instance
for k, v in self.options.items():
if hasattr(scanner, k):
setattr(scanner, k, v)
self.results.groups = []
self._results_changed()
def do(j): def do(j):
j.set_progress(0, tr("Collecting files to scan")) j.set_progress(0, tr("Collecting files to scan"))
if self.scanner.scan_type == scanner.ScanType.Folders: if scanner.scan_type == ScanType.Folders:
files = list(self.directories.get_folders(j)) files = list(self.directories.get_folders(j))
else: else:
files = list(self.directories.get_files(j)) files = list(self.directories.get_files(j))
if self.options['ignore_hardlink_matches']: if self.options['ignore_hardlink_matches']:
files = self._remove_hardlink_dupes(files) files = self._remove_hardlink_dupes(files)
logging.info('Scanning %d files' % len(files)) logging.info('Scanning %d files' % len(files))
self.results.groups = self.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
if not self.directories.has_any_file():
self.view.show_message(tr("The selected directories contain no scannable file."))
return
self.results.groups = []
self._results_changed()
self._start_job(JobType.Scan, do) self._start_job(JobType.Scan, do)
def toggle_selected_mark_state(self): def toggle_selected_mark_state(self):
@ -783,7 +794,7 @@ class DupeGuru(Broadcaster):
@property @property
def stat_line(self): def stat_line(self):
result = self.results.stat_line result = self.results.stat_line
if self.scanner.discarded_file_count: if self.discarded_file_count:
result = tr("%s (%d discarded)") % (result, self.scanner.discarded_file_count) result = tr("%s (%d discarded)") % (result, self.discarded_file_count)
return result return result

View File

@ -124,7 +124,8 @@ class Scanner:
return True return True
return len(dupe.path) > len(ref.path) return len(dupe.path) > len(ref.path)
def get_scan_options(self): @staticmethod
def get_scan_options():
"""Returns a list of scanning options for this scanner. """Returns a list of scanning options for this scanner.
Returns a list of ``ScanOption``. Returns a list of ``ScanOption``.

View File

@ -105,7 +105,7 @@ class TestCaseDupeGuru:
os.link(str(tmppath['myfile']), str(tmppath['hardlink'])) os.link(str(tmppath['myfile']), str(tmppath['hardlink']))
app = TestApp().app app = TestApp().app
app.directories.add_path(tmppath) app.directories.add_path(tmppath)
app.scanner.scan_type = ScanType.Contents app.options['scan_type'] = ScanType.Contents
app.options['ignore_hardlink_matches'] = True app.options['ignore_hardlink_matches'] = True
app.start_scanning() app.start_scanning()
eq_(len(app.results.groups), 0) eq_(len(app.results.groups), 0)

View File

@ -13,7 +13,8 @@ class ScannerME(ScannerBase):
def _key_func(dupe): def _key_func(dupe):
return (-dupe.bitrate, -dupe.size) return (-dupe.bitrate, -dupe.size)
def get_scan_options(self): @staticmethod
def get_scan_options():
return [ return [
ScanOption(ScanType.Filename, tr("Filename")), ScanOption(ScanType.Filename, tr("Filename")),
ScanOption(ScanType.Fields, tr("Filename - Fields")), ScanOption(ScanType.Fields, tr("Filename - Fields")),

View File

@ -16,7 +16,8 @@ class ScannerPE(Scanner):
match_scaled = False match_scaled = False
threshold = 75 threshold = 75
def get_scan_options(self): @staticmethod
def get_scan_options():
return [ return [
ScanOption(ScanType.FuzzyBlock, tr("Contents")), ScanOption(ScanType.FuzzyBlock, tr("Contents")),
ScanOption(ScanType.ExifTimestamp, tr("EXIF Timestamp")), ScanOption(ScanType.ExifTimestamp, tr("EXIF Timestamp")),

View File

@ -9,7 +9,8 @@ from hscommon.trans import tr
from core.scanner import Scanner as ScannerBase, ScanOption, ScanType from core.scanner import Scanner as ScannerBase, ScanOption, ScanType
class ScannerSE(ScannerBase): class ScannerSE(ScannerBase):
def get_scan_options(self): @staticmethod
def get_scan_options():
return [ return [
ScanOption(ScanType.Filename, tr("Filename")), ScanOption(ScanType.Filename, tr("Filename")),
ScanOption(ScanType.Contents, tr("Contents")), ScanOption(ScanType.Contents, tr("Contents")),

View File

@ -89,7 +89,7 @@ class DupeGuru(QObject):
createActions(ACTIONS, self) createActions(ACTIONS, self)
def _update_options(self): def _update_options(self):
self.model.scanner.mix_file_kind = self.prefs.mix_file_kind self.model.options['mix_file_kind'] = self.prefs.mix_file_kind
self.model.options['escape_filter_regexp'] = not self.prefs.use_regexp self.model.options['escape_filter_regexp'] = not self.prefs.use_regexp
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches

View File

@ -28,7 +28,7 @@ class DirectoriesDialog(QMainWindow):
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
self.recentFolders = Recent(self.app, 'recentFolders') self.recentFolders = Recent(self.app, 'recentFolders')
self._setupUi() self._setupUi()
SCAN_TYPE_ORDER = [so.scan_type for so in self.app.model.scanner.get_scan_options()] SCAN_TYPE_ORDER = [so.scan_type for so in self.app.model.SCANNER_CLASS.get_scan_options()]
scan_type_index = SCAN_TYPE_ORDER.index(self.app.prefs.scan_type) scan_type_index = SCAN_TYPE_ORDER.index(self.app.prefs.scan_type)
self.scanTypeComboBox.setCurrentIndex(scan_type_index) self.scanTypeComboBox.setCurrentIndex(scan_type_index)
self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
@ -130,7 +130,7 @@ class DirectoriesDialog(QMainWindow):
self.scanTypeComboBox = QComboBox(self) self.scanTypeComboBox = QComboBox(self)
self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
self.scanTypeComboBox.setMaximumWidth(400) self.scanTypeComboBox.setMaximumWidth(400)
for scan_option in self.app.model.scanner.get_scan_options(): for scan_option in self.app.model.SCANNER_CLASS.get_scan_options():
self.scanTypeComboBox.addItem(scan_option.label) self.scanTypeComboBox.addItem(scan_option.label)
hl.addWidget(self.scanTypeComboBox) hl.addWidget(self.scanTypeComboBox)
self.showPreferencesButton = QPushButton(tr("Options"), self.centralwidget) self.showPreferencesButton = QPushButton(tr("Options"), self.centralwidget)
@ -240,7 +240,7 @@ class DirectoriesDialog(QMainWindow):
self.app.model.start_scanning() self.app.model.start_scanning()
def scanTypeChanged(self, index): def scanTypeChanged(self, index):
scan_options = self.app.model.scanner.get_scan_options() scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
self.app.prefs.scan_type = scan_options[index].scan_type self.app.prefs.scan_type = scan_options[index].scan_type
self.app._update_options() self.app._update_options()

View File

@ -1,6 +1,4 @@
# Created By: Virgil Dupras # Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
# Created On: 2009-05-21
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
# #
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# 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
@ -28,10 +26,10 @@ class DupeGuru(DupeGuruBase):
def _update_options(self): def _update_options(self):
DupeGuruBase._update_options(self) DupeGuruBase._update_options(self)
self.model.scanner.min_match_percentage = self.prefs.filter_hardness self.model.options['min_match_percentage'] = self.prefs.filter_hardness
self.model.scanner.scan_type = self.prefs.scan_type self.model.options['scan_type'] = self.prefs.scan_type
self.model.scanner.word_weighting = self.prefs.word_weighting self.model.options['word_weighting'] = self.prefs.word_weighting
self.model.scanner.match_similar_words = self.prefs.match_similar self.model.options['match_similar_words'] = self.prefs.match_similar
scanned_tags = set() scanned_tags = set()
if self.prefs.scan_tag_track: if self.prefs.scan_tag_track:
scanned_tags.add('track') scanned_tags.add('track')
@ -45,5 +43,5 @@ class DupeGuru(DupeGuruBase):
scanned_tags.add('genre') scanned_tags.add('genre')
if self.prefs.scan_tag_year: if self.prefs.scan_tag_year:
scanned_tags.add('year') scanned_tags.add('year')
self.model.scanner.scanned_tags = scanned_tags self.model.options['scanned_tags'] = scanned_tags

View File

@ -1,6 +1,4 @@
# Created By: Virgil Dupras # Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
# Created On: 2009-04-25
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
# #
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# 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
@ -86,7 +84,7 @@ class DupeGuru(DupeGuruBase):
def _update_options(self): def _update_options(self):
DupeGuruBase._update_options(self) DupeGuruBase._update_options(self)
self.model.scanner.scan_type = self.prefs.scan_type self.model.options['scan_type'] = self.prefs.scan_type
self.model.scanner.match_scaled = self.prefs.match_scaled self.model.options['match_scaled'] = self.prefs.match_scaled
self.model.scanner.threshold = self.prefs.filter_hardness self.model.options['threshold'] = self.prefs.filter_hardness

View File

@ -1,6 +1,4 @@
# Created By: Virgil Dupras # Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
# Created On: 2009-05-24
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
# #
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# 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
@ -43,10 +41,10 @@ class DupeGuru(DupeGuruBase):
def _update_options(self): def _update_options(self):
DupeGuruBase._update_options(self) DupeGuruBase._update_options(self)
self.model.scanner.min_match_percentage = self.prefs.filter_hardness self.model.options['min_match_percentage'] = self.prefs.filter_hardness
self.model.scanner.scan_type = self.prefs.scan_type self.model.options['scan_type'] = self.prefs.scan_type
self.model.scanner.word_weighting = self.prefs.word_weighting self.model.options['word_weighting'] = self.prefs.word_weighting
self.model.scanner.match_similar_words = self.prefs.match_similar self.model.options['match_similar_words'] = self.prefs.match_similar
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0 threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
self.model.scanner.size_threshold = threshold * 1024 # threshold is in KB. the scanner wants bytes self.model.options['size_threshold'] = threshold * 1024 # threshold is in KB. the scanner wants bytes