1
0
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:
Virgil Dupras 2010-04-12 12:21:01 +02:00
parent 7f10aa3de2
commit 1e0f6bfecb
16 changed files with 1406 additions and 69 deletions

View 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

View 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

View File

@ -41,7 +41,7 @@ http://www.hardcoded.net/licenses/hs_license
//Data //Data
- (NSNumber *)getIgnoreListCount; - (NSNumber *)getIgnoreListCount;
- (NSNumber *)getMarkCount; - (NSNumber *)getMarkCount;
- (NSNumber *)getOperationalErrorCount; - (BOOL)scanWasProblematic;
//Scanning options //Scanning options
- (void)setMinMatchPercentage:(NSNumber *)percentage; - (void)setMinMatchPercentage:(NSNumber *)percentage;

View 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

View File

@ -10,6 +10,7 @@ http://www.hardcoded.net/licenses/hs_license
#import "HSOutlineView.h" #import "HSOutlineView.h"
#import "StatsLabel.h" #import "StatsLabel.h"
#import "ResultOutline.h" #import "ResultOutline.h"
#import "ProblemDialog.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@interface ResultWindowBase : NSWindowController @interface ResultWindowBase : NSWindowController
@ -28,6 +29,7 @@ http://www.hardcoded.net/licenses/hs_license
NSWindowController *preferencesPanel; NSWindowController *preferencesPanel;
ResultOutline *outline; ResultOutline *outline;
StatsLabel *statsLabel; StatsLabel *statsLabel;
ProblemDialog *problemDialog;
} }
/* Helpers */ /* Helpers */
- (void)fillColumnsMenu; - (void)fillColumnsMenu;

View File

