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

Compare commits

..

13 Commits

Author SHA1 Message Date
Virgil Dupras
4249c528e9 [#144 state:fixed] Fixed crash on error handling under windows when sys.stderr is None. 2011-02-18 10:10:11 +00:00
Virgil Dupras
084068852e [#140 state:fixed] Fixed a crash on showing details panel in PE right after a mark-based removal (selection wasn't refreshed). 2011-02-18 10:37:40 +01:00
Virgil Dupras
c524a85897 Greatly improved iPhoto deletion process. 2011-02-17 15:08:23 +01:00
Virgil Dupras
d39d46be5a Added tag me6.0.0 for changeset 2fd901a516f8 2011-02-01 12:23:44 +01:00
Virgil Dupras
b8980b4667 Fixed a glitch with initial window position (they'd sometimes end up in awkward places on the screen). 2011-02-01 11:43:16 +01:00
Virgil Dupras
e0adec7b2b me v6.0.0 2011-02-01 10:03:56 +01:00
Virgil Dupras
eb8b9d663f Fixed a mistake in french translation. 2011-02-01 10:01:57 +01:00
Virgil Dupras
fa4b0cf9ec Added tag pe2.0.0 for changeset f1d40b556c01 2011-01-29 14:56:15 +01:00
Virgil Dupras
f72db8dd1d pe v2.0.0 2011-01-29 11:39:51 +01:00
Virgil Dupras
c5bf0f228a Changed error logging in core_pe.cache because it would sometimes result in huge logs of no value. Also, added debug logging during the analysis of pictures. 2011-01-29 11:31:17 +01:00
Virgil Dupras
e150b26cab Forgot a litteral --> constant conversion in the last commit. 2011-01-29 11:20:19 +01:00
Virgil Dupras
da41d07dae [#115 state:fixed] Re-factored the data columns (and delta columns) and made the Dimensions column a delta one. 2011-01-29 11:07:33 +01:00
Virgil Dupras
c885cb35d8 Added tag se3.0.1 for changeset 778876a8a978 2011-01-27 12:11:11 +01:00
34 changed files with 180 additions and 132 deletions

View File

@@ -41,3 +41,6 @@ ca93352ce35184853ad9fcb881935a43a8b1e249 me5.10.3
3f71a8f5bf8f6d0729748a27af9163e013723294 pe1.11.3
0056293b0dade8b8230f68c1fe6f0c2d1e0b74d8 se2.12.3
8d12cab3b12b723e3a86d02cf8002731a0f73f95 se3.0.0
778876a8a9787658aa6adf6944b53aebcb7faeea se3.0.1
f1d40b556c01f32c58f9ef9f9acac5b78e01ba7a pe2.0.0
2fd901a516f8cb6b4438491f63f2ebfd52a57c13 me6.0.0

View File

@@ -45,6 +45,7 @@ http://www.hardcoded.net/licenses/bsd_license
- (NSNumber *)getMarkCount;
- (BOOL)scanWasProblematic;
- (BOOL)resultsAreModified;
- (NSArray *)deltaColumns;
//Scanning options
- (void)setMinMatchPercentage:(NSNumber *)percentage;

View File

@@ -29,6 +29,7 @@ http://www.hardcoded.net/licenses/bsd_license
[self fillColumnsMenu];
[matches setTarget:self];
[matches setDoubleAction:@selector(openClicked:)];
[table setDeltaColumns:[Utils array2IndexSet:[py deltaColumns]]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];

View File

@@ -14,7 +14,7 @@
"56.title" = "Référence";
/* Class = "NSMenuItem"; title = "Excluded"; ObjectID = "57"; */
"57.title" = "Exclus";
"57.title" = "Exclu";
/* Class = "NSTextFieldCell"; title = "Select folders to scan and press \"Scan\"."; ObjectID = "71"; */
"71.title" = "Sélectionnez les dossiers à scanner et cliquez sur Scan.";

View File

@@ -191,7 +191,7 @@
</object>
<object class="NSMenuItem" id="142495353">
<reference key="NSMenu" ref="104112446"/>
<string key="NSTitle">Exclus</string>
<string key="NSTitle">Exclu</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<string key="NSAction">_popUpItemAction:</string>

View File

@@ -14,15 +14,6 @@ http://www.hardcoded.net/licenses/bsd_license
@implementation ResultWindow
/* Override */
- (id)initWithParentApp:(AppDelegateBase *)aApp;
{
self = [super initWithParentApp:aApp];
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,6)];
[deltaColumns removeIndex:6];
[table setDeltaColumns:deltaColumns];
return self;
}
- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

View File

@@ -14,15 +14,6 @@ http://www.hardcoded.net/licenses/bsd_license
@implementation ResultWindow
/* Override */
- (id)initWithParentApp:(AppDelegateBase *)aApp;
{
self = [super initWithParentApp:aApp];
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
[deltaColumns addIndex:5];
[table setDeltaColumns:deltaColumns];
return self;
}
- (void)initResultColumns
{
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];

View File

@@ -13,15 +13,6 @@ http://www.hardcoded.net/licenses/bsd_license
@implementation ResultWindow
/* Override */
- (id)initWithParentApp:(AppDelegateBase *)aApp;
{
self = [super initWithParentApp:aApp];
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
[deltaColumns addIndex:4];
[table setDeltaColumns:deltaColumns];
return self;
}
- (void)initResultColumns
{
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"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']:
@@ -221,7 +226,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
column_ids = [colid for colid in column_ids if colid.isdigit()]
column_ids = list(map(int, column_ids))
column_ids.sort()
colnames = [col['display'] for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
colnames = [col.display for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
rows = []
for group in self.results.groups:
for dupe in group:
@@ -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

@@ -112,6 +112,9 @@ class PyDupeGuruBase(PyFairware):
def resultsAreModified(self):
return self.py.results.is_modified
def deltaColumns(self):
return list(self.py.data.DELTA_COLUMNS)
#---Properties
@signature('v@:c')
def setMixFileKind_(self, mix_file_kind):

View File

@@ -6,10 +6,14 @@
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from collections import namedtuple
from hscommon.util import format_time_decimal, format_size
import time
Column = namedtuple('Column', 'attr display')
def format_path(p):
return str(p[:-1])

View File

@@ -31,7 +31,7 @@ class DetailsPanel(GUIObject):
# we don't want the two sides of the table to display the stats for the same file
ref = group.ref if group is not None and group.ref is not dupe else None
l2 = self.app._get_display_info(ref, group, False)
names = [c['display'] for c in self.app.data.COLUMNS]
names = [c.display for c in self.app.data.COLUMNS]
self._table = list(zip(names, l1, l2))
#--- Public

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

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2009-10-23
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
@@ -10,16 +9,17 @@
# data module for tests
from hscommon.util import format_size
from ..data import format_path, cmp_value
from ..data import format_path, cmp_value, Column
COLUMNS = [
{'attr':'name','display':'Filename'},
{'attr':'path','display':'Directory'},
{'attr':'size','display':'Size (KB)'},
{'attr':'extension','display':'Kind'},
Column('name', 'Filename'),
Column('path', 'Directory'),
Column('size', 'Size (KB)'),
Column('extension', 'Kind'),
]
METADATA_TO_READ = ['size']
DELTA_COLUMNS = {2,}
def GetDisplayInfo(dupe, group, delta):
size = dupe.size
@@ -35,10 +35,10 @@ def GetDisplayInfo(dupe, group, delta):
]
def GetDupeSortKey(dupe, get_group, key, delta):
r = cmp_value(getattr(dupe, COLUMNS[key]['attr']))
if delta and (key == 2):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr']))
r = cmp_value(getattr(dupe, COLUMNS[key].attr))
if delta and (key in DELTA_COLUMNS):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key].attr))
return r
def GetGroupSortKey(group, key):
return cmp_value(getattr(group.ref, COLUMNS[key]['attr']))
return cmp_value(getattr(group.ref, COLUMNS[key].attr))

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

@@ -1,2 +1,2 @@
__version__ = '5.10.4'
__version__ = '6.0.0'
__appname__ = 'dupeGuru Music Edition'

View File

@@ -9,33 +9,34 @@
from hscommon.util import format_time, format_size
from hscommon.trans import tr as trbase
from core.data import (format_path, format_timestamp, format_words, format_perc,
format_dupe_count, cmp_value)
format_dupe_count, cmp_value, Column)
tr = lambda s: trbase(s, 'columns')
COLUMNS = [
{'attr': 'name', 'display': tr("Filename")},
{'attr': 'path', 'display': tr("Folder")},
{'attr': 'size', 'display': tr("Size (MB)")},
{'attr': 'duration', 'display': tr("Time")},
{'attr': 'bitrate', 'display': tr("Bitrate")},
{'attr': 'samplerate', 'display': tr("Sample Rate")},
{'attr': 'extension', 'display': tr("Kind")},
{'attr': 'mtime', 'display': tr("Modification")},
{'attr': 'title', 'display': tr("Title")},
{'attr': 'artist', 'display': tr("Artist")},
{'attr': 'album', 'display': tr("Album")},
{'attr': 'genre', 'display': tr("Genre")},
{'attr': 'year', 'display': tr("Year")},
{'attr': 'track', 'display': tr("Track Number")},
{'attr': 'comment', 'display': tr("Comment")},
{'attr': 'percentage', 'display': tr("Match %")},
{'attr': 'words', 'display': tr("Words Used")},
{'attr': 'dupe_count', 'display': tr("Dupe Count")},
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('size', tr("Size (MB)")),
Column('duration', tr("Time")),
Column('bitrate', tr("Bitrate")),
Column('samplerate', tr("Sample Rate")),
Column('extension', tr("Kind")),
Column('mtime', tr("Modification")),
Column('title', tr("Title")),
Column('artist', tr("Artist")),
Column('album', tr("Album")),
Column('genre', tr("Genre")),
Column('year', tr("Year")),
Column('track', tr("Track Number")),
Column('comment', tr("Comment")),
Column('percentage', tr("Match %")),
Column('words', tr("Words Used")),
Column('dupe_count', tr("Dupe Count")),
]
MATCHPERC_COL = 15
DUPECOUNT_COL = 17
DELTA_COLUMNS = {2, 3, 4, 5, 7}
METADATA_TO_READ = ['size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
'album', 'genre', 'year', 'track', 'comment']
@@ -87,9 +88,9 @@ def GetDupeSortKey(dupe, get_group, key, delta):
return m.percentage
if key == DUPECOUNT_COL:
return 0
r = cmp_value(getattr(dupe, COLUMNS[key]['attr'], ''))
if delta and (key in {2, 3, 4, 7}):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr'], ''))
r = cmp_value(getattr(dupe, COLUMNS[key].attr, ''))
if delta and (key in DELTA_COLUMNS):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key].attr, ''))
return r
def GetGroupSortKey(group, key):
@@ -97,4 +98,4 @@ def GetGroupSortKey(group, key):
return group.percentage
if key == DUPECOUNT_COL:
return len(group)
return cmp_value(getattr(group.ref, COLUMNS[key]['attr'], ''))
return cmp_value(getattr(group.ref, COLUMNS[key].attr, ''))

