1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-24 23:51:38 +00:00

Compare commits

..

4 Commits

7 changed files with 54 additions and 26 deletions

View File

@@ -43,3 +43,4 @@ ca93352ce35184853ad9fcb881935a43a8b1e249 me5.10.3
8d12cab3b12b723e3a86d02cf8002731a0f73f95 se3.0.0
778876a8a9787658aa6adf6944b53aebcb7faeea se3.0.1
f1d40b556c01f32c58f9ef9f9acac5b78e01ba7a pe2.0.0
2fd901a516f8cb6b4438491f63f2ebfd52a57c13 me6.0.0

View File

@@ -98,12 +98,17 @@ class DupeGuru(RegistrableApplication, Broadcaster):
except EnvironmentError:
return None
def _results_changed(self):
self.selected_dupes = [d for d in self.selected_dupes
if self.results.get_group_of_duplicate(d) is not None]
self.notify('results_changed')
def _job_completed(self, jobid):
# Must be called by subclasses when they detect that an async job is completed.
if jobid == JOB_SCAN:
self.notify('results_changed')
self._results_changed()
elif jobid in (JOB_LOAD, JOB_MOVE, JOB_DELETE):
self.notify('results_changed')
self._results_changed()
self.notify('problems_changed')
@staticmethod
@@ -171,7 +176,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
filter = escape(filter, set('()[]\\.|+?^'))
filter = escape(filter, '*', '.')
self.results.apply_filter(filter)
self.notify('results_changed')
self._results_changed()
def clean_empty_dirs(self, path):
if self.options['clean_empty_dirs']:
@@ -316,7 +321,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
def remove_marked(self):
self.results.perform_on_marked(lambda x:None, True)
self.notify('results_changed')
self._results_changed()
def remove_selected(self):
self.remove_duplicates(self.selected_dupes)
@@ -356,7 +361,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
if not self.directories.has_any_file():
raise NoScannableFileError()
self.results.groups = []
self.notify('results_changed')
self._results_changed()
self._start_job(JOB_SCAN, do)
def toggle_selected_mark_state(self):

View File

@@ -259,11 +259,14 @@ class Results(Markable):
group = self.get_group_of_duplicate(dupe)
if dupe not in group.dupes:
return
ref = group.ref
group.remove_dupe(dupe, False)
del self.__group_of_duplicate[dupe]
self._remove_mark_flag(dupe)
self.__total_count -= 1
self.__total_size -= dupe.size
if not group:
del self.__group_of_duplicate[ref]
self.__groups.remove(group)
if self.__filtered_groups:
self.__filtered_groups.remove(group)

View File

@@ -391,6 +391,16 @@ class TestCaseDupeGuruWithResults:
app.start_scanning() # will be cancelled immediately
eq_(len(self.rtable), 0)
def test_selected_dupes_after_removal(self, do_setup):
# Purge the app's `selected_dupes` attribute when removing dupes, or else it might cause a
# crash later with None refs.
app = self.app
app.results.mark_all()
self.rtable.select([0, 1, 2, 3, 4])
app.remove_marked()
eq_(len(self.rtable), 0)
eq_(app.selected_dupes, [])
class TestCaseDupeGuru_renameSelected:
def pytest_funcarg__do_setup(self, request):
tmpdir = request.getfuncargvalue('tmpdir')

View File

@@ -231,6 +231,15 @@ class TestCaseResultsWithSomeGroups:
self.results.perform_on_marked(lambda x:None, True)
assert not self.results.is_modified
def test_group_of_duplicate_after_removal(self):
# removing a duplicate also removes it from the dupe:group map.
dupe = self.results.groups[1].dupes[0]
ref = self.results.groups[1].ref
self.results.remove_duplicates([dupe])
assert self.results.get_group_of_duplicate(dupe) is None
# also remove group ref
assert self.results.get_group_of_duplicate(ref) is None
class TestCaseResultsWithSavedResults:
def setup_method(self, method):

View File

@@ -11,12 +11,11 @@ import plistlib
import logging
import re
from appscript import app, k, CommandError, ApplicationNotFoundError
from appscript import app, its, CommandError, ApplicationNotFoundError
from hscommon import io
from hscommon.util import get_file_ext, remove_invalid_xml
from hscommon.path import Path
from hscommon.cocoa import as_fetch
from hscommon.cocoa.objcmin import NSUserDefaults, NSURL
from hscommon.trans import tr
@@ -151,37 +150,28 @@ class DupeGuruPE(app_cocoa.DupeGuru):
return self._do_delete_dupe(dupe, replace_with_hardlinks)
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
self.path2iphoto = {}
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
if any(isinstance(dupe, IPhoto) for dupe in marked):
j = j.start_subjob([6, 4], tr("Probing iPhoto. Don't touch it during the operation!"))
j.add_progress(0, desc=tr("Talking to iPhoto. Don't touch it!"))
try:
a = app('iPhoto')
a.activate(timeout=0)
a.select(a.photo_library_album(timeout=0), timeout=0)
photos = as_fetch(a.photo_library_album().photos, k.item)
for photo in j.iter_with_progress(photos):
try:
self.path2iphoto[str(photo.image_path(timeout=0))] = photo
except CommandError:
pass
except (CommandError, RuntimeError, ApplicationNotFoundError):
pass
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
self.results.perform_on_marked(op, True)
del self.path2iphoto
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
if isinstance(dupe, IPhoto):
if str(dupe.path) in self.path2iphoto:
photo = self.path2iphoto[str(dupe.path)]
try:
a = app('iPhoto')
[photo] = a.photo_library_album().photos[its.image_path == str(dupe.path)]()
a.remove(photo, timeout=0)
except ValueError:
msg = "Could not find photo '{}' in iPhoto Library".format(str(dupe.path))
raise EnvironmentError(msg)
except (CommandError, RuntimeError) as e:
raise EnvironmentError(str(e))
else:
msg = "Could not find photo %s in iPhoto Library" % str(dupe.path)
raise EnvironmentError(msg)
else:
app_cocoa.DupeGuru._do_delete_dupe(self, dupe, replace_with_hardlinks)

View File

@@ -10,6 +10,7 @@ import sys
import logging
import os
import os.path as op
import io
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, QProcess, SIGNAL, pyqtSignal
from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox, QApplication
@@ -38,6 +39,11 @@ JOBID2TITLE = {
JOB_DELETE: tr("Sending files to the recycle bin"),
}
class SysWrapper(io.IOBase):
def write(self, s):
if s.strip(): # don't log empty stuff
logging.warning(s)
class DupeGuru(DupeGuruBase, QObject):
LOGO_NAME = '<replace this>'
NAME = '<replace this>'
@@ -49,6 +55,10 @@ class DupeGuru(DupeGuruBase, QObject):
# For basicConfig() to work, we have to be sure that no logging has taken place before this call.
logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING,
format='%(asctime)s - %(levelname)s - %(message)s')
if sys.stderr is None: # happens under a cx_freeze environment
sys.stderr = SysWrapper()
if sys.stdout is None:
sys.stdout = SysWrapper()
self.prefs = self._create_preferences()
self.prefs.load()
DupeGuruBase.__init__(self, data_module, appdata)