mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-10 05:34:36 +00:00
Added a dialog giving more information about the causes of problems during operations.
This commit is contained in:
parent
7f10aa3de2
commit
1e0f6bfecb
25
cocoa/base/ProblemDialog.h
Normal file
25
cocoa/base/ProblemDialog.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.hardcoded.net/licenses/hs_license
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "HSWindowController.h"
|
||||
#import "PyApp.h"
|
||||
#import "PyProblemDialog.h"
|
||||
#import "HSTable.h"
|
||||
|
||||
@interface ProblemDialog : HSWindowController
|
||||
{
|
||||
IBOutlet NSTableView *problemTableView;
|
||||
|
||||
HSTable *problemTable;
|
||||
}
|
||||
- (id)initWithPy:(PyApp *)aPy;
|
||||
- (PyProblemDialog *)py;
|
||||
|
||||
- (IBAction)revealSelected:(id)sender;
|
||||
@end
|
40
cocoa/base/ProblemDialog.m
Normal file
40
cocoa/base/ProblemDialog.m
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.hardcoded.net/licenses/hs_license
|
||||
*/
|
||||
|
||||
#import "ProblemDialog.h"
|
||||
#import "Utils.h"
|
||||
|
||||
@implementation ProblemDialog
|
||||
- (id)initWithPy:(PyApp *)aPy
|
||||
{
|
||||
self = [super initWithNibName:@"ProblemDialog" pyClassName:@"PyProblemDialog" pyParent:aPy];
|
||||
[self window]; //So the detailsTable is initialized.
|
||||
problemTable = [[HSTable alloc] initWithPyClassName:@"PyProblemTable" pyParent:[self py] view:problemTableView];
|
||||
[self connect];
|
||||
[problemTable connect];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[problemTable disconnect];
|
||||
[self disconnect];
|
||||
[problemTable release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (PyProblemDialog *)py
|
||||
{
|
||||
return (PyProblemDialog *)py;
|
||||
}
|
||||
|
||||
- (IBAction)revealSelected:(id)sender
|
||||
{
|
||||
[[self py] revealSelected];
|
||||
}
|
||||
@end
|
@ -41,7 +41,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
//Data
|
||||
- (NSNumber *)getIgnoreListCount;
|
||||
- (NSNumber *)getMarkCount;
|
||||
- (NSNumber *)getOperationalErrorCount;
|
||||
- (BOOL)scanWasProblematic;
|
||||
|
||||
//Scanning options
|
||||
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
||||
|
14
cocoa/base/PyProblemDialog.h
Normal file
14
cocoa/base/PyProblemDialog.h
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.hardcoded.net/licenses/hs_license
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "PyGUI.h"
|
||||
|
||||
@interface PyProblemDialog : PyGUI
|
||||
- (void)revealSelected;
|
||||
@end
|
@ -10,6 +10,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
#import "HSOutlineView.h"
|
||||
#import "StatsLabel.h"
|
||||
#import "ResultOutline.h"
|
||||
#import "ProblemDialog.h"
|
||||
#import "PyDupeGuru.h"
|
||||
|
||||
@interface ResultWindowBase : NSWindowController
|
||||
@ -28,6 +29,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
NSWindowController *preferencesPanel;
|
||||
ResultOutline *outline;
|
||||
StatsLabel *statsLabel;
|
||||
ProblemDialog *problemDialog;
|
||||
}
|
||||
/* Helpers */
|
||||
- (void)fillColumnsMenu;
|
||||
|
@ -21,6 +21,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
||||
outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
|
||||
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
|
||||
problemDialog = [[ProblemDialog alloc] initWithPy:py];
|
||||
[self initResultColumns];
|
||||
[self fillColumnsMenu];
|
||||
[deltaSwitch setSelectedSegment:0];
|
||||
@ -38,6 +39,8 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
{
|
||||
[outline release];
|
||||
[preferencesPanel release];
|
||||
[statsLabel release];
|
||||
[problemDialog release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@ -349,28 +352,30 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
- (void)jobCompleted:(NSNotification *)aNotification
|
||||
{
|
||||
NSInteger r = n2i([py getOperationalErrorCount]);
|
||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
||||
if ([lastAction isEqualTo:jobCopy]) {
|
||||
if (r > 0)
|
||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]];
|
||||
else
|
||||
if ([py scanWasProblematic]) {
|
||||
[problemDialog showWindow:self];
|
||||
}
|
||||
else {
|
||||
[Dialogs showMessage:@"All marked files were copied sucessfully."];
|
||||
}
|
||||
}
|
||||
else if ([lastAction isEqualTo:jobMove]) {
|
||||
if (r > 0)
|
||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]];
|
||||
else
|
||||
if ([py scanWasProblematic]) {
|
||||
[problemDialog showWindow:self];
|
||||
}
|
||||
else {
|
||||
[Dialogs showMessage:@"All marked files were moved sucessfully."];
|
||||
}
|
||||
}
|
||||
else if ([lastAction isEqualTo:jobDelete]) {
|
||||
if (r > 0) {
|
||||
NSString *msg = @"%d file(s) couldn't be sent to Trash. They were kept in the results, "\
|
||||
"and still are marked. See the F.A.Q. section in the help file for details.";
|
||||
[Dialogs showMessage:[NSString stringWithFormat:msg,r]];
|
||||
if ([py scanWasProblematic]) {
|
||||
[problemDialog showWindow:self];
|
||||
}
|
||||
else
|
||||
else {
|
||||
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
||||
}
|
||||
}
|
||||
else if ([lastAction isEqualTo:jobScan]) {
|
||||
NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];
|
||||
|
1142
cocoa/base/xib/ProblemDialog.xib
Normal file
1142
cocoa/base/xib/ProblemDialog.xib
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,8 @@
|
||||
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3A46F9109B212E002ABFD5 /* MainMenu.xib */; };
|
||||
CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
||||
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
||||
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = CE647E551173024A006D28BA /* ProblemDialog.m */; };
|
||||
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE647E581173026F006D28BA /* ProblemDialog.xib */; };
|
||||
CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
|
||||
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
|
||||
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */; };
|
||||
@ -27,6 +29,7 @@
|
||||
CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */; };
|
||||
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
|
||||
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.m */; };
|
||||
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE8C53BB117324CE0011B41F /* HSTable.m */; };
|
||||
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F212113BC22D0010360B /* ResultOutline.m */; };
|
||||
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; };
|
||||
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
|
||||
@ -83,6 +86,10 @@
|
||||
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
||||
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
||||
CE45579A0AE3BC2B005A9546 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
||||
CE647E541173024A006D28BA /* ProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProblemDialog.h; path = ../base/ProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||
CE647E551173024A006D28BA /* ProblemDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProblemDialog.m; path = ../base/ProblemDialog.m; sourceTree = SOURCE_ROOT; };
|
||||
CE647E561173024A006D28BA /* PyProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyProblemDialog.h; path = ../base/PyProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||
CE647E581173026F006D28BA /* ProblemDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = ProblemDialog.xib; path = ../base/xib/ProblemDialog.xib; sourceTree = SOURCE_ROOT; };
|
||||
CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
|
||||
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||
CE76FDBE111EE37C006618EA /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
|
||||
@ -102,6 +109,8 @@
|
||||
CE76FDDE111EE42F006618EA /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
|
||||
CE76FDF5111EE561006618EA /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
|
||||
CE76FDF6111EE561006618EA /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
|
||||
CE8C53B61173248F0011B41F /* PyTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyTable.h; sourceTree = "<group>"; };
|
||||
CE8C53BB117324CE0011B41F /* HSTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSTable.m; sourceTree = "<group>"; };
|
||||
CE91F20F113BC22D0010360B /* PyResultTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTree.h; path = ../base/PyResultTree.h; sourceTree = SOURCE_ROOT; };
|
||||
CE91F210113BC22D0010360B /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||
CE91F211113BC22D0010360B /* ResultOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultOutline.h; path = ../base/ResultOutline.h; sourceTree = SOURCE_ROOT; };
|
||||
@ -271,6 +280,7 @@
|
||||
CE76FDDE111EE42F006618EA /* HSOutline.m */,
|
||||
CE76FDC8111EE38E006618EA /* HSGUIController.h */,
|
||||
CE76FDC9111EE38E006618EA /* HSGUIController.m */,
|
||||
CE8C53BB117324CE0011B41F /* HSTable.m */,
|
||||
);
|
||||
name = controllers;
|
||||
path = ../../cocoalib/controllers;
|
||||
@ -281,6 +291,7 @@
|
||||
children = (
|
||||
CE76FDCD111EE38E006618EA /* PyGUI.h */,
|
||||
CE76FDCE111EE38E006618EA /* PyOutline.h */,
|
||||
CE8C53B61173248F0011B41F /* PyTable.h */,
|
||||
);
|
||||
name = proxies;
|
||||
path = ../../cocoalib/proxies;
|
||||
@ -299,6 +310,7 @@
|
||||
CEEFC0CA10943849001F3A39 /* xib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE647E581173026F006D28BA /* ProblemDialog.xib */,
|
||||
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */,
|
||||
CEAC6810109B0B7E00B43C85 /* Preferences.xib */,
|
||||
CEEFC0F710945D9F001F3A39 /* DirectoryPanel.xib */,
|
||||
@ -370,6 +382,9 @@
|
||||
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */,
|
||||
CEFC7FB70FC951A700CD5728 /* ResultWindow.h */,
|
||||
CEFC7FB80FC951A700CD5728 /* ResultWindow.m */,
|
||||
CE647E541173024A006D28BA /* ProblemDialog.h */,
|
||||
CE647E551173024A006D28BA /* ProblemDialog.m */,
|
||||
CE647E561173024A006D28BA /* PyProblemDialog.h */,
|
||||
);
|
||||
name = dgbase;
|
||||
sourceTree = "<group>";
|
||||
@ -435,6 +450,7 @@
|
||||
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */,
|
||||
CE19BC6411199231007CCEB0 /* progress.xib in Resources */,
|
||||
CE19BC6511199231007CCEB0 /* registration.xib in Resources */,
|
||||
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -470,6 +486,8 @@
|
||||
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
|
||||
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */,
|
||||
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */,
|
||||
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */,
|
||||
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
45
core/app.py
45
core/app.py
@ -48,7 +48,6 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
self.results = results.Results(data_module)
|
||||
self.scanner = scanner.Scanner()
|
||||
self.action_count = 0
|
||||
self.last_op_error_count = 0
|
||||
self.options = {
|
||||
'escape_filter_regexp': True,
|
||||
'clean_empty_dirs': False,
|
||||
@ -70,19 +69,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
return self._do_delete_dupe(dupe)
|
||||
|
||||
j.start_job(self.results.mark_count)
|
||||
self.last_op_error_count = self.results.perform_on_marked(op, True)
|
||||
self.results.perform_on_marked(op, True)
|
||||
|
||||
def _do_delete_dupe(self, dupe):
|
||||
if not io.exists(dupe.path):
|
||||
return True
|
||||
try:
|
||||
send2trash(unicode(dupe.path))
|
||||
except OSError as e:
|
||||
msg = "Could not send {0} to trash: {1}"
|
||||
logging.warning(msg.format(unicode(dupe.path), unicode(e)))
|
||||
return False
|
||||
return
|
||||
send2trash(unicode(dupe.path)) # Raises OSError when there's a problem
|
||||
self.clean_empty_dirs(dupe.path[:-1])
|
||||
return True
|
||||
|
||||
def _do_load(self, j):
|
||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||
@ -114,8 +107,11 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
|
||||
def _job_completed(self, jobid):
|
||||
# Must be called by subclasses when they detect that an async job is completed.
|
||||
if jobid in (JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_DELETE):
|
||||
if jobid == JOB_SCAN:
|
||||
self.notify('results_changed')
|
||||
elif jobid in (JOB_LOAD, JOB_MOVE, JOB_DELETE):
|
||||
self.notify('results_changed')
|
||||
self.notify('problems_changed')
|
||||
|
||||
@staticmethod
|
||||
def _open_path(path):
|
||||
@ -182,28 +178,23 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
dest_path = dest_path + source_path[1:-1] #Remove drive letter and filename
|
||||
elif dest_type == 1:
|
||||
dest_path = dest_path + source_path[location_path:-1]
|
||||
try:
|
||||
if not io.exists(dest_path):
|
||||
io.makedirs(dest_path)
|
||||
if copy:
|
||||
files.copy(source_path, dest_path)
|
||||
else:
|
||||
files.move(source_path, dest_path)
|
||||
self.clean_empty_dirs(source_path[:-1])
|
||||
except EnvironmentError as e:
|
||||
operation = 'Copy' if copy else 'Move'
|
||||
logging.warning('%s operation failed on %s. Error: %s' % (operation, unicode(dupe.path), unicode(e)))
|
||||
return False
|
||||
return True
|
||||
if not io.exists(dest_path):
|
||||
io.makedirs(dest_path)
|
||||
# Raises an EnvironmentError if there's a problem
|
||||
if copy:
|
||||
files.copy(source_path, dest_path)
|
||||
else:
|
||||
files.move(source_path, dest_path)
|
||||
self.clean_empty_dirs(source_path[:-1])
|
||||
|
||||
def copy_or_move_marked(self, copy, destination, recreate_path):
|
||||
def do(j):
|
||||
def op(dupe):
|
||||
j.add_progress()
|
||||
return self.copy_or_move(dupe, copy, destination, recreate_path)
|
||||
self.copy_or_move(dupe, copy, destination, recreate_path)
|
||||
|
||||
j.start_job(self.results.mark_count)
|
||||
self.last_op_error_count = self.results.perform_on_marked(op, not copy)
|
||||
self.results.perform_on_marked(op, not copy)
|
||||
|
||||
self._demo_check()
|
||||
jobid = JOB_COPY if copy else JOB_MOVE
|
||||
@ -283,7 +274,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
self.notify('results_changed_but_keep_selection')
|
||||
|
||||
def remove_marked(self):
|
||||
self.results.perform_on_marked(lambda x:True, True)
|
||||
self.results.perform_on_marked(lambda x:None, True)
|
||||
self.notify('results_changed')
|
||||
|
||||
def remove_selected(self):
|
||||
|
@ -9,10 +9,12 @@
|
||||
|
||||
# Common interface for all editions' dg_cocoa unit.
|
||||
|
||||
from hsutil.cocoa.inter import signature, PyOutline, PyGUIObject, PyRegistrable
|
||||
from hsutil.cocoa.inter import signature, PyTable, PyOutline, PyGUIObject, PyRegistrable
|
||||
|
||||
from .gui.details_panel import DetailsPanel
|
||||
from .gui.directory_tree import DirectoryTree
|
||||
from .gui.problem_dialog import ProblemDialog
|
||||
from .gui.problem_table import ProblemTable
|
||||
from .gui.result_tree import ResultTree
|
||||
from .gui.stats_label import StatsLabel
|
||||
|
||||
@ -100,8 +102,9 @@ class PyDupeGuruBase(PyRegistrable):
|
||||
def getMarkCount(self):
|
||||
return self.py.results.mark_count
|
||||
|
||||
def getOperationalErrorCount(self):
|
||||
return self.py.last_op_error_count
|
||||
@signature('i@:')
|
||||
def scanWasProblematic(self):
|
||||
return bool(self.py.results.problems)
|
||||
|
||||
#---Properties
|
||||
def setMixFileKind_(self, mix_file_kind):
|
||||
@ -196,3 +199,13 @@ class PyStatsLabel(PyGUIObject):
|
||||
def display(self):
|
||||
return self.py.display
|
||||
|
||||
|
||||
class PyProblemDialog(PyGUIObject):
|
||||
py_class = ProblemDialog
|
||||
|
||||
def revealSelected(self):
|
||||
self.py.reveal_selected_dupe()
|
||||
|
||||
|
||||
class PyProblemTable(PyTable):
|
||||
py_class = ProblemTable
|
||||
|
@ -24,6 +24,9 @@ class GUIObject(Listener):
|
||||
def marking_changed(self):
|
||||
pass
|
||||
|
||||
def problems_changed(self):
|
||||
pass
|
||||
|
||||
def results_changed(self):
|
||||
pass
|
||||
|
||||
|
31
core/gui/problem_dialog.py
Normal file
31
core/gui/problem_dialog.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2010-04-12
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from hsutil.notify import Broadcaster
|
||||
|
||||
from .base import GUIObject
|
||||
|
||||
class ProblemDialog(GUIObject, Broadcaster):
|
||||
def __init__(self, view, app):
|
||||
GUIObject.__init__(self, view, app)
|
||||
Broadcaster.__init__(self)
|
||||
self._selected_dupe = None
|
||||
|
||||
def reveal_selected_dupe(self):
|
||||
if self._selected_dupe is not None:
|
||||
self.app._reveal_path(self._selected_dupe.path)
|
||||
|
||||
def select_dupe(self, dupe):
|
||||
self._selected_dupe = dupe
|
||||
|
||||
#--- Event Handlers
|
||||
def problems_changed(self):
|
||||
self._selected_dupe = None
|
||||
self.notify('problems_changed')
|
||||
|
43
core/gui/problem_table.py
Normal file
43
core/gui/problem_table.py
Normal file
@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2010-04-12
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from hsutil.notify import Listener
|
||||
from hsgui.table import GUITable, Row
|
||||
|
||||
class ProblemTable(GUITable, Listener):
|
||||
def __init__(self, view, problem_dialog):
|
||||
GUITable.__init__(self)
|
||||
Listener.__init__(self, problem_dialog)
|
||||
self.view = view
|
||||
self.dialog = problem_dialog
|
||||
|
||||
#--- Override
|
||||
def _update_selection(self):
|
||||
row = self.selected_row
|
||||
dupe = row.dupe if row is not None else None
|
||||
self.dialog.select_dupe(dupe)
|
||||
|
||||
def _fill(self):
|
||||
problems = self.dialog.app.results.problems
|
||||
for dupe, msg in problems:
|
||||
self.append(ProblemRow(self, dupe, msg))
|
||||
|
||||
#--- Event handlers
|
||||
def problems_changed(self):
|
||||
self.refresh()
|
||||
self.view.refresh()
|
||||
|
||||
|
||||
class ProblemRow(Row):
|
||||
def __init__(self, table, dupe, msg):
|
||||
Row.__init__(self, table)
|
||||
self.dupe = dupe
|
||||
self.msg = msg
|
||||
self.path = unicode(dupe.path)
|
||||
|
@ -32,6 +32,7 @@ class Results(Markable):
|
||||
self.__recalculate_stats()
|
||||
self.__marked_size = 0
|
||||
self.data = data_module
|
||||
self.problems = [] # (dupe, error_msg)
|
||||
|
||||
def _did_mark(self, dupe):
|
||||
self.__marked_size += dupe.size
|
||||
@ -230,17 +231,22 @@ class Results(Markable):
|
||||
self.__dupes = None
|
||||
|
||||
def perform_on_marked(self, func, remove_from_results):
|
||||
problems = []
|
||||
for d in self.dupes:
|
||||
if self.is_marked(d) and (not func(d)):
|
||||
problems.append(d)
|
||||
# Performs `func` on all marked dupes. If an EnvironmentError is raised during the call,
|
||||
# the problematic dupe is added to self.problems.
|
||||
self.problems = []
|
||||
to_remove = []
|
||||
marked = (dupe for dupe in self.dupes if self.is_marked(dupe))
|
||||
for dupe in marked:
|
||||
try:
|
||||
func(dupe)
|
||||
to_remove.append(dupe)
|
||||
except EnvironmentError as e:
|
||||
self.problems.append((dupe, unicode(e)))
|
||||
if remove_from_results:
|
||||
to_remove = [d for d in self.dupes if self.is_marked(d) and (d not in problems)]
|
||||
self.remove_duplicates(to_remove)
|
||||
self.mark_none()
|
||||
for d in problems:
|
||||
self.mark(d)
|
||||
return len(problems)
|
||||
for dupe, _ in self.problems:
|
||||
self.mark(dupe)
|
||||
|
||||
def remove_duplicates(self, dupes):
|
||||
'''Remove 'dupes' from their respective group, and remove the group is it ends up empty.
|
||||
|
@ -9,7 +9,9 @@
|
||||
|
||||
import StringIO
|
||||
import os.path as op
|
||||
|
||||
from lxml import etree
|
||||
from nose.tools import eq_
|
||||
|
||||
from hsutil.path import Path
|
||||
from hsutil.testcase import TestCase
|
||||
@ -252,18 +254,23 @@ class TCResultsMarkings(TestCase):
|
||||
def test_perform_on_marked_with_problems(self):
|
||||
def log_object(o):
|
||||
log.append(o)
|
||||
return o is not self.objects[1]
|
||||
if o is self.objects[1]:
|
||||
raise EnvironmentError('foobar')
|
||||
|
||||
log = []
|
||||
self.results.mark_all()
|
||||
self.assert_(self.results.is_marked(self.objects[1]))
|
||||
self.assertEqual(1,self.results.perform_on_marked(log_object, True))
|
||||
self.assertEqual(3,len(log))
|
||||
self.assertEqual(1,len(self.results.groups))
|
||||
self.assertEqual(2,len(self.results.groups[0]))
|
||||
self.assert_(self.objects[1] in self.results.groups[0])
|
||||
self.assert_(not self.results.is_marked(self.objects[2]))
|
||||
self.assert_(self.results.is_marked(self.objects[1]))
|
||||
assert self.results.is_marked(self.objects[1])
|
||||
self.results.perform_on_marked(log_object, True)
|
||||
eq_(len(log), 3)
|
||||
eq_(len(self.results.groups), 1)
|
||||
eq_(len(self.results.groups[0]), 2)
|
||||
assert self.objects[1] in self.results.groups[0]
|
||||
assert not self.results.is_marked(self.objects[2])
|
||||
assert self.results.is_marked(self.objects[1])
|
||||
eq_(len(self.results.problems), 1)
|
||||
dupe, msg = self.results.problems[0]
|
||||
assert dupe is self.objects[1]
|
||||
eq_(msg, 'foobar')
|
||||
|
||||
def test_perform_on_marked_with_ref(self):
|
||||
def log_object(o):
|
||||
|
@ -7,9 +7,7 @@
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import os.path as op
|
||||
import logging
|
||||
import plistlib
|
||||
import re
|
||||
|
||||
from lxml import etree
|
||||
from appscript import app, k, CommandError
|
||||
@ -148,7 +146,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
||||
except (CommandError, RuntimeError):
|
||||
pass
|
||||
j.start_job(self.results.mark_count, "Sending dupes to the Trash")
|
||||
self.last_op_error_count = self.results.perform_on_marked(op, True)
|
||||
self.results.perform_on_marked(op, True)
|
||||
del self.path2iphoto
|
||||
|
||||
def _do_delete_dupe(self, dupe):
|
||||
@ -158,14 +156,13 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
||||
try:
|
||||
a = app('iPhoto')
|
||||
a.remove(photo, timeout=0)
|
||||
return True
|
||||
except (CommandError, RuntimeError):
|
||||
return False
|
||||
except (CommandError, RuntimeError) as e:
|
||||
raise EnvironmentError(unicode(e))
|
||||
else:
|
||||
logging.warning(u"Could not find photo %s in iPhoto Library", unicode(dupe.path))
|
||||
return False
|
||||
msg = u"Could not find photo %s in iPhoto Library" % unicode(dupe.path)
|
||||
raise EnvironmentError(msg)
|
||||
else:
|
||||
return app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
|
||||
app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
|
||||
|
||||
def _do_load(self, j):
|
||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user