View File

@@ -1,2 +1,2 @@
__version__ = '1.11.3'
__version__ = '2.0.0'
__appname__ = 'dupeGuru Picture Edition'

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')
a.remove(photo, timeout=0)
except (CommandError, RuntimeError) as e:
raise EnvironmentError(str(e))
else:
msg = "Could not find photo %s in iPhoto Library" % 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:
app_cocoa.DupeGuru._do_delete_dupe(self, dupe, replace_with_hardlinks)

View File

@@ -81,9 +81,9 @@ class Cache(object):
try:
self.con.execute(sql, [value, key])
except sqlite.OperationalError:
logging.warning('Picture cache could not set %r for key %r', value, key)
logging.warning('Picture cache could not set value for key %r', key)
except sqlite.DatabaseError as e:
logging.warning('DatabaseError while setting %r for key %r: %s', value, key, str(e))
logging.warning('DatabaseError while setting value for key %r: %s', key, str(e))
def _create_con(self, second_try=False):
def create_tables():

View File

@@ -8,7 +8,7 @@
from hscommon.util import format_size
from hscommon.trans import tr as trbase
from core.data import format_path, format_timestamp, format_perc, format_dupe_count, cmp_value
from core.data import format_path, format_timestamp, format_perc, format_dupe_count, cmp_value, Column
tr = lambda s: trbase(s, 'columns')
@@ -16,26 +16,31 @@ def format_dimensions(dimensions):
return '%d x %d' % (dimensions[0], dimensions[1])
COLUMNS = [
{'attr':'name', 'display': tr("Filename")},
{'attr':'path', 'display': tr("Folder")},
{'attr':'size', 'display': tr("Size (KB)")},
{'attr':'extension', 'display': tr("Kind")},
{'attr':'dimensions', 'display': tr("Dimensions")},
{'attr':'mtime', 'display': tr("Modification")},
{'attr':'percentage', 'display': tr("Match %")},
{'attr':'dupe_count', 'display': tr("Dupe Count")},
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('size', tr("Size (KB)")),
Column('extension', tr("Kind")),
Column('dimensions', tr("Dimensions")),
Column('mtime', tr("Modification")),
Column('percentage', tr("Match %")),
Column('dupe_count', tr("Dupe Count")),
]
MATCHPERC_COL = 6
DUPECOUNT_COL = 7
DELTA_COLUMNS = {2, 4, 5}
METADATA_TO_READ = ['size', 'mtime', 'dimensions']
def get_delta_dimensions(value, ref_value):
return (value[0]-ref_value[0], value[1]-ref_value[1])
def GetDisplayInfo(dupe,group,delta=False):
if (dupe is None) or (group is None):
return ['---'] * len(COLUMNS)
size = dupe.size
mtime = dupe.mtime
dimensions = dupe.dimensions
m = group.get_match_of(dupe)
if m:
percentage = m.percentage
@@ -44,6 +49,7 @@ def GetDisplayInfo(dupe,group,delta=False):
r = group.ref
size -= r.size
mtime -= r.mtime
dimensions = get_delta_dimensions(dimensions, r.dimensions)
else:
percentage = group.percentage
dupe_count = len(group.dupes)
@@ -53,7 +59,7 @@ def GetDisplayInfo(dupe,group,delta=False):
format_path(dupe_path),
format_size(size, 0, 1, False),
dupe.extension,
format_dimensions(dupe.dimensions),
format_dimensions(dimensions),
format_timestamp(mtime, delta and m),
format_perc(percentage),
format_dupe_count(dupe_count)
@@ -65,9 +71,13 @@ def GetDupeSortKey(dupe, get_group, key, delta):
return m.percentage
if key == DUPECOUNT_COL:
return 0
r = cmp_value(getattr(dupe, COLUMNS[key]['attr'], ''))
if delta and (key in {2, 5}):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr'], ''))
r = cmp_value(getattr(dupe, COLUMNS[key].attr, ''))
if delta and (key in DELTA_COLUMNS):
ref_value = cmp_value(getattr(get_group().ref, COLUMNS[key].attr, ''))
if key == 4: # dimensions
r = get_delta_dimensions(r, ref_value)
else:
r -= ref_value
return r
def GetGroupSortKey(group, key):
@@ -75,5 +85,5 @@ def GetGroupSortKey(group, key):
return group.percentage
if key == DUPECOUNT_COL:
return len(group)
return cmp_value(getattr(group.ref, COLUMNS[key]['attr'], ''))
return cmp_value(getattr(group.ref, COLUMNS[key].attr, ''))