@ -21,6 +21,7 @@ http://www.hardcoded.net/licenses/hs_license
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"]; preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
outline = [[ResultOutline alloc] initWithPyParent:py view:matches]; outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats]; statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
problemDialog = [[ProblemDialog alloc] initWithPy:py];
[self initResultColumns]; [self initResultColumns];
[self fillColumnsMenu]; [self fillColumnsMenu];
[deltaSwitch setSelectedSegment:0]; [deltaSwitch setSelectedSegment:0];
@ -38,6 +39,8 @@ http://www.hardcoded.net/licenses/hs_license
{ {
[outline release]; [outline release];
[preferencesPanel release]; [preferencesPanel release];
[statsLabel release];
[problemDialog release];
[super dealloc]; [super dealloc];
} }
@ -349,28 +352,30 @@ http://www.hardcoded.net/licenses/hs_license
- (void)jobCompleted:(NSNotification *)aNotification - (void)jobCompleted:(NSNotification *)aNotification
{ {
NSInteger r = n2i([py getOperationalErrorCount]);
id lastAction = [[ProgressController mainProgressController] jobId]; id lastAction = [[ProgressController mainProgressController] jobId];
if ([lastAction isEqualTo:jobCopy]) { if ([lastAction isEqualTo:jobCopy]) {
if (r > 0) if ([py scanWasProblematic]) {
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]]; [problemDialog showWindow:self];
else }
else {
[Dialogs showMessage:@"All marked files were copied sucessfully."]; [Dialogs showMessage:@"All marked files were copied sucessfully."];
}
} }
else if ([lastAction isEqualTo:jobMove]) { else if ([lastAction isEqualTo:jobMove]) {
if (r > 0) if ([py scanWasProblematic]) {
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]]; [problemDialog showWindow:self];
else }
else {
[Dialogs showMessage:@"All marked files were moved sucessfully."]; [Dialogs showMessage:@"All marked files were moved sucessfully."];
}
} }
else if ([lastAction isEqualTo:jobDelete]) { else if ([lastAction isEqualTo:jobDelete]) {
if (r > 0) { if ([py scanWasProblematic]) {
NSString *msg = @"%d file(s) couldn't be sent to Trash. They were kept in the results, "\ [problemDialog showWindow:self];
"and still are marked. See the F.A.Q. section in the help file for details.";
[Dialogs showMessage:[NSString stringWithFormat:msg,r]];
} }
else else {
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."]; [Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
}
} }
else if ([lastAction isEqualTo:jobScan]) { else if ([lastAction isEqualTo:jobScan]) {
NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil]; NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,8 @@
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3A46F9109B212E002ABFD5 /* MainMenu.xib */; }; CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3A46F9109B212E002ABFD5 /* MainMenu.xib */; };
CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; }; CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {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 */; }; CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; }; CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.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 */; }; CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */; };
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; }; CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.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 */; }; CE91F215113BC22D0010360B /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F212113BC22D0010360B /* ResultOutline.m */; };
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; }; CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; };
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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; }; 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; }; 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; }; 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; }; 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; }; 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 */, CE76FDDE111EE42F006618EA /* HSOutline.m */,
CE76FDC8111EE38E006618EA /* HSGUIController.h */, CE76FDC8111EE38E006618EA /* HSGUIController.h */,
CE76FDC9111EE38E006618EA /* HSGUIController.m */, CE76FDC9111EE38E006618EA /* HSGUIController.m */,
CE8C53BB117324CE0011B41F /* HSTable.m */,
); );
name = controllers; name = controllers;
path = ../../cocoalib/controllers; path = ../../cocoalib/controllers;
@ -281,6 +291,7 @@
children = ( children = (
CE76FDCD111EE38E006618EA /* PyGUI.h */, CE76FDCD111EE38E006618EA /* PyGUI.h */,
CE76FDCE111EE38E006618EA /* PyOutline.h */, CE76FDCE111EE38E006618EA /* PyOutline.h */,
CE8C53B61173248F0011B41F /* PyTable.h */,
); );
name = proxies; name = proxies;
path = ../../cocoalib/proxies; path = ../../cocoalib/proxies;
@ -299,6 +310,7 @@
CEEFC0CA10943849001F3A39 /* xib */ = { CEEFC0CA10943849001F3A39 /* xib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE647E581173026F006D28BA /* ProblemDialog.xib */,
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */, CE3A46F9109B212E002ABFD5 /* MainMenu.xib */,
CEAC6810109B0B7E00B43C85 /* Preferences.xib */, CEAC6810109B0B7E00B43C85 /* Preferences.xib */,
CEEFC0F710945D9F001F3A39 /* DirectoryPanel.xib */, CEEFC0F710945D9F001F3A39 /* DirectoryPanel.xib */,
@ -370,6 +382,9 @@
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */, CE6E7407111C997500C350E3 /* PyDetailsPanel.h */,
CEFC7FB70FC951A700CD5728 /* ResultWindow.h */, CEFC7FB70FC951A700CD5728 /* ResultWindow.h */,
CEFC7FB80FC951A700CD5728 /* ResultWindow.m */, CEFC7FB80FC951A700CD5728 /* ResultWindow.m */,
CE647E541173024A006D28BA /* ProblemDialog.h */,
CE647E551173024A006D28BA /* ProblemDialog.m */,
CE647E561173024A006D28BA /* PyProblemDialog.h */,
); );
name = dgbase; name = dgbase;
sourceTree = "<group>"; sourceTree = "<group>";
@ -435,6 +450,7 @@
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */, CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */,
CE19BC6411199231007CCEB0 /* progress.xib in Resources */, CE19BC6411199231007CCEB0 /* progress.xib in Resources */,
CE19BC6511199231007CCEB0 /* registration.xib in Resources */, CE19BC6511199231007CCEB0 /* registration.xib in Resources */,
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -470,6 +486,8 @@
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */, CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */, CE91F215113BC22D0010360B /* ResultOutline.m in Sources */,
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */, CE91F216113BC22D0010360B /* StatsLabel.m in Sources */,
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */,
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -48,7 +48,6 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.results = results.Results(data_module) self.results = results.Results(data_module)
self.scanner = scanner.Scanner() self.scanner = scanner.Scanner()
self.action_count = 0 self.action_count = 0
self.last_op_error_count = 0
self.options = { self.options = {
'escape_filter_regexp': True, 'escape_filter_regexp': True,
'clean_empty_dirs': False, 'clean_empty_dirs': False,
@ -70,19 +69,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
return self._do_delete_dupe(dupe) return self._do_delete_dupe(dupe)
j.start_job(self.results.mark_count) 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): def _do_delete_dupe(self, dupe):
if not io.exists(dupe.path): if not io.exists(dupe.path):
return True return
try: send2trash(unicode(dupe.path)) # Raises OSError when there's a problem
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
self.clean_empty_dirs(dupe.path[:-1]) self.clean_empty_dirs(dupe.path[:-1])
return True
def _do_load(self, j): def _do_load(self, j):
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml')) 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): def _job_completed(self, jobid):
# Must be called by subclasses when they detect that an async job is completed. # 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') self.notify('results_changed')
elif jobid in (JOB_LOAD, JOB_MOVE, JOB_DELETE):
self.notify('results_changed')
self.notify('problems_changed')
@staticmethod @staticmethod
def _open_path(path): 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 dest_path = dest_path + source_path[1:-1] #Remove drive letter and filename
elif dest_type == 1: elif dest_type == 1:
dest_path = dest_path + source_path[location_path:-1] dest_path = dest_path + source_path[location_path:-1]
try: if not io.exists(dest_path):
if not io.exists(dest_path): io.makedirs(dest_path)
io.makedirs(dest_path) # Raises an EnvironmentError if there's a problem
if copy: if copy:
files.copy(source_path, dest_path) files.copy(source_path, dest_path)
else: else:
files.move(source_path, dest_path) files.move(source_path, dest_path)
self.clean_empty_dirs(source_path[:-1]) 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
def copy_or_move_marked(self, copy, destination, recreate_path): def copy_or_move_marked(self, copy, destination, recreate_path):
def do(j): def do(j):
def op(dupe): def op(dupe):
j.add_progress() 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) 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() self._demo_check()
jobid = JOB_COPY if copy else JOB_MOVE jobid = JOB_COPY if copy else JOB_MOVE
@ -283,7 +274,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.notify('results_changed_but_keep_selection') self.notify('results_changed_but_keep_selection')
def remove_marked(self): 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') self.notify('results_changed')
def remove_selected(self): def remove_selected(self):

