mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-10 05:34:36 +00:00
Merge branch 'master' into develop
This commit is contained in:
commit
6d53511cee
71
core/app.py
71
core/app.py
@ -100,10 +100,14 @@ class DupeGuru(RegistrableApplication, 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:`Results`, :class:`Scanner`,
|
whose reference needs to be held: :class:`~core.results.Results`, :class:`Scanner`,
|
||||||
: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.
|
It also hosts high level methods and acts as a coordinator for all those elements. This is why
|
||||||
|
some of its methods seem a bit shallow, like for example :meth:`mark_all` and
|
||||||
|
:meth:`remove_duplicates`. These methos are just proxies for a method in :attr:`results`, but
|
||||||
|
they are also followed by a notification call which is very important if we want GUI elements
|
||||||
|
to be correctly notified of a change in the data they're presenting.
|
||||||
|
|
||||||
.. attribute:: directories
|
.. attribute:: directories
|
||||||
|
|
||||||
@ -437,11 +441,22 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self._start_job(JobType.Delete, self._do_delete, args=args)
|
self._start_job(JobType.Delete, self._do_delete, args=args)
|
||||||
|
|
||||||
def export_to_xhtml(self):
|
def export_to_xhtml(self):
|
||||||
|
"""Export current results to XHTML.
|
||||||
|
|
||||||
|
The configuration of the :attr:`result_table` (columns order and visibility) is used to
|
||||||
|
determine how the data is presented in the export. In other words, the exported table in
|
||||||
|
the resulting XHTML will look just like the results table.
|
||||||
|
"""
|
||||||
colnames, rows = self._get_export_data()
|
colnames, rows = self._get_export_data()
|
||||||
export_path = export.export_to_xhtml(colnames, rows)
|
export_path = export.export_to_xhtml(colnames, rows)
|
||||||
desktop.open_path(export_path)
|
desktop.open_path(export_path)
|
||||||
|
|
||||||
def export_to_csv(self):
|
def export_to_csv(self):
|
||||||
|
"""Export current results to CSV.
|
||||||
|
|
||||||
|
The columns and their order in the resulting CSV file is determined in the same way as in
|
||||||
|
:meth:`export_to_xhtml`.
|
||||||
|
"""
|
||||||
dest_file = self.view.select_dest_file(tr("Select a destination for your exported CSV"), 'csv')
|
dest_file = self.view.select_dest_file(tr("Select a destination for your exported CSV"), 'csv')
|
||||||
if dest_file:
|
if dest_file:
|
||||||
colnames, rows = self._get_export_data()
|
colnames, rows = self._get_export_data()
|
||||||
@ -489,6 +504,12 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
subprocess.Popen(cmd, shell=True)
|
subprocess.Popen(cmd, shell=True)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
"""Load directory selection and ignore list from files in appdata.
|
||||||
|
|
||||||
|
This method is called during startup so that directory selection and ignore list, which
|
||||||
|
is persistent data, is the same as when the last session was closed (when :meth:`save` was
|
||||||
|
called).
|
||||||
|
"""
|
||||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||||
self.notify('directories_changed')
|
self.notify('directories_changed')
|
||||||
p = op.join(self.appdata, 'ignore_list.xml')
|
p = op.join(self.appdata, 'ignore_list.xml')
|
||||||
@ -505,6 +526,12 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self._start_job(JobType.Load, do)
|
self._start_job(JobType.Load, do)
|
||||||
|
|
||||||
def make_selected_reference(self):
|
def make_selected_reference(self):
|
||||||
|
"""Promote :attr:`selected_dupes` to reference position within their respective groups.
|
||||||
|
|
||||||
|
Each selected dupe will become the :attr:`~core.engine.Group.ref` of its group. If there's
|
||||||
|
more than one dupe selected for the same group, only the first (in the order currently shown
|
||||||
|
in :attr:`result_table`) dupe will be promoted.
|
||||||
|
"""
|
||||||
dupes = self.without_ref(self.selected_dupes)
|
dupes = self.without_ref(self.selected_dupes)
|
||||||
changed_groups = set()
|
changed_groups = set()
|
||||||
for dupe in dupes:
|
for dupe in dupes:
|
||||||
@ -531,18 +558,30 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self.notify('results_changed_but_keep_selection')
|
self.notify('results_changed_but_keep_selection')
|
||||||
|
|
||||||
def mark_all(self):
|
def mark_all(self):
|
||||||
|
"""Set all dupes in the results as marked.
|
||||||
|
"""
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
self.notify('marking_changed')
|
self.notify('marking_changed')
|
||||||
|
|
||||||
def mark_none(self):
|
def mark_none(self):
|
||||||
|
"""Set all dupes in the results as unmarked.
|
||||||
|
"""
|
||||||
self.results.mark_none()
|
self.results.mark_none()
|
||||||
self.notify('marking_changed')
|
self.notify('marking_changed')
|
||||||
|
|
||||||
def mark_invert(self):
|
def mark_invert(self):
|
||||||
|
"""Invert the marked state of all dupes in the results.
|
||||||
|
"""
|
||||||
self.results.mark_invert()
|
self.results.mark_invert()
|
||||||
self.notify('marking_changed')
|
self.notify('marking_changed')
|
||||||
|
|
||||||
def mark_dupe(self, dupe, marked):
|
def mark_dupe(self, dupe, marked):
|
||||||
|
"""Change marked status of ``dupe``.
|
||||||
|
|
||||||
|
:param dupe: dupe to mark/unmark
|
||||||
|
:type dupe: :class:`~core.fs.File`
|
||||||
|
:param bool marked: True = mark, False = unmark
|
||||||
|
"""
|
||||||
if marked:
|
if marked:
|
||||||
self.results.mark(dupe)
|
self.results.mark(dupe)
|
||||||
else:
|
else:
|
||||||
@ -559,10 +598,17 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
desktop.open_path(dupe.path)
|
desktop.open_path(dupe.path)
|
||||||
|
|
||||||
def purge_ignore_list(self):
|
def purge_ignore_list(self):
|
||||||
|
"""Remove files that don't exist from :attr:`ignore_list`.
|
||||||
|
"""
|
||||||
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
|
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
|
||||||
self.ignore_list_dialog.refresh()
|
self.ignore_list_dialog.refresh()
|
||||||
|
|
||||||
def remove_directories(self, indexes):
|
def remove_directories(self, indexes):
|
||||||
|
"""Remove root directories at ``indexes`` from :attr:`directories`.
|
||||||
|
|
||||||
|
:param indexes: Indexes of the directories to remove.
|
||||||
|
:type indexes: list of int
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
indexes = sorted(indexes, reverse=True)
|
indexes = sorted(indexes, reverse=True)
|
||||||
for index in indexes:
|
for index in indexes:
|
||||||
@ -572,6 +618,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def remove_duplicates(self, duplicates):
|
def remove_duplicates(self, duplicates):
|
||||||
|
"""Remove ``duplicates`` from :attr:`results`.
|
||||||
|
|
||||||
|
Calls :meth:`~core.results.Results.remove_duplicates` and send appropriate notifications.
|
||||||
|
|
||||||
|
:param duplicates: duplicates to remove.
|
||||||
|
:type duplicates: list of :class:`~core.fs.File`
|
||||||
|
"""
|
||||||
self.results.remove_duplicates(self.without_ref(duplicates))
|
self.results.remove_duplicates(self.without_ref(duplicates))
|
||||||
self.notify('results_changed_but_keep_selection')
|
self.notify('results_changed_but_keep_selection')
|
||||||
|
|
||||||
@ -600,6 +653,12 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self.remove_duplicates(dupes)
|
self.remove_duplicates(dupes)
|
||||||
|
|
||||||
def rename_selected(self, newname):
|
def rename_selected(self, newname):
|
||||||
|
"""Renames the selected dupes's file to ``newname``.
|
||||||
|
|
||||||
|
If there's more than one selected dupes, the first one is used.
|
||||||
|
|
||||||
|
:param str newname: The filename to rename the dupe's file to.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
d = self.selected_dupes[0]
|
d = self.selected_dupes[0]
|
||||||
d.rename(newname)
|
d.rename(newname)
|
||||||
@ -609,6 +668,14 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def reprioritize_groups(self, sort_key):
|
def reprioritize_groups(self, sort_key):
|
||||||
|
"""Sort dupes in each group (in :attr:`results`) according to ``sort_key``.
|
||||||
|
|
||||||
|
Called by the re-prioritize dialog. Calls :meth:`~core.engine.Group.prioritize` and, once
|
||||||
|
the sorting is done, show a message that confirms the action.
|
||||||
|
|
||||||
|
:param sort_key: The key being sent to :meth:`~core.engine.Group.prioritize`
|
||||||
|
:type sort_key: f(dupe)
|
||||||
|
"""
|
||||||
count = 0
|
count = 0
|
||||||
for group in self.results.groups:
|
for group in self.results.groups:
|
||||||
if group.prioritize(key_func=sort_key):
|
if group.prioritize(key_func=sort_key):
|
||||||
|
@ -157,26 +157,31 @@ def reduce_common_words(word_dict, threshold):
|
|||||||
else:
|
else:
|
||||||
del word_dict[word]
|
del word_dict[word]
|
||||||
|
|
||||||
Match = namedtuple('Match', 'first second percentage')
|
# Writing docstrings in a namedtuple is tricky. From Python 3.3, it's possible to set __doc__, but
|
||||||
Match.__doc__ = """Represents a match between two :class:`~core.fs.File`.
|
# some research allowed me to find a more elegant solution, which is what is done here. See
|
||||||
|
# http://stackoverflow.com/questions/1606436/adding-docstrings-to-namedtuples-in-python
|
||||||
|
|
||||||
Regarless of the matching method, when two files are determined to match, a Match pair is created,
|
class Match(namedtuple('Match', 'first second percentage')):
|
||||||
which holds, of course, the two matched files, but also their match "level".
|
"""Represents a match between two :class:`~core.fs.File`.
|
||||||
|
|
||||||
.. attribute:: first
|
Regarless of the matching method, when two files are determined to match, a Match pair is created,
|
||||||
|
which holds, of course, the two matched files, but also their match "level".
|
||||||
|
|
||||||
first file of the pair.
|
.. attribute:: first
|
||||||
|
|
||||||
.. attribute:: second
|
first file of the pair.
|
||||||
|
|
||||||
second file of the pair.
|
.. attribute:: second
|
||||||
|
|
||||||
.. attribute:: percentage
|
second file of the pair.
|
||||||
|
|
||||||
their match level according to the scan method which found the match. int from 1 to 100. For
|
.. attribute:: percentage
|
||||||
exact scan methods, such as Contents scans, this will always be 100.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
their match level according to the scan method which found the match. int from 1 to 100. For
|
||||||
|
exact scan methods, such as Contents scans, this will always be 100.
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def get_match(first, second, flags=()):
|
def get_match(first, second, flags=()):
|
||||||
#it is assumed here that first and second both have a "words" attribute
|
#it is assumed here that first and second both have a "words" attribute
|
||||||
percentage = compare(first.words, second.words, flags)
|
percentage = compare(first.words, second.words, flags)
|
||||||
|
@ -136,7 +136,7 @@ def package_debian_distribution(edition, distribution):
|
|||||||
|
|
||||||
def package_debian(edition):
|
def package_debian(edition):
|
||||||
print("Packaging for Ubuntu")
|
print("Packaging for Ubuntu")
|
||||||
for distribution in ['precise', 'quantal', 'raring']:
|
for distribution in ['precise', 'quantal', 'raring', 'saucy']:
|
||||||
package_debian_distribution(edition, distribution)
|
package_debian_distribution(edition, distribution)
|
||||||
|
|
||||||
def package_arch(edition):
|
def package_arch(edition):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user