View File

@@ -34,8 +34,9 @@ def prepare_pictures(pictures, cache_path, j=job.nulljob):
prepared = [] # only pictures for which there was no error getting blocks
try:
for picture in j.iter_with_progress(pictures, tr("Analyzed %d/%d pictures")):
picture.dimensions
picture.unicode_path = str(picture.path)
logging.debug("Analyzing picture at {}".format(picture.unicode_path))
picture.dimensions # pre-read dimensions
try:
if picture.unicode_path not in cache:
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)

View File

@@ -9,23 +9,24 @@
from hscommon.util import format_size
from hscommon.trans import tr as trbase
from core.data import (format_path, format_timestamp, format_words, format_perc,
format_dupe_count, cmp_value)
format_dupe_count, cmp_value, Column)
tr = lambda s: trbase(s, 'columns')
COLUMNS = [
{'attr':'name', 'display': tr("Filename")},
{'attr':'path', 'display': tr("Folder")},
{'attr':'size', 'display': tr("Size (KB)")},
{'attr':'extension', 'display': tr("Kind")},
{'attr':'mtime', 'display': tr("Modification")},
{'attr':'percentage', 'display': tr("Match %")},
{'attr':'words', 'display': tr("Words Used")},
{'attr':'dupe_count', 'display': tr("Dupe Count")},
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('size', tr("Size (KB)")),
Column('extension', tr("Kind")),
Column('mtime', tr("Modification")),
Column('percentage', tr("Match %")),
Column('words', tr("Words Used")),
Column('dupe_count', tr("Dupe Count")),
]
MATCHPERC_COL = 5
DUPECOUNT_COL = 7
DELTA_COLUMNS = {2, 4}
METADATA_TO_READ = ['size', 'mtime']
@@ -60,9 +61,9 @@ def GetDupeSortKey(dupe, get_group, key, delta):
return m.percentage
if key == DUPECOUNT_COL:
return 0
r = cmp_value(getattr(dupe, COLUMNS[key]['attr'], ''))
if delta and (key in {2, 4}):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr'], ''))
r = cmp_value(getattr(dupe, COLUMNS[key].attr, ''))
if delta and (key in DELTA_COLUMNS):
r -= cmp_value(getattr(get_group().ref, COLUMNS[key].attr, ''))
return r
def GetGroupSortKey(group, key):
@@ -70,4 +71,4 @@ def GetGroupSortKey(group, key):
return group.percentage
if key == DUPECOUNT_COL:
return len(group)
return cmp_value(getattr(group.ref, COLUMNS[key]['attr'], ''))
return cmp_value(getattr(group.ref, COLUMNS[key].attr, ''))