View File

@ -9,10 +9,12 @@
# Common interface for all editions' dg_cocoa unit. # 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.details_panel import DetailsPanel
from .gui.directory_tree import DirectoryTree 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.result_tree import ResultTree
from .gui.stats_label import StatsLabel from .gui.stats_label import StatsLabel
@ -100,8 +102,9 @@ class PyDupeGuruBase(PyRegistrable):
def getMarkCount(self): def getMarkCount(self):
return self.py.results.mark_count return self.py.results.mark_count
def getOperationalErrorCount(self): @signature('i@:')
return self.py.last_op_error_count def scanWasProblematic(self):
return bool(self.py.results.problems)
#---Properties #---Properties
def setMixFileKind_(self, mix_file_kind): def setMixFileKind_(self, mix_file_kind):
@ -196,3 +199,13 @@ class PyStatsLabel(PyGUIObject):
def display(self): def display(self):
return self.py.display return self.py.display
class PyProblemDialog(PyGUIObject):
py_class = ProblemDialog
def revealSelected(self):
self.py.reveal_selected_dupe()
class PyProblemTable(PyTable):
py_class = ProblemTable

View File

@ -24,6 +24,9 @@ class GUIObject(Listener):
def marking_changed(self): def marking_changed(self):
pass pass
def problems_changed(self):
pass
def results_changed(self): def results_changed(self):
pass pass

View 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
View 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)

View File