View File

@@ -1,3 +1,13 @@
=== 6.0.0 (2011-02-01)
* Re-designed the UI. (#129)
* Internationalized dupeGuru and localized it to french. (#32)
* Changed the format of the help file. (#130)
* Fixed crashes when reading malformed songs. (#127 #131)
* Removed focus from the cancel button in the progress dialog to avoid accidental cancellations. [Mac OS X] (#135)
* Folders added through drag and drop are added to the recent folders list. (#136)
* Added a debugging mode. (#132)
=== 5.10.4 (2010-12-30)
* Fixed bug causing results to be corrupted after a scan cancellation. (#120)

View File

@@ -1,3 +1,13 @@
=== 2.0.0 (2011-01-29)
* Re-designed the UI. (#129)
* Internationalized dupeGuru and localized it to french. (#32)
* Changed the format of the help file. (#130)
* The "Dimensions" column is now affected by the delta mode. (#115)
* Removed focus from the cancel button in the progress dialog to avoid accidental cancellations. [Mac OS X] (#135)
* Folders added through drag and drop are added to the recent folders list. (#136)
* Added a debugging mode. (#132)
=== 1.11.3 (2010-12-31)
* Fixed bug causing results to be corrupted after a scan cancellation. (#120)

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,10 +39,14 @@ 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>'
DELTA_COLUMNS = frozenset()
def __init__(self, data_module):
appdata = str(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
@@ -50,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)

View File

@@ -13,6 +13,7 @@ from PyQt4.QtGui import (QWidget, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLa
from hscommon.trans import tr, trmsg
from qtlib.recent import Recent
from qtlib.util import moveToScreenCenter
from core.app import NoScannableFileError
from . import platform
@@ -144,6 +145,8 @@ class DirectoriesDialog(QMainWindow):
if self.app.prefs.directoriesWindowRect is not None:
self.setGeometry(self.app.prefs.directoriesWindowRect)
else:
moveToScreenCenter(self)
def _updateAddButton(self):
if self.recentFolders.isEmpty():

View File

@@ -15,6 +15,7 @@ from PyQt4.QtGui import (QMainWindow, QMenu, QLabel, QHeaderView, QMessageBox, Q
from hscommon.trans import tr, trmsg
from hscommon.util import nonone
from qtlib.util import moveToScreenCenter
from .results_model import ResultsModel, ResultsView
from .stats_label import StatsLabel
@@ -137,7 +138,7 @@ class ResultWindow(QMainWindow):
menu = self.menuColumns
self._column_actions = []
for index, column in enumerate(self.app.data.COLUMNS):
action = menu.addAction(column['display'])
action = menu.addAction(column.display)
action.setCheckable(True)
action.column_index = index
self._column_actions.append(action)
@@ -192,8 +193,11 @@ class ResultWindow(QMainWindow):
if self.app.prefs.resultWindowIsMaximized:
self.setWindowState(self.windowState() | Qt.WindowMaximized)
if self.app.prefs.resultWindowRect is not None and not self.app.prefs.resultWindowIsMaximized:
self.setGeometry(self.app.prefs.resultWindowRect)
else:
if self.app.prefs.resultWindowRect is not None:
self.setGeometry(self.app.prefs.resultWindowRect)
else:
moveToScreenCenter(self)
#--- Private
def _load_columns(self):

View File

@@ -18,7 +18,7 @@ class ResultsModel(Table):
model = ResultTableModel(self, app)
self._app = app
self._data = app.data
self._delta_columns = app.DELTA_COLUMNS
self._delta_columns = app.data.DELTA_COLUMNS
Table.__init__(self, model, view)
self.model.connect()
@@ -63,7 +63,7 @@ class ResultsModel(Table):
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self._data.COLUMNS):
return self._data.COLUMNS[section]['display']
return self._data.COLUMNS[section].display
return None
def setData(self, index, value, role):

View File

@@ -244,7 +244,7 @@
</message>
<message>
<source>Excluded</source>
<translation>Exclus</translation>
<translation>Exclu</translation>
</message>
<message>
<source>Problems!</source>

View File

@@ -17,7 +17,6 @@ class DupeGuru(DupeGuruBase):
EDITION = 'me'
LOGO_NAME = 'logo_me'
NAME = __appname__
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7])
def __init__(self):
DupeGuruBase.__init__(self, data)

View File

@@ -59,7 +59,6 @@ class DupeGuru(DupeGuruBase):
EDITION = 'pe'
LOGO_NAME = 'logo_pe'
NAME = __appname__
DELTA_COLUMNS = frozenset([2, 5])
def __init__(self):
DupeGuruBase.__init__(self, data_pe)

View File

@@ -27,7 +27,6 @@ class DupeGuru(DupeGuruBase):
EDITION = 'se'
LOGO_NAME = 'logo_se'
NAME = __appname__
DELTA_COLUMNS = frozenset([2, 4])
def __init__(self):
DupeGuruBase.__init__(self, data)