@ -32,6 +32,7 @@ class Results(Markable):
self.__recalculate_stats() self.__recalculate_stats()
self.__marked_size = 0 self.__marked_size = 0
self.data = data_module self.data = data_module
self.problems = [] # (dupe, error_msg)
def _did_mark(self, dupe): def _did_mark(self, dupe):
self.__marked_size += dupe.size self.__marked_size += dupe.size
@ -230,17 +231,22 @@ class Results(Markable):
self.__dupes = None self.__dupes = None
def perform_on_marked(self, func, remove_from_results): def perform_on_marked(self, func, remove_from_results):
problems = [] # Performs `func` on all marked dupes. If an EnvironmentError is raised during the call,
for d in self.dupes: # the problematic dupe is added to self.problems.
if self.is_marked(d) and (not func(d)): self.problems = []
problems.append(d) 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: 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.remove_duplicates(to_remove)
self.mark_none() self.mark_none()
for d in problems: for dupe, _ in self.problems:
self.mark(d) self.mark(dupe)
return len(problems)
def remove_duplicates(self, dupes): def remove_duplicates(self, dupes):
'''Remove 'dupes' from their respective group, and remove the group is it ends up empty. '''Remove 'dupes' from their respective group, and remove the group is it ends up empty.

View File

@ -9,7 +9,9 @@
import StringIO import StringIO
import os.path as op import os.path as op
from lxml import etree from lxml import etree
from nose.tools import eq_
from hsutil.path import Path from hsutil.path import Path
from hsutil.testcase import TestCase from hsutil.testcase import TestCase
@ -252,18 +254,23 @@ class TCResultsMarkings(TestCase):
def test_perform_on_marked_with_problems(self): def test_perform_on_marked_with_problems(self):
def log_object(o): def log_object(o):
log.append(o) log.append(o)
return o is not self.objects[1] if o is self.objects[1]:
raise EnvironmentError('foobar')
log = [] log = []
self.results.mark_all() self.results.mark_all()
self.assert_(self.results.is_marked(self.objects[1])) assert self.results.is_marked(self.objects[1])
self.assertEqual(1,self.results.perform_on_marked(log_object, True)) self.results.perform_on_marked(log_object, True)
self.assertEqual(3,len(log)) eq_(len(log), 3)
self.assertEqual(1,len(self.results.groups)) eq_(len(self.results.groups), 1)
self.assertEqual(2,len(self.results.groups[0])) eq_(len(self.results.groups[0]), 2)
self.assert_(self.objects[1] in self.results.groups[0]) assert self.objects[1] in self.results.groups[0]
self.assert_(not self.results.is_marked(self.objects[2])) 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])
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 test_perform_on_marked_with_ref(self):
def log_object(o): def log_object(o):

View File

@ -7,9 +7,7 @@
# http://www.hardcoded.net/licenses/hs_license # http://www.hardcoded.net/licenses/hs_license
import os.path as op import os.path as op
import logging
import plistlib import plistlib
import re
from lxml import etree from lxml import etree
from appscript import app, k, CommandError from appscript import app, k, CommandError
@ -148,7 +146,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
except (CommandError, RuntimeError): except (CommandError, RuntimeError):
pass pass
j.start_job(self.results.mark_count, "Sending dupes to the Trash") 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 del self.path2iphoto
def _do_delete_dupe(self, dupe): def _do_delete_dupe(self, dupe):
@ -158,14 +156,13 @@ class DupeGuruPE(app_cocoa.DupeGuru):
try: try:
a = app('iPhoto') a = app('iPhoto')
a.remove(photo, timeout=0) a.remove(photo, timeout=0)
return True except (CommandError, RuntimeError) as e:
except (CommandError, RuntimeError): raise EnvironmentError(unicode(e))
return False
else: else:
logging.warning(u"Could not find photo %s in iPhoto Library", unicode(dupe.path)) msg = u"Could not find photo %s in iPhoto Library" % unicode(dupe.path)
return False raise EnvironmentError(msg)
else: else:
return app_cocoa.DupeGuru._do_delete_dupe(self, dupe) app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
def _do_load(self, j): def _do_load(self, j):
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml')) self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))