mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 08:01:39 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6af6e4dc | ||
|
|
a6d2a9b7b3 | ||
|
|
cf34164191 | ||
|
|
9a7bb30df4 | ||
|
|
5dc78809b6 | ||
|
|
2b53a6e7d6 | ||
|
|
eb82a35e5b | ||
|
|
51b14435e0 | ||
|
|
59de033523 | ||
|
|
c9b0a278ca | ||
|
|
b487189742 | ||
|
|
d5a60b1580 | ||
|
|
e2665610e9 | ||
|
|
3262ee9938 | ||
|
|
2f153003b3 | ||
|
|
6724e710d8 | ||
|
|
9729e05fe8 | ||
|
|
686c60b83b | ||
|
|
5fe11f3b3a | ||
|
|
30d29c6b34 | ||
|
|
fbfb16e77a | ||
|
|
7aea384f86 | ||
|
|
78bef5c3c6 | ||
|
|
5c8d90a57c | ||
|
|
13dc9ff76d | ||
|
|
eb3645d493 | ||
|
|
0a06e52d65 | ||
|
|
e004c0c2d4 | ||
|
|
6d5f6a0c3c | ||
|
|
024e3c380f | ||
|
|
06859fe9cd | ||
|
|
6392d08584 | ||
|
|
80a5290bc8 | ||
|
|
6b30c88fba | ||
|
|
6adce9bf03 | ||
|
|
3c90ad81a7 | ||
|
|
484529add0 | ||
|
|
600c7906a4 | ||
|
|
f070e90347 | ||
|
|
88127d8b8d | ||
|
|
607ab86188 | ||
|
|
c936f9ccc6 | ||
|
|
d4d8917956 | ||
|
|
89bce95c27 | ||
|
|
f0a38a2b3f | ||
|
|
911521d8e0 | ||
|
|
b25c1c3a3b | ||
|
|
37a40040b3 | ||
|
|
25dadc83eb | ||
|
|
b8c11b5aae | ||
|
|
a3ab314378 | ||
|
|
794192835d | ||
|
|
385768a69b | ||
|
|
a281931b16 | ||
|
|
085311d559 | ||
|
|
4d7f032889 | ||
|
|
cf44c93013 | ||
|
|
787cbcd01f | ||
|
|
b2b316b642 | ||
|
|
49165125e4 | ||
|
|
54ac0fd19e | ||
|
|
0aff7f16e5 | ||
|
|
f9abc3b35d | ||
|
|
b167a51243 | ||
|
|
371cdda911 | ||
|
|
11977c6533 | ||
|
|
7228adf433 |
@@ -9,17 +9,22 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "RecentDirectories.h"
|
#import "RecentDirectories.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
|
||||||
@interface AppDelegateBase : NSObject
|
@interface AppDelegateBase : NSObject
|
||||||
{
|
{
|
||||||
IBOutlet PyDupeGuruBase *py;
|
IBOutlet PyDupeGuruBase *py;
|
||||||
IBOutlet RecentDirectories *recentDirectories;
|
IBOutlet RecentDirectories *recentDirectories;
|
||||||
IBOutlet NSMenuItem *unlockMenuItem;
|
IBOutlet NSMenuItem *unlockMenuItem;
|
||||||
|
IBOutlet ResultWindowBase *result;
|
||||||
|
|
||||||
NSString *_appName;
|
NSString *_appName;
|
||||||
|
DetailsPanelBase *_detailsPanel;
|
||||||
}
|
}
|
||||||
- (IBAction)unlockApp:(id)sender;
|
- (IBAction)unlockApp:(id)sender;
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py;
|
- (PyDupeGuruBase *)py;
|
||||||
- (RecentDirectories *)recentDirectories;
|
- (RecentDirectories *)recentDirectories;
|
||||||
|
- (DetailsPanelBase *)detailsPanel; // Virtual
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
RegistrationInterface *ri = [[RegistrationInterface alloc] initWithApp:[self py] name:_appName limitDescription:LIMIT_DESC];
|
RegistrationInterface *ri = [[RegistrationInterface alloc] initWithApp:[self py] name:_appName limitDescription:LIMIT_DESC];
|
||||||
if ([ri enterCode] == NSOKButton)
|
if ([ri enterCode] == NSOKButton)
|
||||||
{
|
{
|
||||||
NSString *menuTitle = [NSString stringWithFormat:@"Thanks for buying %@",_appName];
|
NSString *menuTitle = [NSString stringWithFormat:@"Thanks for buying %@!",_appName];
|
||||||
[unlockMenuItem setTitle:menuTitle];
|
[unlockMenuItem setTitle:menuTitle];
|
||||||
}
|
}
|
||||||
[ri release];
|
[ri release];
|
||||||
@@ -35,4 +35,25 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (PyDupeGuruBase *)py { return py; }
|
- (PyDupeGuruBase *)py { return py; }
|
||||||
- (RecentDirectories *)recentDirectories { return recentDirectories; }
|
- (RecentDirectories *)recentDirectories { return recentDirectories; }
|
||||||
|
- (DetailsPanelBase *)detailsPanel { return nil; } // Virtual
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[[ProgressController mainProgressController] setWorker:py];
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
//Restore Columns
|
||||||
|
NSArray *columnsOrder = [ud arrayForKey:@"columnsOrder"];
|
||||||
|
NSDictionary *columnsWidth = [ud dictionaryForKey:@"columnsWidth"];
|
||||||
|
if ([columnsOrder count])
|
||||||
|
[result restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||||
|
else
|
||||||
|
[result resetColumnsToDefault:nil];
|
||||||
|
//Reg stuff
|
||||||
|
if ([RegistrationInterface showNagWithApp:[self py] name:_appName limitDescription:LIMIT_DESC])
|
||||||
|
[unlockMenuItem setTitle:[NSString stringWithFormat:@"Thanks for buying %@!",_appName]];
|
||||||
|
//Restore results
|
||||||
|
[py loadIgnoreList];
|
||||||
|
[py loadResults];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (id)initWithPy:(PyApp *)aPy;
|
- (id)initWithPy:(PyApp *)aPy;
|
||||||
|
|
||||||
- (void)refresh;
|
- (void)refresh;
|
||||||
|
- (void)toggleVisibility;
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification;
|
- (void)duplicateSelectionChanged:(NSNotification *)aNotification;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
@implementation DetailsPanelBase
|
@implementation DetailsPanelBase
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
- (id)initWithPy:(PyApp *)aPy
|
||||||
{
|
{
|
||||||
self = [super initWithWindowNibName:@"Details"];
|
self = [super initWithWindowNibName:@"DetailsPanel"];
|
||||||
[self window]; //So the detailsTable is initialized.
|
[self window]; //So the detailsTable is initialized.
|
||||||
[detailsTable setPy:aPy];
|
[detailsTable setPy:aPy];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(duplicateSelectionChanged:) name:DuplicateSelectionChangedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(duplicateSelectionChanged:) name:DuplicateSelectionChangedNotification object:nil];
|
||||||
@@ -24,6 +24,17 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[detailsTable reloadData];
|
[detailsTable reloadData];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)toggleVisibility
|
||||||
|
{
|
||||||
|
if ([[self window] isVisible])
|
||||||
|
[[self window] close];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self refresh]; // selection might have changed since last time
|
||||||
|
[[self window] orderFront:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
|
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,10 +11,19 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "Outline.h"
|
#import "Outline.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@interface DirectoryOutline : OutlineView
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@protocol DirectoryOutlineDelegate
|
||||||
|
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory;
|
||||||
|
@end
|
||||||
|
|
||||||
@interface DirectoryPanelBase : NSWindowController
|
@interface DirectoryPanelBase : NSWindowController
|
||||||
{
|
{
|
||||||
IBOutlet NSPopUpButton *addButtonPopUp;
|
IBOutlet NSPopUpButton *addButtonPopUp;
|
||||||
IBOutlet OutlineView *directories;
|
IBOutlet DirectoryOutline *directories;
|
||||||
IBOutlet NSButton *removeButton;
|
IBOutlet NSButton *removeButton;
|
||||||
|
|
||||||
PyDupeGuruBase *_py;
|
PyDupeGuruBase *_py;
|
||||||
|
|||||||
@@ -11,10 +11,52 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
@implementation DirectoryOutline
|
||||||
|
- (void)doInit
|
||||||
|
{
|
||||||
|
[super doInit];
|
||||||
|
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ([[pboard types] containsObject:NSFilenamesPboardType])
|
||||||
|
{
|
||||||
|
if (sourceDragMask & NSDragOperationLink)
|
||||||
|
return NSDragOperationLink;
|
||||||
|
}
|
||||||
|
return NSDragOperationNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ( [[pboard types] containsObject:NSFilenamesPboardType] )
|
||||||
|
{
|
||||||
|
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
|
||||||
|
if (!(sourceDragMask & NSDragOperationLink))
|
||||||
|
return NO;
|
||||||
|
if (([self delegate] == nil) || (![[self delegate] respondsToSelector:@selector(outlineView:addDirectory:)]))
|
||||||
|
return NO;
|
||||||
|
for (NSString *filename in filenames)
|
||||||
|
[[self delegate] outlineView:self addDirectory:filename];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation DirectoryPanelBase
|
@implementation DirectoryPanelBase
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
- (id)initWithParentApp:(id)aParentApp
|
||||||
{
|
{
|
||||||
self = [super initWithWindowNibName:@"Directories"];
|
self = [super initWithWindowNibName:@"DirectoryPanel"];
|
||||||
[self window];
|
[self window];
|
||||||
AppDelegateBase *app = aParentApp;
|
AppDelegateBase *app = aParentApp;
|
||||||
_py = [app py];
|
_py = [app py];
|
||||||
@@ -104,10 +146,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (IBAction)toggleVisible:(id)sender
|
- (IBAction)toggleVisible:(id)sender
|
||||||
{
|
{
|
||||||
if ([[self window] isVisible])
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
[[self window] close];
|
|
||||||
else
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
@@ -154,6 +193,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
/* Delegate */
|
/* Delegate */
|
||||||
|
|
||||||
|
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory
|
||||||
|
{
|
||||||
|
[self addDirectory:directory];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||||
{
|
{
|
||||||
OVNode *node = item;
|
OVNode *node = item;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "Outline.h"
|
#import "Outline.h"
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface MatchesView : OutlineView
|
@interface MatchesView : OutlineView
|
||||||
@@ -20,29 +19,29 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
@protected
|
@protected
|
||||||
IBOutlet PyDupeGuruBase *py;
|
IBOutlet PyDupeGuruBase *py;
|
||||||
IBOutlet id app;
|
IBOutlet id app;
|
||||||
IBOutlet NSView *actionMenuView;
|
|
||||||
IBOutlet NSSegmentedControl *deltaSwitch;
|
IBOutlet NSSegmentedControl *deltaSwitch;
|
||||||
IBOutlet NSView *deltaSwitchView;
|
|
||||||
IBOutlet NSView *filterFieldView;
|
|
||||||
IBOutlet MatchesView *matches;
|
IBOutlet MatchesView *matches;
|
||||||
IBOutlet NSSegmentedControl *pmSwitch;
|
IBOutlet NSSegmentedControl *pmSwitch;
|
||||||
IBOutlet NSView *pmSwitchView;
|
|
||||||
IBOutlet NSTextField *stats;
|
IBOutlet NSTextField *stats;
|
||||||
|
IBOutlet NSMenu *columnsMenu;
|
||||||
|
|
||||||
BOOL _powerMode;
|
BOOL _powerMode;
|
||||||
BOOL _displayDelta;
|
BOOL _displayDelta;
|
||||||
|
NSMutableArray *_resultColumns;
|
||||||
|
NSWindowController *preferencesPanel;
|
||||||
}
|
}
|
||||||
/* Override */
|
|
||||||
- (NSString *)logoImageName;
|
|
||||||
|
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu;
|
||||||
|
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn;
|
||||||
- (NSArray *)getColumnsOrder;
|
- (NSArray *)getColumnsOrder;
|
||||||
- (NSDictionary *)getColumnsWidth;
|
- (NSDictionary *)getColumnsWidth;
|
||||||
- (NSArray *)getSelected:(BOOL)aDupesOnly;
|
- (NSArray *)getSelected:(BOOL)aDupesOnly;
|
||||||
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
|
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
|
||||||
|
- (void)initResultColumns;
|
||||||
- (void)updatePySelection;
|
- (void)updatePySelection;
|
||||||
- (void)performPySelection:(NSArray *)aIndexPaths;
|
- (void)performPySelection:(NSArray *)aIndexPaths;
|
||||||
- (void)refreshStats;
|
- (void)refreshStats;
|
||||||
|
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
- (IBAction)changeDelta:(id)sender;
|
- (IBAction)changeDelta:(id)sender;
|
||||||
@@ -52,7 +51,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)expandAll:(id)sender;
|
- (IBAction)expandAll:(id)sender;
|
||||||
- (IBAction)exportToXHTML:(id)sender;
|
- (IBAction)exportToXHTML:(id)sender;
|
||||||
- (IBAction)moveMarked:(id)sender;
|
- (IBAction)moveMarked:(id)sender;
|
||||||
|
- (IBAction)resetColumnsToDefault:(id)sender;
|
||||||
|
- (IBAction)showPreferencesPanel:(id)sender;
|
||||||
- (IBAction)switchSelected:(id)sender;
|
- (IBAction)switchSelected:(id)sender;
|
||||||
|
- (IBAction)toggleColumn:(id)sender;
|
||||||
|
- (IBAction)toggleDetailsPanel:(id)sender;
|
||||||
- (IBAction)togglePowerMarker:(id)sender;
|
- (IBAction)togglePowerMarker:(id)sender;
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
|
|||||||
@@ -14,15 +14,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
|
||||||
#define tbbDirectories @"tbbDirectories"
|
|
||||||
#define tbbDetails @"tbbDetail"
|
|
||||||
#define tbbPreferences @"tbbPreferences"
|
|
||||||
#define tbbPowerMarker @"tbbPowerMarker"
|
|
||||||
#define tbbScan @"tbbScan"
|
|
||||||
#define tbbAction @"tbbAction"
|
|
||||||
#define tbbDelta @"tbbDelta"
|
|
||||||
#define tbbFilter @"tbbFilter"
|
|
||||||
|
|
||||||
@implementation MatchesView
|
@implementation MatchesView
|
||||||
- (void)keyDown:(NSEvent *)theEvent
|
- (void)keyDown:(NSEvent *)theEvent
|
||||||
{
|
{
|
||||||
@@ -60,6 +51,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)awakeFromNib
|
- (void)awakeFromNib
|
||||||
{
|
{
|
||||||
[self window];
|
[self window];
|
||||||
|
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
||||||
|
[self initResultColumns];
|
||||||
|
[self fillColumnsMenu];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
|
||||||
@@ -68,13 +62,42 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Virtual */
|
- (void)dealloc
|
||||||
- (NSString *)logoImageName
|
|
||||||
{
|
{
|
||||||
return @"dg_logo32";
|
[preferencesPanel release];
|
||||||
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu
|
||||||
|
{
|
||||||
|
// The columns menu is supposed to be empty and initResultColumns must have been called
|
||||||
|
for (NSTableColumn *col in _resultColumns)
|
||||||
|
{
|
||||||
|
NSMenuItem *mi = [columnsMenu addItemWithTitle:[[col headerCell] stringValue] action:@selector(toggleColumn:) keyEquivalent:@""];
|
||||||
|
[mi setTag:[[col identifier] integerValue]];
|
||||||
|
[mi setTarget:self];
|
||||||
|
if ([[matches tableColumns] containsObject:col])
|
||||||
|
[mi setState:NSOnState];
|
||||||
|
}
|
||||||
|
[columnsMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
NSMenuItem *mi = [columnsMenu addItemWithTitle:@"Reset to Default" action:@selector(resetColumnsToDefault:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn
|
||||||
|
{
|
||||||
|
NSNumber *n = [NSNumber numberWithInt:aIdentifier];
|
||||||
|
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[n stringValue]];
|
||||||
|
[col setWidth:aWidth];
|
||||||
|
[col setEditable:NO];
|
||||||
|
[[col dataCell] setFont:[[aColumn dataCell] font]];
|
||||||
|
[[col headerCell] setStringValue:aTitle];
|
||||||
|
[col setResizingMask:NSTableColumnUserResizingMask];
|
||||||
|
[col setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:[n stringValue] ascending:YES]];
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
//Returns an array of identifiers, in order.
|
//Returns an array of identifiers, in order.
|
||||||
- (NSArray *)getColumnsOrder
|
- (NSArray *)getColumnsOrder
|
||||||
{
|
{
|
||||||
@@ -135,6 +158,40 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)initResultColumns
|
||||||
|
{
|
||||||
|
// Virtual
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
|
||||||
|
{
|
||||||
|
NSTableColumn *col;
|
||||||
|
NSString *colId;
|
||||||
|
NSNumber *width;
|
||||||
|
NSMenuItem *mi;
|
||||||
|
//Remove all columns
|
||||||
|
NSEnumerator *e = [[columnsMenu itemArray] objectEnumerator];
|
||||||
|
while (mi = [e nextObject])
|
||||||
|
{
|
||||||
|
if ([mi state] == NSOnState)
|
||||||
|
[self toggleColumn:mi];
|
||||||
|
}
|
||||||
|
//Add columns and set widths
|
||||||
|
e = [aColumnsOrder objectEnumerator];
|
||||||
|
while (colId = [e nextObject])
|
||||||
|
{
|
||||||
|
if (![colId isEqual:@"mark"])
|
||||||
|
{
|
||||||
|
col = [_resultColumns objectAtIndex:[colId intValue]];
|
||||||
|
width = [aColumnsWidth objectForKey:[col identifier]];
|
||||||
|
mi = [columnsMenu itemWithTag:[colId intValue]];
|
||||||
|
if (width)
|
||||||
|
[col setWidth:[width floatValue]];
|
||||||
|
[self toggleColumn:mi];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)updatePySelection
|
- (void)updatePySelection
|
||||||
{
|
{
|
||||||
NSArray *selection;
|
NSArray *selection;
|
||||||
@@ -242,6 +299,16 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IBAction)resetColumnsToDefault:(id)sender
|
||||||
|
{
|
||||||
|
// Virtual
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showPreferencesPanel:(id)sender
|
||||||
|
{
|
||||||
|
[preferencesPanel showWindow:sender];
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)switchSelected:(id)sender
|
- (IBAction)switchSelected:(id)sender
|
||||||
{
|
{
|
||||||
// It might look like a complicated way to get the length of the current dupe list on the py side
|
// It might look like a complicated way to get the length of the current dupe list on the py side
|
||||||
@@ -259,6 +326,31 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleColumn:(id)sender
|
||||||
|
{
|
||||||
|
NSMenuItem *mi = sender;
|
||||||
|
NSString *colId = [NSString stringWithFormat:@"%d",[mi tag]];
|
||||||
|
NSTableColumn *col = [matches tableColumnWithIdentifier:colId];
|
||||||
|
if (col == nil)
|
||||||
|
{
|
||||||
|
//Add Column
|
||||||
|
col = [_resultColumns objectAtIndex:[mi tag]];
|
||||||
|
[matches addTableColumn:col];
|
||||||
|
[mi setState:NSOnState];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Remove column
|
||||||
|
[matches removeTableColumn:col];
|
||||||
|
[mi setState:NSOffState];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleDetailsPanel:(id)sender
|
||||||
|
{
|
||||||
|
[[(AppDelegateBase *)app detailsPanel] toggleVisibility];
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)togglePowerMarker:(id)sender
|
- (IBAction)togglePowerMarker:(id)sender
|
||||||
{
|
{
|
||||||
if ([pmSwitch selectedSegment] == 1)
|
if ([pmSwitch selectedSegment] == 1)
|
||||||
@@ -311,7 +403,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
if ([lastAction isEqualTo:jobDelete])
|
if ([lastAction isEqualTo:jobDelete])
|
||||||
{
|
{
|
||||||
if (r > 0)
|
if (r > 0)
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be sent to Trash. They were kept in the results, and still are marked.",r]];
|
{
|
||||||
|
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]];
|
||||||
|
}
|
||||||
else
|
else
|
||||||
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
||||||
}
|
}
|
||||||
@@ -353,107 +449,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)resultsUpdated:(NSNotification *)aNotification
|
- (void)resultsUpdated:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
[matches invalidateBuffers];
|
[matches invalidateBuffers];
|
||||||
}
|
[matches invalidateMarkings];
|
||||||
|
|
||||||
/* Toolbar */
|
|
||||||
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
|
|
||||||
{
|
|
||||||
NSToolbarItem *tbi = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
|
|
||||||
if ([itemIdentifier isEqualTo:tbbDirectories])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Directories"];
|
|
||||||
[tbi setToolTip: @"Show/Hide the directories panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"folder32"]];
|
|
||||||
[tbi setTarget: app];
|
|
||||||
[tbi setAction: @selector(toggleDirectories:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbDetails])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Details"];
|
|
||||||
[tbi setToolTip: @"Show/Hide the details panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"details32"]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(toggleDetailsPanel:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbPreferences])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Preferences"];
|
|
||||||
[tbi setToolTip: @"Show the preferences panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"preferences32"]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(showPreferencesPanel:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbPowerMarker])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Power Marker"];
|
|
||||||
[tbi setToolTip: @"When enabled, only the duplicates are shown, not the references."];
|
|
||||||
[tbi setView:pmSwitchView];
|
|
||||||
[tbi setMinSize:[pmSwitchView frame].size];
|
|
||||||
[tbi setMaxSize:[pmSwitchView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbScan])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Start Scanning"];
|
|
||||||
[tbi setToolTip: @"Start scanning for duplicates in the selected directories."];
|
|
||||||
[tbi setImage: [NSImage imageNamed:[self logoImageName]]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(startDuplicateScan:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbAction])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Action"];
|
|
||||||
[tbi setView:actionMenuView];
|
|
||||||
[tbi setMinSize:[actionMenuView frame].size];
|
|
||||||
[tbi setMaxSize:[actionMenuView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbDelta])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Delta Values"];
|
|
||||||
[tbi setToolTip: @"When enabled, this option makes dupeGuru display, where applicable, delta values instead of absolute values."];
|
|
||||||
[tbi setView:deltaSwitchView];
|
|
||||||
[tbi setMinSize:[deltaSwitchView frame].size];
|
|
||||||
[tbi setMaxSize:[deltaSwitchView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbFilter])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Filter"];
|
|
||||||
[tbi setToolTip: @"Filters the results using regular expression."];
|
|
||||||
[tbi setView:filterFieldView];
|
|
||||||
[tbi setMinSize:[filterFieldView frame].size];
|
|
||||||
[tbi setMaxSize:NSMakeSize(1000, [filterFieldView frame].size.height)];
|
|
||||||
}
|
|
||||||
[tbi setPaletteLabel: [tbi label]];
|
|
||||||
return tbi;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
|
|
||||||
{
|
|
||||||
return [NSArray arrayWithObjects:
|
|
||||||
tbbDirectories,
|
|
||||||
tbbDetails,
|
|
||||||
tbbPreferences,
|
|
||||||
tbbPowerMarker,
|
|
||||||
tbbScan,
|
|
||||||
tbbAction,
|
|
||||||
tbbDelta,
|
|
||||||
tbbFilter,
|
|
||||||
NSToolbarSeparatorItemIdentifier,
|
|
||||||
NSToolbarSpaceItemIdentifier,
|
|
||||||
NSToolbarFlexibleSpaceItemIdentifier,
|
|
||||||
nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
|
|
||||||
{
|
|
||||||
return [NSArray arrayWithObjects:
|
|
||||||
tbbScan,
|
|
||||||
tbbAction,
|
|
||||||
tbbDirectories,
|
|
||||||
tbbDetails,
|
|
||||||
tbbPowerMarker,
|
|
||||||
tbbDelta,
|
|
||||||
tbbFilter,
|
|
||||||
nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
||||||
|
|||||||
1083
base/cocoa/xib/DetailsPanel.xib
Normal file
1083
base/cocoa/xib/DetailsPanel.xib
Normal file
File diff suppressed because it is too large
Load Diff
1503
base/cocoa/xib/DirectoryPanel.xib
Normal file
1503
base/cocoa/xib/DirectoryPanel.xib
Normal file
File diff suppressed because it is too large
Load Diff
4618
base/cocoa/xib/MainMenu.xib
Normal file
4618
base/cocoa/xib/MainMenu.xib
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,13 +14,13 @@ import os
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from hsutil import job, io, files
|
from hsutil import io, files
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.reg import RegistrableApplication, RegistrationRequired
|
from hsutil.reg import RegistrableApplication, RegistrationRequired
|
||||||
from hsutil.misc import flatten, first
|
from hsutil.misc import flatten, first
|
||||||
from hsutil.str import escape
|
from hsutil.str import escape
|
||||||
|
|
||||||
from . import directories, results, scanner, export
|
from . import directories, results, scanner, export, fs
|
||||||
|
|
||||||
JOB_SCAN = 'job_scan'
|
JOB_SCAN = 'job_scan'
|
||||||
JOB_LOAD = 'job_load'
|
JOB_LOAD = 'job_load'
|
||||||
@@ -70,12 +70,10 @@ class DupeGuru(RegistrableApplication):
|
|||||||
|
|
||||||
def _do_delete_dupe(self, dupe):
|
def _do_delete_dupe(self, dupe):
|
||||||
if not io.exists(dupe.path):
|
if not io.exists(dupe.path):
|
||||||
dupe.parent = None
|
|
||||||
return True
|
return True
|
||||||
self._recycle_dupe(dupe)
|
self._recycle_dupe(dupe)
|
||||||
self.clean_empty_dirs(dupe.path[:-1])
|
self.clean_empty_dirs(dupe.path[:-1])
|
||||||
if not io.exists(dupe.path):
|
if not io.exists(dupe.path):
|
||||||
dupe.parent = None
|
|
||||||
return True
|
return True
|
||||||
logging.warning("Could not send {0} to trash.".format(unicode(dupe.path)))
|
logging.warning("Could not send {0} to trash.".format(unicode(dupe.path)))
|
||||||
return False
|
return False
|
||||||
@@ -98,13 +96,8 @@ class DupeGuru(RegistrableApplication):
|
|||||||
return ['---'] * len(self.data.COLUMNS)
|
return ['---'] * len(self.data.COLUMNS)
|
||||||
|
|
||||||
def _get_file(self, str_path):
|
def _get_file(self, str_path):
|
||||||
p = Path(str_path)
|
path = Path(str_path)
|
||||||
for d in self.directories:
|
return fs.get_file(path, self.directories.fileclasses)
|
||||||
if p not in d.path:
|
|
||||||
continue
|
|
||||||
result = d.find_path(p[d.path:])
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _recycle_dupe(dupe):
|
def _recycle_dupe(dupe):
|
||||||
@@ -150,7 +143,7 @@ class DupeGuru(RegistrableApplication):
|
|||||||
2 = absolute re-creation.
|
2 = absolute re-creation.
|
||||||
"""
|
"""
|
||||||
source_path = dupe.path
|
source_path = dupe.path
|
||||||
location_path = dupe.root.path
|
location_path = first(p for p in self.directories if dupe.path in p)
|
||||||
dest_path = Path(destination)
|
dest_path = Path(destination)
|
||||||
if dest_type == 2:
|
if dest_type == 2:
|
||||||
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
|
||||||
|
|||||||
@@ -7,18 +7,18 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
|
import objc
|
||||||
from Foundation import *
|
from Foundation import *
|
||||||
from AppKit import *
|
from AppKit import *
|
||||||
import logging
|
import logging
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
|
||||||
import hsfs as fs
|
|
||||||
from hsutil import io, cocoa, job
|
from hsutil import io, cocoa, job
|
||||||
from hsutil.cocoa import install_exception_hook
|
from hsutil.cocoa import install_exception_hook
|
||||||
from hsutil.misc import stripnone
|
from hsutil.misc import stripnone
|
||||||
from hsutil.reg import RegistrationRequired
|
from hsutil.reg import RegistrationRequired
|
||||||
|
|
||||||
import app, data
|
from . import app, fs
|
||||||
|
|
||||||
JOBID2TITLE = {
|
JOBID2TITLE = {
|
||||||
app.JOB_SCAN: "Scanning for duplicates",
|
app.JOB_SCAN: "Scanning for duplicates",
|
||||||
@@ -43,8 +43,6 @@ class DupeGuru(app.DupeGuru):
|
|||||||
logging.basicConfig(level=LOGGING_LEVEL, format='%(levelname)s %(message)s')
|
logging.basicConfig(level=LOGGING_LEVEL, format='%(levelname)s %(message)s')
|
||||||
logging.debug('started in debug mode')
|
logging.debug('started in debug mode')
|
||||||
install_exception_hook()
|
install_exception_hook()
|
||||||
if data_module is None:
|
|
||||||
data_module = data
|
|
||||||
appsupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0]
|
appsupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0]
|
||||||
appdata = op.join(appsupport, appdata_subdir)
|
appdata = op.join(appsupport, appdata_subdir)
|
||||||
app.DupeGuru.__init__(self, data_module, appdata, appid)
|
app.DupeGuru.__init__(self, data_module, appdata, appid)
|
||||||
@@ -56,18 +54,14 @@ class DupeGuru(app.DupeGuru):
|
|||||||
#--- Override
|
#--- Override
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _recycle_dupe(dupe):
|
def _recycle_dupe(dupe):
|
||||||
if not io.exists(dupe.path):
|
directory = unicode(dupe.path[:-1])
|
||||||
dupe.parent = None
|
|
||||||
return True
|
|
||||||
directory = unicode(dupe.parent.path)
|
|
||||||
filename = dupe.name
|
filename = dupe.name
|
||||||
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
|
if objc.__version__ == '1.4': # For a while, we have to support this.
|
||||||
NSWorkspaceRecycleOperation, directory, '', [filename])
|
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
|
||||||
if not io.exists(dupe.path):
|
NSWorkspaceRecycleOperation, directory, '', [filename])
|
||||||
dupe.parent = None
|
else:
|
||||||
return True
|
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
|
||||||
logging.warning('Could not send %s to trash. tag: %d' % (unicode(dupe.path), tag))
|
NSWorkspaceRecycleOperation, directory, '', [filename], None)
|
||||||
return False
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
def _start_job(self, jobid, func):
|
||||||
try:
|
try:
|
||||||
@@ -91,15 +85,15 @@ class DupeGuru(app.DupeGuru):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return (None,None)
|
return (None,None)
|
||||||
|
|
||||||
def GetDirectory(self,node_path,curr_dir=None):
|
def get_folder_path(self, node_path, curr_path=None):
|
||||||
if not node_path:
|
if not node_path:
|
||||||
return curr_dir
|
return curr_path
|
||||||
if curr_dir is not None:
|
current_index = node_path[0]
|
||||||
l = curr_dir.dirs
|
if curr_path is None:
|
||||||
|
curr_path = self.directories[current_index]
|
||||||
else:
|
else:
|
||||||
l = self.directories
|
curr_path = self.directories.get_subfolders(curr_path)[current_index]
|
||||||
d = l[node_path[0]]
|
return self.get_folder_path(node_path[1:], curr_path)
|
||||||
return self.GetDirectory(node_path[1:],d)
|
|
||||||
|
|
||||||
def RefreshDetailsTable(self,dupe,group):
|
def RefreshDetailsTable(self,dupe,group):
|
||||||
l1 = self._get_display_info(dupe, group, False)
|
l1 = self._get_display_info(dupe, group, False)
|
||||||
@@ -146,13 +140,13 @@ class DupeGuru(app.DupeGuru):
|
|||||||
def RemoveSelected(self):
|
def RemoveSelected(self):
|
||||||
self.results.remove_duplicates(self.selected_dupes)
|
self.results.remove_duplicates(self.selected_dupes)
|
||||||
|
|
||||||
def RenameSelected(self,newname):
|
def RenameSelected(self, newname):
|
||||||
try:
|
try:
|
||||||
d = self.selected_dupes[0]
|
d = self.selected_dupes[0]
|
||||||
d = d.move(d.parent,newname)
|
d.rename(newname)
|
||||||
return True
|
return True
|
||||||
except (IndexError,fs.FSError),e:
|
except (IndexError, fs.FSError) as e:
|
||||||
logging.warning("dupeGuru Warning: %s" % str(e))
|
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def RevealSelected(self):
|
def RevealSelected(self):
|
||||||
@@ -214,9 +208,9 @@ class DupeGuru(app.DupeGuru):
|
|||||||
self.results.dupes[row] for row in rows if row in xrange(len(self.results.dupes))
|
self.results.dupes[row] for row in rows if row in xrange(len(self.results.dupes))
|
||||||
]
|
]
|
||||||
|
|
||||||
def SetDirectoryState(self,node_path,state):
|
def SetDirectoryState(self, node_path, state):
|
||||||
d = self.GetDirectory(node_path)
|
p = self.get_folder_path(node_path)
|
||||||
self.directories.set_state(d.path,state)
|
self.directories.set_state(p, state)
|
||||||
|
|
||||||
def sort_dupes(self,key,asc):
|
def sort_dupes(self,key,asc):
|
||||||
self.results.sort_dupes(key,asc,self.display_delta_values)
|
self.results.sort_dupes(key,asc,self.display_delta_values)
|
||||||
@@ -245,8 +239,12 @@ class DupeGuru(app.DupeGuru):
|
|||||||
return [len(g.dupes) for g in self.results.groups]
|
return [len(g.dupes) for g in self.results.groups]
|
||||||
elif tag == 1: #Directories
|
elif tag == 1: #Directories
|
||||||
try:
|
try:
|
||||||
dirs = self.GetDirectory(node_path).dirs if node_path else self.directories
|
if node_path:
|
||||||
return [d.dircount for d in dirs]
|
path = self.get_folder_path(node_path)
|
||||||
|
subfolders = self.directories.get_subfolders(path)
|
||||||
|
else:
|
||||||
|
subfolders = self.directories
|
||||||
|
return [len(self.directories.get_subfolders(path)) for path in subfolders]
|
||||||
except IndexError: # node_path out of range
|
except IndexError: # node_path out of range
|
||||||
return []
|
return []
|
||||||
else: #Power Marker
|
else: #Power Marker
|
||||||
@@ -270,8 +268,9 @@ class DupeGuru(app.DupeGuru):
|
|||||||
return result
|
return result
|
||||||
elif tag == 1: #Directories
|
elif tag == 1: #Directories
|
||||||
try:
|
try:
|
||||||
d = self.GetDirectory(node_path)
|
path = self.get_folder_path(node_path)
|
||||||
return [d.name, self.directories.get_state(d.path)]
|
name = unicode(path) if len(node_path) == 1 else path[-1]
|
||||||
|
return [name, self.directories.get_state(path)]
|
||||||
except IndexError: # node_path out of range
|
except IndexError: # node_path out of range
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-24
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 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 logging
|
|
||||||
|
|
||||||
from AppKit import *
|
|
||||||
|
|
||||||
from hsfs.phys import Directory as DirectoryBase
|
|
||||||
from hsfs.phys.bundle import Bundle
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.misc import extract
|
|
||||||
from hsutil.str import get_file_ext
|
|
||||||
|
|
||||||
from . import app_cocoa, data
|
|
||||||
from .directories import Directories as DirectoriesBase, STATE_EXCLUDED
|
|
||||||
|
|
||||||
if NSWorkspace.sharedWorkspace().respondsToSelector_('typeOfFile:error:'): # Only from 10.5
|
|
||||||
def is_bundle(str_path):
|
|
||||||
sw = NSWorkspace.sharedWorkspace()
|
|
||||||
uti, error = sw.typeOfFile_error_(str_path)
|
|
||||||
if error is not None:
|
|
||||||
logging.warning(u'There was an error trying to detect the UTI of %s', str_path)
|
|
||||||
return sw.type_conformsToType_(uti, 'com.apple.bundle') or sw.type_conformsToType_(uti, 'com.apple.package')
|
|
||||||
else: # Tiger
|
|
||||||
def is_bundle(str_path): # just return a list of a few known bundle extensions.
|
|
||||||
return get_file_ext(str_path) in ('app', 'pages', 'numbers')
|
|
||||||
|
|
||||||
class DGDirectory(DirectoryBase):
|
|
||||||
def _create_sub_file(self, name, with_parent=True):
|
|
||||||
if is_bundle(unicode(self.path + name)):
|
|
||||||
parent = self if with_parent else None
|
|
||||||
return Bundle(parent, name)
|
|
||||||
else:
|
|
||||||
return super(DGDirectory, self)._create_sub_file(name, with_parent)
|
|
||||||
|
|
||||||
def _fetch_subitems(self):
|
|
||||||
subdirs, subfiles = super(DGDirectory, self)._fetch_subitems()
|
|
||||||
apps, normal_dirs = extract(lambda name: is_bundle(unicode(self.path + name)), subdirs)
|
|
||||||
subfiles += apps
|
|
||||||
return normal_dirs, subfiles
|
|
||||||
|
|
||||||
|
|
||||||
class Directories(DirectoriesBase):
|
|
||||||
ROOT_PATH_TO_EXCLUDE = map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev'])
|
|
||||||
HOME_PATH_TO_EXCLUDE = [Path('Library')]
|
|
||||||
def __init__(self):
|
|
||||||
DirectoriesBase.__init__(self)
|
|
||||||
self.dirclass = DGDirectory
|
|
||||||
|
|
||||||
def _default_state_for_path(self, path):
|
|
||||||
result = DirectoriesBase._default_state_for_path(self, path)
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
if path in self.ROOT_PATH_TO_EXCLUDE:
|
|
||||||
return STATE_EXCLUDED
|
|
||||||
if path[:2] == Path('/Users') and path[3:] in self.HOME_PATH_TO_EXCLUDE:
|
|
||||||
return STATE_EXCLUDED
|
|
||||||
|
|
||||||
|
|
||||||
class DupeGuru(app_cocoa.DupeGuru):
|
|
||||||
def __init__(self):
|
|
||||||
app_cocoa.DupeGuru.__init__(self, data, 'dupeGuru', appid=4)
|
|
||||||
self.directories = Directories()
|
|
||||||
|
|
||||||
@@ -40,63 +40,3 @@ def format_dupe_count(c):
|
|||||||
|
|
||||||
def cmp_value(value):
|
def cmp_value(value):
|
||||||
return value.lower() if isinstance(value, basestring) else value
|
return value.lower() if isinstance(value, basestring) else value
|
||||||
|
|
||||||
COLUMNS = [
|
|
||||||
{'attr':'name','display':'Filename'},
|
|
||||||
{'attr':'path','display':'Directory'},
|
|
||||||
{'attr':'size','display':'Size (KB)'},
|
|
||||||
{'attr':'extension','display':'Kind'},
|
|
||||||
{'attr':'ctime','display':'Creation'},
|
|
||||||
{'attr':'mtime','display':'Modification'},
|
|
||||||
{'attr':'percentage','display':'Match %'},
|
|
||||||
{'attr':'words','display':'Words Used'},
|
|
||||||
{'attr':'dupe_count','display':'Dupe Count'},
|
|
||||||
]
|
|
||||||
|
|
||||||
METADATA_TO_READ = ['size', 'ctime', 'mtime']
|
|
||||||
|
|
||||||
def GetDisplayInfo(dupe, group, delta):
|
|
||||||
size = dupe.size
|
|
||||||
ctime = dupe.ctime
|
|
||||||
mtime = dupe.mtime
|
|
||||||
m = group.get_match_of(dupe)
|
|
||||||
if m:
|
|
||||||
percentage = m.percentage
|
|
||||||
dupe_count = 0
|
|
||||||
if delta:
|
|
||||||
r = group.ref
|
|
||||||
size -= r.size
|
|
||||||
ctime -= r.ctime
|
|
||||||
mtime -= r.mtime
|
|
||||||
else:
|
|
||||||
percentage = group.percentage
|
|
||||||
dupe_count = len(group.dupes)
|
|
||||||
return [
|
|
||||||
dupe.name,
|
|
||||||
format_path(dupe.path),
|
|
||||||
format_size(size, 0, 1, False),
|
|
||||||
dupe.extension,
|
|
||||||
format_timestamp(ctime, delta and m),
|
|
||||||
format_timestamp(mtime, delta and m),
|
|
||||||
format_perc(percentage),
|
|
||||||
format_words(dupe.words),
|
|
||||||
format_dupe_count(dupe_count)
|
|
||||||
]
|
|
||||||
|
|
||||||
def GetDupeSortKey(dupe, get_group, key, delta):
|
|
||||||
if key == 6:
|
|
||||||
m = get_group().get_match_of(dupe)
|
|
||||||
return m.percentage
|
|
||||||
if key == 8:
|
|
||||||
return 0
|
|
||||||
r = cmp_value(getattr(dupe, COLUMNS[key]['attr']))
|
|
||||||
if delta and (key in (2, 4, 5)):
|
|
||||||
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr']))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def GetGroupSortKey(group, key):
|
|
||||||
if key == 6:
|
|
||||||
return group.percentage
|
|
||||||
if key == 8:
|
|
||||||
return len(group)
|
|
||||||
return cmp_value(getattr(group.ref, COLUMNS[key]['attr']))
|
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
|
|
||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
|
|
||||||
from hsfs import phys
|
from hsutil import io
|
||||||
import hsfs as fs
|
|
||||||
from hsutil.files import FileOrPath
|
from hsutil.files import FileOrPath
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
|
|
||||||
|
from . import fs
|
||||||
|
|
||||||
(STATE_NORMAL,
|
(STATE_NORMAL,
|
||||||
STATE_REFERENCE,
|
STATE_REFERENCE,
|
||||||
STATE_EXCLUDED) = range(3)
|
STATE_EXCLUDED) = range(3)
|
||||||
@@ -26,15 +27,14 @@ class InvalidPathError(Exception):
|
|||||||
|
|
||||||
class Directories(object):
|
class Directories(object):
|
||||||
#---Override
|
#---Override
|
||||||
def __init__(self):
|
def __init__(self, fileclasses=[fs.File]):
|
||||||
self._dirs = []
|
self._dirs = []
|
||||||
self.states = {}
|
self.states = {}
|
||||||
self.dirclass = phys.Directory
|
self.fileclasses = fileclasses
|
||||||
self.special_dirclasses = {}
|
|
||||||
|
|
||||||
def __contains__(self,path):
|
def __contains__(self, path):
|
||||||
for d in self._dirs:
|
for p in self._dirs:
|
||||||
if path in d.path:
|
if path in p:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -53,8 +53,7 @@ class Directories(object):
|
|||||||
if path[-1].startswith('.'): # hidden
|
if path[-1].startswith('.'): # hidden
|
||||||
return STATE_EXCLUDED
|
return STATE_EXCLUDED
|
||||||
|
|
||||||
def _get_files(self, from_dir):
|
def _get_files(self, from_path):
|
||||||
from_path = from_dir.path
|
|
||||||
state = self.get_state(from_path)
|
state = self.get_state(from_path)
|
||||||
if state == STATE_EXCLUDED:
|
if state == STATE_EXCLUDED:
|
||||||
# Recursively get files from folders with lots of subfolder is expensive. However, there
|
# Recursively get files from folders with lots of subfolder is expensive. However, there
|
||||||
@@ -62,14 +61,21 @@ class Directories(object):
|
|||||||
# through self.states and see if we must continue, or we can stop right here to save time
|
# through self.states and see if we must continue, or we can stop right here to save time
|
||||||
if not any(p[:len(from_path)] == from_path for p in self.states):
|
if not any(p[:len(from_path)] == from_path for p in self.states):
|
||||||
return
|
return
|
||||||
result = []
|
try:
|
||||||
for subdir in from_dir.dirs:
|
filepaths = set()
|
||||||
for file in self._get_files(subdir):
|
if state != STATE_EXCLUDED:
|
||||||
yield file
|
for file in fs.get_files(from_path, fileclasses=self.fileclasses):
|
||||||
if state != STATE_EXCLUDED:
|
file.is_ref = state == STATE_REFERENCE
|
||||||
for file in from_dir.files:
|
filepaths.add(file.path)
|
||||||
file.is_ref = state == STATE_REFERENCE
|
yield file
|
||||||
yield file
|
subpaths = [from_path + name for name in io.listdir(from_path)]
|
||||||
|
# it's possible that a folder (bundle) gets into the file list. in that case, we don't want to recurse into it
|
||||||
|
subfolders = [p for p in subpaths if not io.islink(p) and io.isdir(p) and p not in filepaths]
|
||||||
|
for subfolder in subfolders:
|
||||||
|
for file in self._get_files(subfolder):
|
||||||
|
yield file
|
||||||
|
except (EnvironmentError, fs.InvalidPath):
|
||||||
|
pass
|
||||||
|
|
||||||
#---Public
|
#---Public
|
||||||
def add_path(self, path):
|
def add_path(self, path):
|
||||||
@@ -80,29 +86,30 @@ class Directories(object):
|
|||||||
under it will be removed. Can also raise InvalidPathError if 'path' does not exist.
|
under it will be removed. Can also raise InvalidPathError if 'path' does not exist.
|
||||||
"""
|
"""
|
||||||
if path in self:
|
if path in self:
|
||||||
raise AlreadyThereError
|
raise AlreadyThereError()
|
||||||
self._dirs = [d for d in self._dirs if d.path not in path]
|
if not io.exists(path):
|
||||||
try:
|
|
||||||
dirclass = self.special_dirclasses.get(path, self.dirclass)
|
|
||||||
d = dirclass(None, unicode(path))
|
|
||||||
d[:] #If an InvalidPath exception has to be raised, it will be raised here
|
|
||||||
self._dirs.append(d)
|
|
||||||
return d
|
|
||||||
except fs.InvalidPath:
|
|
||||||
raise InvalidPathError()
|
raise InvalidPathError()
|
||||||
|
self._dirs = [p for p in self._dirs if p not in path]
|
||||||
|
self._dirs.append(path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_subfolders(path):
|
||||||
|
"""returns a sorted list of paths corresponding to subfolders in `path`"""
|
||||||
|
try:
|
||||||
|
names = [name for name in io.listdir(path) if io.isdir(path + name)]
|
||||||
|
names.sort(key=lambda x:x.lower())
|
||||||
|
return [path + name for name in names]
|
||||||
|
except EnvironmentError:
|
||||||
|
return []
|
||||||
|
|
||||||
def get_files(self):
|
def get_files(self):
|
||||||
"""Returns a list of all files that are not excluded.
|
"""Returns a list of all files that are not excluded.
|
||||||
|
|
||||||
Returned files also have their 'is_ref' attr set.
|
Returned files also have their 'is_ref' attr set.
|
||||||
"""
|
"""
|
||||||
for d in self._dirs:
|
for path in self._dirs:
|
||||||
d.force_update()
|
for file in self._get_files(path):
|
||||||
try:
|
yield file
|
||||||
for file in self._get_files(d):
|
|
||||||
yield file
|
|
||||||
except fs.InvalidPath:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_state(self, path):
|
def get_state(self, path):
|
||||||
"""Returns the state of 'path' (One of the STATE_* const.)
|
"""Returns the state of 'path' (One of the STATE_* const.)
|
||||||
@@ -123,8 +130,8 @@ class Directories(object):
|
|||||||
doc = xml.dom.minidom.parse(infile)
|
doc = xml.dom.minidom.parse(infile)
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
root_dir_nodes = doc.getElementsByTagName('root_directory')
|
root_path_nodes = doc.getElementsByTagName('root_directory')
|
||||||
for rdn in root_dir_nodes:
|
for rdn in root_path_nodes:
|
||||||
if not rdn.getAttributeNode('path'):
|
if not rdn.getAttributeNode('path'):
|
||||||
continue
|
continue
|
||||||
path = rdn.getAttributeNode('path').nodeValue
|
path = rdn.getAttributeNode('path').nodeValue
|
||||||
@@ -144,9 +151,9 @@ class Directories(object):
|
|||||||
with FileOrPath(outfile, 'wb') as fp:
|
with FileOrPath(outfile, 'wb') as fp:
|
||||||
doc = xml.dom.minidom.Document()
|
doc = xml.dom.minidom.Document()
|
||||||
root = doc.appendChild(doc.createElement('directories'))
|
root = doc.appendChild(doc.createElement('directories'))
|
||||||
for root_dir in self:
|
for root_path in self:
|
||||||
root_dir_node = root.appendChild(doc.createElement('root_directory'))
|
root_path_node = root.appendChild(doc.createElement('root_directory'))
|
||||||
root_dir_node.setAttribute('path', unicode(root_dir.path).encode('utf-8'))
|
root_path_node.setAttribute('path', unicode(root_path).encode('utf-8'))
|
||||||
for path, state in self.states.iteritems():
|
for path, state in self.states.iteritems():
|
||||||
state_node = root.appendChild(doc.createElement('state'))
|
state_node = root.appendChild(doc.createElement('state'))
|
||||||
state_node.setAttribute('path', unicode(path).encode('utf-8'))
|
state_node.setAttribute('path', unicode(path).encode('utf-8'))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import difflib
|
import difflib
|
||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
@@ -156,58 +157,71 @@ def get_match(first, second, flags=()):
|
|||||||
percentage = compare(first.words, second.words, flags)
|
percentage = compare(first.words, second.words, flags)
|
||||||
return Match(first, second, percentage)
|
return Match(first, second, percentage)
|
||||||
|
|
||||||
class MatchFactory(object):
|
def getmatches(objects, min_match_percentage=0, match_similar_words=False, weight_words=False,
|
||||||
common_word_threshold = 50
|
no_field_order=False, j=job.nulljob):
|
||||||
match_similar_words = False
|
COMMON_WORD_THRESHOLD = 50
|
||||||
min_match_percentage = 0
|
LIMIT = 5000000
|
||||||
weight_words = False
|
j = j.start_subjob(2)
|
||||||
no_field_order = False
|
sj = j.start_subjob(2)
|
||||||
limit = 5000000
|
for o in objects:
|
||||||
|
if not hasattr(o, 'words'):
|
||||||
def getmatches(self, objects, j=job.nulljob):
|
o.words = getwords(o.name)
|
||||||
j = j.start_subjob(2)
|
word_dict = build_word_dict(objects, sj)
|
||||||
sj = j.start_subjob(2)
|
reduce_common_words(word_dict, COMMON_WORD_THRESHOLD)
|
||||||
for o in objects:
|
if match_similar_words:
|
||||||
if not hasattr(o, 'words'):
|
merge_similar_words(word_dict)
|
||||||
o.words = getwords(o.name)
|
match_flags = []
|
||||||
word_dict = build_word_dict(objects, sj)
|
if weight_words:
|
||||||
reduce_common_words(word_dict, self.common_word_threshold)
|
match_flags.append(WEIGHT_WORDS)
|
||||||
if self.match_similar_words:
|
if match_similar_words:
|
||||||
merge_similar_words(word_dict)
|
match_flags.append(MATCH_SIMILAR_WORDS)
|
||||||
match_flags = []
|
if no_field_order:
|
||||||
if self.weight_words:
|
match_flags.append(NO_FIELD_ORDER)
|
||||||
match_flags.append(WEIGHT_WORDS)
|
j.start_job(len(word_dict), '0 matches found')
|
||||||
if self.match_similar_words:
|
compared = defaultdict(set)
|
||||||
match_flags.append(MATCH_SIMILAR_WORDS)
|
result = []
|
||||||
if self.no_field_order:
|
try:
|
||||||
match_flags.append(NO_FIELD_ORDER)
|
# This whole 'popping' thing is there to avoid taking too much memory at the same time.
|
||||||
j.start_job(len(word_dict), '0 matches found')
|
while word_dict:
|
||||||
compared = defaultdict(set)
|
items = word_dict.popitem()[1]
|
||||||
result = []
|
while items:
|
||||||
try:
|
ref = items.pop()
|
||||||
# This whole 'popping' thing is there to avoid taking too much memory at the same time.
|
compared_already = compared[ref]
|
||||||
while word_dict:
|
to_compare = items - compared_already
|
||||||
items = word_dict.popitem()[1]
|
compared_already |= to_compare
|
||||||
while items:
|
for other in to_compare:
|
||||||
ref = items.pop()
|
m = get_match(ref, other, match_flags)
|
||||||
compared_already = compared[ref]
|
if m.percentage >= min_match_percentage:
|
||||||
to_compare = items - compared_already
|
result.append(m)
|
||||||
compared_already |= to_compare
|
if len(result) >= LIMIT:
|
||||||
for other in to_compare:
|
return result
|
||||||
m = get_match(ref, other, match_flags)
|
j.add_progress(desc='%d matches found' % len(result))
|
||||||
if m.percentage >= self.min_match_percentage:
|
except MemoryError:
|
||||||
result.append(m)
|
# This is the place where the memory usage is at its peak during the scan.
|
||||||
if len(result) >= self.limit:
|
# Just continue the process with an incomplete list of matches.
|
||||||
return result
|
del compared # This should give us enough room to call logging.
|
||||||
j.add_progress(desc='%d matches found' % len(result))
|
logging.warning('Memory Overflow. Matches: %d. Word dict: %d' % (len(result), len(word_dict)))
|
||||||
except MemoryError:
|
|
||||||
# This is the place where the memory usage is at its peak during the scan.
|
|
||||||
# Just continue the process with an incomplete list of matches.
|
|
||||||
del compared # This should give us enough room to call logging.
|
|
||||||
logging.warning('Memory Overflow. Matches: %d. Word dict: %d' % (len(result), len(word_dict)))
|
|
||||||
return result
|
|
||||||
return result
|
return result
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getmatches_by_contents(files, sizeattr='size', partial=False, j=job.nulljob):
|
||||||
|
j = j.start_subjob([2, 8])
|
||||||
|
size2files = defaultdict(set)
|
||||||
|
for file in j.iter_with_progress(files, 'Read size of %d/%d files'):
|
||||||
|
filesize = getattr(file, sizeattr)
|
||||||
|
if filesize:
|
||||||
|
size2files[filesize].add(file)
|
||||||
|
possible_matches = [files for files in size2files.values() if len(files) > 1]
|
||||||
|
del size2files
|
||||||
|
result = []
|
||||||
|
j.start_job(len(possible_matches), '0 matches found')
|
||||||
|
for group in possible_matches:
|
||||||
|
for first, second in itertools.combinations(group, 2):
|
||||||
|
if first.md5partial == second.md5partial:
|
||||||
|
if partial or first.md5 == second.md5:
|
||||||
|
result.append(Match(first, second, 100))
|
||||||
|
j.add_progress(desc='%d matches found' % len(result))
|
||||||
|
return result
|
||||||
|
|
||||||
class Group(object):
|
class Group(object):
|
||||||
#---Override
|
#---Override
|
||||||
|
|||||||
178
base/py/fs.py
Normal file
178
base/py/fs.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-10-22
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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
|
||||||
|
|
||||||
|
# This is a fork from hsfs. The reason for this fork is that hsfs has been designed for musicGuru
|
||||||
|
# and was re-used for dupeGuru. The problem is that hsfs is way over-engineered for dupeGuru,
|
||||||
|
# resulting needless complexity and memory usage. It's been a while since I wanted to do that fork,
|
||||||
|
# and I'm doing it now.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from hsutil import io
|
||||||
|
from hsutil.misc import nonone, flatten
|
||||||
|
from hsutil.str import get_file_ext
|
||||||
|
|
||||||
|
class FSError(Exception):
|
||||||
|
cls_message = "An error has occured on '{name}' in '{parent}'"
|
||||||
|
def __init__(self, fsobject, parent=None):
|
||||||
|
message = self.cls_message
|
||||||
|
if isinstance(fsobject, basestring):
|
||||||
|
name = fsobject
|
||||||
|
elif isinstance(fsobject, File):
|
||||||
|
name = fsobject.name
|
||||||
|
else:
|
||||||
|
name = ''
|
||||||
|
parentname = unicode(parent) if parent is not None else ''
|
||||||
|
Exception.__init__(self, message.format(name=name, parent=parentname))
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyExistsError(FSError):
|
||||||
|
"The directory or file name we're trying to add already exists"
|
||||||
|
cls_message = "'{name}' already exists in '{parent}'"
|
||||||
|
|
||||||
|
class InvalidPath(FSError):
|
||||||
|
"The path of self is invalid, and cannot be worked with."
|
||||||
|
cls_message = "'{name}' is invalid."
|
||||||
|
|
||||||
|
class InvalidDestinationError(FSError):
|
||||||
|
"""A copy/move operation has been called, but the destination is invalid."""
|
||||||
|
cls_message = "'{name}' is an invalid destination for this operation."
|
||||||
|
|
||||||
|
class OperationError(FSError):
|
||||||
|
"""A copy/move/delete operation has been called, but the checkup after the
|
||||||
|
operation shows that it didn't work."""
|
||||||
|
cls_message = "Operation on '{name}' failed."
|
||||||
|
|
||||||
|
class File(object):
|
||||||
|
INITIAL_INFO = {
|
||||||
|
'size': 0,
|
||||||
|
'ctime': 0,
|
||||||
|
'mtime': 0,
|
||||||
|
'md5': '',
|
||||||
|
'md5partial': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
#This offset is where we should start reading the file to get a partial md5
|
||||||
|
#For audio file, it should be where audio data starts
|
||||||
|
self._md5partial_offset = 0x4000 #16Kb
|
||||||
|
self._md5partial_size = 0x4000 #16Kb
|
||||||
|
|
||||||
|
def __getattr__(self, attrname):
|
||||||
|
# Only called when attr is not there
|
||||||
|
if attrname in self.INITIAL_INFO:
|
||||||
|
try:
|
||||||
|
self._read_info(attrname)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning("An error '%s' was raised while decoding '%s'", e, repr(self.path))
|
||||||
|
try:
|
||||||
|
return self.__dict__[attrname]
|
||||||
|
except KeyError:
|
||||||
|
return self.INITIAL_INFO[attrname]
|
||||||
|
raise AttributeError()
|
||||||
|
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field in ('size', 'ctime', 'mtime'):
|
||||||
|
stats = io.stat(self.path)
|
||||||
|
self.size = nonone(stats.st_size, 0)
|
||||||
|
self.ctime = nonone(stats.st_ctime, 0)
|
||||||
|
self.mtime = nonone(stats.st_mtime, 0)
|
||||||
|
elif field == 'md5partial':
|
||||||
|
try:
|
||||||
|
fp = io.open(self.path, 'rb')
|
||||||
|
offset = self._md5partial_offset
|
||||||
|
size = self._md5partial_size
|
||||||
|
fp.seek(offset)
|
||||||
|
partialdata = fp.read(size)
|
||||||
|
md5 = hashlib.md5(partialdata)
|
||||||
|
self.md5partial = md5.digest()
|
||||||
|
fp.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
elif field == 'md5':
|
||||||
|
try:
|
||||||
|
fp = io.open(self.path, 'rb')
|
||||||
|
filedata = fp.read()
|
||||||
|
md5 = hashlib.md5(filedata)
|
||||||
|
self.md5 = md5.digest()
|
||||||
|
fp.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _read_all_info(self, attrnames=None):
|
||||||
|
"""Cache all possible info.
|
||||||
|
|
||||||
|
If `attrnames` is not None, caches only attrnames.
|
||||||
|
"""
|
||||||
|
if attrnames is None:
|
||||||
|
attrnames = self.INITIAL_INFO.keys()
|
||||||
|
for attrname in attrnames:
|
||||||
|
if attrname not in self.__dict__:
|
||||||
|
self._read_info(attrname)
|
||||||
|
|
||||||
|
#--- Public
|
||||||
|
@classmethod
|
||||||
|
def can_handle(cls, path):
|
||||||
|
return not io.islink(path) and io.isfile(path)
|
||||||
|
|
||||||
|
def rename(self, newname):
|
||||||
|
if newname == self.name:
|
||||||
|
return
|
||||||
|
destpath = self.path[:-1] + newname
|
||||||
|
if io.exists(destpath):
|
||||||
|
raise AlreadyExistsError(newname, self.path[:-1])
|
||||||
|
try:
|
||||||
|
io.rename(self.path, destpath)
|
||||||
|
except EnvironmentError:
|
||||||
|
raise OperationError(self)
|
||||||
|
if not io.exists(destpath):
|
||||||
|
raise OperationError(self)
|
||||||
|
self.path = destpath
|
||||||
|
|
||||||
|
#--- Properties
|
||||||
|
@property
|
||||||
|
def extension(self):
|
||||||
|
return get_file_ext(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.path[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_file(path, fileclasses=[File]):
|
||||||
|
for fileclass in fileclasses:
|
||||||
|
if fileclass.can_handle(path):
|
||||||
|
return fileclass(path)
|
||||||
|
|
||||||
|
def get_files(path, fileclasses=[File]):
|
||||||
|
assert all(issubclass(fileclass, File) for fileclass in fileclasses)
|
||||||
|
try:
|
||||||
|
paths = [path + name for name in io.listdir(path)]
|
||||||
|
result = []
|
||||||
|
for path in paths:
|
||||||
|
file = get_file(path, fileclasses=fileclasses)
|
||||||
|
if file is not None:
|
||||||
|
result.append(file)
|
||||||
|
return result
|
||||||
|
except EnvironmentError:
|
||||||
|
raise InvalidPath(path)
|
||||||
|
|
||||||
|
def get_all_files(path, fileclasses=[File]):
|
||||||
|
files = get_files(path, fileclasses=fileclasses)
|
||||||
|
filepaths = set(f.path for f in files)
|
||||||
|
subpaths = [path + name for name in io.listdir(path)]
|
||||||
|
# it's possible that a folder (bundle) gets into the file list. in that case, we don't want to recurse into it
|
||||||
|
subfolders = [p for p in subpaths if not io.islink(p) and io.isdir(p) and p not in filepaths]
|
||||||
|
subfiles = flatten(get_all_files(subpath, fileclasses=fileclasses) for subpath in subfolders)
|
||||||
|
return subfiles + files
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
from hsutil import job
|
from hsutil import job, io
|
||||||
from hsutil.misc import dedupe
|
from hsutil.misc import dedupe
|
||||||
from hsutil.str import get_file_ext, rem_file_ext
|
from hsutil.str import get_file_ext, rem_file_ext
|
||||||
|
|
||||||
@@ -32,40 +32,32 @@ class Scanner(object):
|
|||||||
self.ignore_list = IgnoreList()
|
self.ignore_list = IgnoreList()
|
||||||
self.discarded_file_count = 0
|
self.discarded_file_count = 0
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _filter_matches_by_content(matches, partial, j):
|
|
||||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
|
||||||
md5attrname = 'md5partial' if partial else 'md5'
|
|
||||||
md5 = lambda f: getattr(f, md5attrname)
|
|
||||||
for matched_file in j.iter_with_progress(matched_files, 'Analyzed %d/%d matching files'):
|
|
||||||
md5(matched_file)
|
|
||||||
j.set_progress(100, 'Removing false matches')
|
|
||||||
return [m for m in matches if md5(m.first) == md5(m.second)]
|
|
||||||
|
|
||||||
def _getmatches(self, files, j):
|
def _getmatches(self, files, j):
|
||||||
j = j.start_subjob(2)
|
|
||||||
mf = engine.MatchFactory()
|
|
||||||
if self.scan_type != SCAN_TYPE_CONTENT:
|
|
||||||
mf.match_similar_words = self.match_similar_words
|
|
||||||
mf.weight_words = self.word_weighting
|
|
||||||
mf.min_match_percentage = self.min_match_percentage
|
|
||||||
if self.scan_type == SCAN_TYPE_FIELDS_NO_ORDER:
|
|
||||||
self.scan_type = SCAN_TYPE_FIELDS
|
|
||||||
mf.no_field_order = True
|
|
||||||
func = {
|
|
||||||
SCAN_TYPE_FILENAME: lambda f: engine.getwords(rem_file_ext(f.name)),
|
|
||||||
SCAN_TYPE_FIELDS: lambda f: engine.getfields(rem_file_ext(f.name)),
|
|
||||||
SCAN_TYPE_TAG: lambda f: [engine.getwords(unicode(getattr(f, attrname))) for attrname in SCANNABLE_TAGS if attrname in self.scanned_tags],
|
|
||||||
SCAN_TYPE_CONTENT: lambda f: [str(f.size)],
|
|
||||||
SCAN_TYPE_CONTENT_AUDIO: lambda f: [str(f.audiosize)]
|
|
||||||
}[self.scan_type]
|
|
||||||
for f in j.iter_with_progress(files, 'Read metadata of %d/%d files'):
|
|
||||||
if self.size_threshold:
|
|
||||||
f.size # pre-read, makes a smoother progress if read here (especially for bundles)
|
|
||||||
f.words = func(f)
|
|
||||||
if self.size_threshold:
|
if self.size_threshold:
|
||||||
|
j = j.start_subjob([2, 8])
|
||||||
|
for f in j.iter_with_progress(files, 'Read size of %d/%d files'):
|
||||||
|
f.size # pre-read, makes a smoother progress if read here (especially for bundles)
|
||||||
files = [f for f in files if f.size >= self.size_threshold]
|
files = [f for f in files if f.size >= self.size_threshold]
|
||||||
return mf.getmatches(files, j)
|
if self.scan_type in (SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO):
|
||||||
|
sizeattr = 'size' if self.scan_type == SCAN_TYPE_CONTENT else 'audiosize'
|
||||||
|
return engine.getmatches_by_contents(files, sizeattr, partial=self.scan_type==SCAN_TYPE_CONTENT_AUDIO, j=j)
|
||||||
|
else:
|
||||||
|
j = j.start_subjob([2, 8])
|
||||||
|
kw = {}
|
||||||
|
kw['match_similar_words'] = self.match_similar_words
|
||||||
|
kw['weight_words'] = self.word_weighting
|
||||||
|
kw['min_match_percentage'] = self.min_match_percentage
|
||||||
|
if self.scan_type == SCAN_TYPE_FIELDS_NO_ORDER:
|
||||||
|
self.scan_type = SCAN_TYPE_FIELDS
|
||||||
|
kw['no_field_order'] = True
|
||||||
|
func = {
|
||||||
|
SCAN_TYPE_FILENAME: lambda f: engine.getwords(rem_file_ext(f.name)),
|
||||||
|
SCAN_TYPE_FIELDS: lambda f: engine.getfields(rem_file_ext(f.name)),
|
||||||
|
SCAN_TYPE_TAG: lambda f: [engine.getwords(unicode(getattr(f, attrname))) for attrname in SCANNABLE_TAGS if attrname in self.scanned_tags],
|
||||||
|
}[self.scan_type]
|
||||||
|
for f in j.iter_with_progress(files, 'Read metadata of %d/%d files'):
|
||||||
|
f.words = func(f)
|
||||||
|
return engine.getmatches(files, j=j, **kw)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _key_func(dupe):
|
def _key_func(dupe):
|
||||||
@@ -86,27 +78,17 @@ class Scanner(object):
|
|||||||
for f in [f for f in files if not hasattr(f, 'is_ref')]:
|
for f in [f for f in files if not hasattr(f, 'is_ref')]:
|
||||||
f.is_ref = False
|
f.is_ref = False
|
||||||
logging.info('Getting matches')
|
logging.info('Getting matches')
|
||||||
if self.match_factory is None:
|
matches = self._getmatches(files, j)
|
||||||
matches = self._getmatches(files, j)
|
|
||||||
else:
|
|
||||||
matches = self.match_factory.getmatches(files, j)
|
|
||||||
logging.info('Found %d matches' % len(matches))
|
logging.info('Found %d matches' % len(matches))
|
||||||
|
j.set_progress(100, 'Removing false matches')
|
||||||
if not self.mix_file_kind:
|
if not self.mix_file_kind:
|
||||||
j.set_progress(100, 'Removing false matches')
|
|
||||||
matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)]
|
matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)]
|
||||||
|
matches = [m for m in matches if io.exists(m.first.path) and io.exists(m.second.path)]
|
||||||
if self.ignore_list:
|
if self.ignore_list:
|
||||||
j = j.start_subjob(2)
|
j = j.start_subjob(2)
|
||||||
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
||||||
matches = [m for m in iter_matches
|
matches = [m for m in iter_matches
|
||||||
if not self.ignore_list.AreIgnored(unicode(m.first.path), unicode(m.second.path))]
|
if not self.ignore_list.AreIgnored(unicode(m.first.path), unicode(m.second.path))]
|
||||||
if self.scan_type in (SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO):
|
|
||||||
j = j.start_subjob(3 if self.scan_type == SCAN_TYPE_CONTENT else 2)
|
|
||||||
matches = self._filter_matches_by_content(matches, partial=True, j=j)
|
|
||||||
if self.scan_type == SCAN_TYPE_CONTENT:
|
|
||||||
matches = self._filter_matches_by_content(matches, partial=False, j=j)
|
|
||||||
# We compared md5. No words were involved.
|
|
||||||
for m in matches:
|
|
||||||
m.first.words = m.second.words = ['--']
|
|
||||||
logging.info('Grouping matches')
|
logging.info('Grouping matches')
|
||||||
groups = engine.get_groups(matches, j)
|
groups = engine.get_groups(matches, j)
|
||||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
||||||
@@ -118,7 +100,6 @@ class Scanner(object):
|
|||||||
g.prioritize(self._key_func, self._tie_breaker)
|
g.prioritize(self._key_func, self._tie_breaker)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
match_factory = None
|
|
||||||
match_similar_words = False
|
match_similar_words = False
|
||||||
min_match_percentage = 80
|
min_match_percentage = 80
|
||||||
mix_file_kind = True
|
mix_file_kind = True
|
||||||
@@ -126,9 +107,3 @@ class Scanner(object):
|
|||||||
scanned_tags = set(['artist', 'title'])
|
scanned_tags = set(['artist', 'title'])
|
||||||
size_threshold = 0
|
size_threshold = 0
|
||||||
word_weighting = False
|
word_weighting = False
|
||||||
|
|
||||||
class ScannerME(Scanner): # Scanner for Music Edition
|
|
||||||
@staticmethod
|
|
||||||
def _key_func(dupe):
|
|
||||||
return (not dupe.is_ref, -dupe.bitrate, -dupe.size)
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ from hsutil.path import Path
|
|||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
from hsutil.decorators import log_calls
|
from hsutil.decorators import log_calls
|
||||||
from hsutil import io
|
from hsutil import io
|
||||||
import hsfs.phys
|
|
||||||
|
|
||||||
|
from . import data
|
||||||
from .results_test import GetTestGroups
|
from .results_test import GetTestGroups
|
||||||
from .. import engine, data
|
from .. import engine, fs
|
||||||
try:
|
try:
|
||||||
from ..app_cocoa import DupeGuru as DupeGuruBase
|
from ..app_cocoa import DupeGuru as DupeGuruBase
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -35,7 +35,6 @@ class DupeGuru(DupeGuruBase):
|
|||||||
def _start_job(self, jobid, func):
|
def _start_job(self, jobid, func):
|
||||||
func(nulljob)
|
func(nulljob)
|
||||||
|
|
||||||
|
|
||||||
def r2np(rows):
|
def r2np(rows):
|
||||||
#Transforms a list of rows [1,2,3] into a list of node paths [[1],[2],[3]]
|
#Transforms a list of rows [1,2,3] into a list of node paths [[1],[2],[3]]
|
||||||
return [[i] for i in rows]
|
return [[i] for i in rows]
|
||||||
@@ -310,15 +309,15 @@ class TCDupeGuru(TestCase):
|
|||||||
|
|
||||||
class TCDupeGuru_renameSelected(TestCase):
|
class TCDupeGuru_renameSelected(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
p = Path(tempfile.mkdtemp())
|
p = self.tmppath()
|
||||||
fp = open(str(p + 'foo bar 1'),mode='w')
|
fp = open(unicode(p + 'foo bar 1'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open(str(p + 'foo bar 2'),mode='w')
|
fp = open(unicode(p + 'foo bar 2'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open(str(p + 'foo bar 3'),mode='w')
|
fp = open(unicode(p + 'foo bar 3'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
refdir = hsfs.phys.Directory(None,str(p))
|
files = fs.get_files(p)
|
||||||
matches = engine.MatchFactory().getmatches(refdir.files)
|
matches = engine.getmatches(files)
|
||||||
groups = engine.get_groups(matches)
|
groups = engine.get_groups(matches)
|
||||||
g = groups[0]
|
g = groups[0]
|
||||||
g.prioritize(lambda x:x.name)
|
g.prioritize(lambda x:x.name)
|
||||||
@@ -327,45 +326,41 @@ class TCDupeGuru_renameSelected(TestCase):
|
|||||||
self.app = app
|
self.app = app
|
||||||
self.groups = groups
|
self.groups = groups
|
||||||
self.p = p
|
self.p = p
|
||||||
self.refdir = refdir
|
self.files = files
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
shutil.rmtree(str(self.p))
|
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
refdir = self.refdir
|
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0]))
|
app.SelectPowerMarkerNodePaths(r2np([0]))
|
||||||
self.assert_(app.RenameSelected('renamed'))
|
assert app.RenameSelected('renamed')
|
||||||
self.assert_('renamed' in refdir)
|
names = io.listdir(self.p)
|
||||||
self.assert_('foo bar 2' not in refdir)
|
assert 'renamed' in names
|
||||||
self.assert_(g.dupes[0] is refdir['renamed'])
|
assert 'foo bar 2' not in names
|
||||||
self.assert_(g.dupes[0] in refdir)
|
eq_(g.dupes[0].name, 'renamed')
|
||||||
|
|
||||||
def test_none_selected(self):
|
def test_none_selected(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
refdir = self.refdir
|
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
app.SelectPowerMarkerNodePaths([])
|
app.SelectPowerMarkerNodePaths([])
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||||
self.assert_(not app.RenameSelected('renamed'))
|
assert not app.RenameSelected('renamed')
|
||||||
msg = logging.warning.calls[0]['msg']
|
msg = logging.warning.calls[0]['msg']
|
||||||
self.assertEqual('dupeGuru Warning: list index out of range', msg)
|
eq_('dupeGuru Warning: list index out of range', msg)
|
||||||
self.assert_('renamed' not in refdir)
|
names = io.listdir(self.p)
|
||||||
self.assert_('foo bar 2' in refdir)
|
assert 'renamed' not in names
|
||||||
self.assert_(g.dupes[0] is refdir['foo bar 2'])
|
assert 'foo bar 2' in names
|
||||||
|
eq_(g.dupes[0].name, 'foo bar 2')
|
||||||
|
|
||||||
def test_name_already_exists(self):
|
def test_name_already_exists(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
refdir = self.refdir
|
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0]))
|
app.SelectPowerMarkerNodePaths(r2np([0]))
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||||
self.assert_(not app.RenameSelected('foo bar 1'))
|
assert not app.RenameSelected('foo bar 1')
|
||||||
msg = logging.warning.calls[0]['msg']
|
msg = logging.warning.calls[0]['msg']
|
||||||
self.assert_(msg.startswith('dupeGuru Warning: \'foo bar 2\' already exists in'))
|
assert msg.startswith('dupeGuru Warning: \'foo bar 1\' already exists in')
|
||||||
self.assert_('foo bar 1' in refdir)
|
names = io.listdir(self.p)
|
||||||
self.assert_('foo bar 2' in refdir)
|
assert 'foo bar 1' in names
|
||||||
self.assert_(g.dupes[0] is refdir['foo bar 2'])
|
assert 'foo bar 2' in names
|
||||||
|
eq_(g.dupes[0].name, 'foo bar 2')
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ from hsutil.testcase import TestCase
|
|||||||
from hsutil import io
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.decorators import log_calls
|
from hsutil.decorators import log_calls
|
||||||
import hsfs as fs
|
|
||||||
import hsfs.phys
|
|
||||||
import hsutil.files
|
import hsutil.files
|
||||||
from hsutil.job import nulljob
|
from hsutil.job import nulljob
|
||||||
|
|
||||||
from .. import data, app
|
from . import data
|
||||||
|
from .. import app, fs
|
||||||
from ..app import DupeGuru as DupeGuruBase
|
from ..app import DupeGuru as DupeGuruBase
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
class DupeGuru(DupeGuruBase):
|
||||||
@@ -59,27 +58,27 @@ class TCDupeGuru(TestCase):
|
|||||||
# The goal here is just to have a test for a previous blowup I had. I know my test coverage
|
# The goal here is just to have a test for a previous blowup I had. I know my test coverage
|
||||||
# for this unit is pathetic. What's done is done. My approach now is to add tests for
|
# for this unit is pathetic. What's done is done. My approach now is to add tests for
|
||||||
# every change I want to make. The blowup was caused by a missing import.
|
# every change I want to make. The blowup was caused by a missing import.
|
||||||
dupe_parent = fs.Directory(None, 'foo')
|
p = self.tmppath()
|
||||||
dupe = fs.File(dupe_parent, 'bar')
|
io.open(p + 'foo', 'w').close()
|
||||||
dupe.copy = log_calls(lambda dest, newname: None)
|
|
||||||
self.mock(hsutil.files, 'copy', log_calls(lambda source_path, dest_path: None))
|
self.mock(hsutil.files, 'copy', log_calls(lambda source_path, dest_path: None))
|
||||||
self.mock(os, 'makedirs', lambda path: None) # We don't want the test to create that fake directory
|
self.mock(os, 'makedirs', lambda path: None) # We don't want the test to create that fake directory
|
||||||
self.mock(fs.phys, 'Directory', fs.Directory) # We don't want an error because makedirs didn't work
|
|
||||||
app = DupeGuru()
|
app = DupeGuru()
|
||||||
app.copy_or_move(dupe, True, 'some_destination', 0)
|
app.directories.add_path(p)
|
||||||
|
[f] = app.directories.get_files()
|
||||||
|
app.copy_or_move(f, True, 'some_destination', 0)
|
||||||
self.assertEqual(1, len(hsutil.files.copy.calls))
|
self.assertEqual(1, len(hsutil.files.copy.calls))
|
||||||
call = hsutil.files.copy.calls[0]
|
call = hsutil.files.copy.calls[0]
|
||||||
self.assertEqual('some_destination', call['dest_path'])
|
self.assertEqual('some_destination', call['dest_path'])
|
||||||
self.assertEqual(dupe.path, call['source_path'])
|
self.assertEqual(f.path, call['source_path'])
|
||||||
|
|
||||||
def test_copy_or_move_clean_empty_dirs(self):
|
def test_copy_or_move_clean_empty_dirs(self):
|
||||||
tmppath = Path(self.tmpdir())
|
tmppath = Path(self.tmpdir())
|
||||||
sourcepath = tmppath + 'source'
|
sourcepath = tmppath + 'source'
|
||||||
io.mkdir(sourcepath)
|
io.mkdir(sourcepath)
|
||||||
io.open(sourcepath + 'myfile', 'w')
|
io.open(sourcepath + 'myfile', 'w')
|
||||||
tmpdir = hsfs.phys.Directory(None, unicode(tmppath))
|
|
||||||
myfile = tmpdir['source']['myfile']
|
|
||||||
app = DupeGuru()
|
app = DupeGuru()
|
||||||
|
app.directories.add_path(tmppath)
|
||||||
|
[myfile] = app.directories.get_files()
|
||||||
self.mock(app, 'clean_empty_dirs', log_calls(lambda path: None))
|
self.mock(app, 'clean_empty_dirs', log_calls(lambda path: None))
|
||||||
app.copy_or_move(myfile, False, tmppath + 'dest', 0)
|
app.copy_or_move(myfile, False, tmppath + 'dest', 0)
|
||||||
calls = app.clean_empty_dirs.calls
|
calls = app.clean_empty_dirs.calls
|
||||||
@@ -87,9 +86,14 @@ class TCDupeGuru(TestCase):
|
|||||||
self.assertEqual(sourcepath, calls[0]['path'])
|
self.assertEqual(sourcepath, calls[0]['path'])
|
||||||
|
|
||||||
def test_Scan_with_objects_evaluating_to_false(self):
|
def test_Scan_with_objects_evaluating_to_false(self):
|
||||||
|
class FakeFile(fs.File):
|
||||||
|
def __nonzero__(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# At some point, any() was used in a wrong way that made Scan() wrongly return 1
|
# At some point, any() was used in a wrong way that made Scan() wrongly return 1
|
||||||
app = DupeGuru()
|
app = DupeGuru()
|
||||||
f1, f2 = [fs.File(None, 'foo') for i in range(2)]
|
f1, f2 = [FakeFile('foo') for i in range(2)]
|
||||||
f1.is_ref, f2.is_ref = (False, False)
|
f1.is_ref, f2.is_ref = (False, False)
|
||||||
assert not (bool(f1) and bool(f2))
|
assert not (bool(f1) and bool(f2))
|
||||||
app.directories.get_files = lambda: [f1, f2]
|
app.directories.get_files = lambda: [f1, f2]
|
||||||
|
|||||||
45
base/py/tests/data.py
Normal file
45
base/py/tests/data.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-10-23
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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
|
||||||
|
|
||||||
|
# data module for tests
|
||||||
|
|
||||||
|
from hsutil.str import format_size
|
||||||
|
from dupeguru.data import format_path, cmp_value
|
||||||
|
|
||||||
|
COLUMNS = [
|
||||||
|
{'attr':'name','display':'Filename'},
|
||||||
|
{'attr':'path','display':'Directory'},
|
||||||
|
{'attr':'size','display':'Size (KB)'},
|
||||||
|
{'attr':'extension','display':'Kind'},
|
||||||
|
]
|
||||||
|
|
||||||
|
METADATA_TO_READ = ['size']
|
||||||
|
|
||||||
|
def GetDisplayInfo(dupe, group, delta):
|
||||||
|
size = dupe.size
|
||||||
|
m = group.get_match_of(dupe)
|
||||||
|
if m and delta:
|
||||||
|
r = group.ref
|
||||||
|
size -= r.size
|
||||||
|
return [
|
||||||
|
dupe.name,
|
||||||
|
format_path(dupe.path),
|
||||||
|
format_size(size, 0, 1, False),
|
||||||
|
dupe.extension,
|
||||||
|
]
|
||||||
|
|
||||||
|
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']))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def GetGroupSortKey(group, key):
|
||||||
|
return cmp_value(getattr(group.ref, COLUMNS[key]['attr']))
|
||||||
@@ -10,20 +10,43 @@
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import shutil
|
|
||||||
|
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
from hsutil import job, io
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
import hsfs.phys
|
|
||||||
from hsfs.tests import phys_test
|
|
||||||
|
|
||||||
from ..directories import *
|
from ..directories import *
|
||||||
|
|
||||||
testpath = Path(TestCase.datadirpath())
|
testpath = Path(TestCase.datadirpath())
|
||||||
|
|
||||||
|
def create_fake_fs(rootpath):
|
||||||
|
rootpath = rootpath + 'fs'
|
||||||
|
io.mkdir(rootpath)
|
||||||
|
io.mkdir(rootpath + 'dir1')
|
||||||
|
io.mkdir(rootpath + 'dir2')
|
||||||
|
io.mkdir(rootpath + 'dir3')
|
||||||
|
fp = io.open(rootpath + 'file1.test', 'w')
|
||||||
|
fp.write('1')
|
||||||
|
fp.close()
|
||||||
|
fp = io.open(rootpath + 'file2.test', 'w')
|
||||||
|
fp.write('12')
|
||||||
|
fp.close()
|
||||||
|
fp = io.open(rootpath + 'file3.test', 'w')
|
||||||
|
fp.write('123')
|
||||||
|
fp.close()
|
||||||
|
fp = io.open(rootpath + ('dir1', 'file1.test'), 'w')
|
||||||
|
fp.write('1')
|
||||||
|
fp.close()
|
||||||
|
fp = io.open(rootpath + ('dir2', 'file2.test'), 'w')
|
||||||
|
fp.write('12')
|
||||||
|
fp.close()
|
||||||
|
fp = io.open(rootpath + ('dir3', 'file3.test'), 'w')
|
||||||
|
fp.write('123')
|
||||||
|
fp.close()
|
||||||
|
return rootpath
|
||||||
|
|
||||||
class TCDirectories(TestCase):
|
class TCDirectories(TestCase):
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
@@ -33,13 +56,11 @@ class TCDirectories(TestCase):
|
|||||||
def test_add_path(self):
|
def test_add_path(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p = testpath + 'utils'
|
p = testpath + 'utils'
|
||||||
added = d.add_path(p)
|
d.add_path(p)
|
||||||
self.assertEqual(1,len(d))
|
self.assertEqual(1,len(d))
|
||||||
self.assert_(p in d)
|
self.assert_(p in d)
|
||||||
self.assert_((p + 'foobar') in d)
|
self.assert_((p + 'foobar') in d)
|
||||||
self.assert_(p[:-1] not in d)
|
self.assert_(p[:-1] not in d)
|
||||||
self.assertEqual(p,added.path)
|
|
||||||
self.assert_(d[0] is added)
|
|
||||||
p = self.tmppath()
|
p = self.tmppath()
|
||||||
d.add_path(p)
|
d.add_path(p)
|
||||||
self.assertEqual(2,len(d))
|
self.assertEqual(2,len(d))
|
||||||
@@ -53,13 +74,13 @@ class TCDirectories(TestCase):
|
|||||||
self.assertRaises(AlreadyThereError, d.add_path, p + 'foobar')
|
self.assertRaises(AlreadyThereError, d.add_path, p + 'foobar')
|
||||||
self.assertEqual(1, len(d))
|
self.assertEqual(1, len(d))
|
||||||
|
|
||||||
def test_AddPath_containing_paths_already_there(self):
|
def test_add_path_containing_paths_already_there(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
d.add_path(testpath + 'utils')
|
d.add_path(testpath + 'utils')
|
||||||
self.assertEqual(1, len(d))
|
self.assertEqual(1, len(d))
|
||||||
added = d.add_path(testpath)
|
d.add_path(testpath)
|
||||||
self.assertEqual(1, len(d))
|
eq_(len(d), 1)
|
||||||
self.assert_(added is d[0])
|
eq_(d[0], testpath)
|
||||||
|
|
||||||
def test_AddPath_non_latin(self):
|
def test_AddPath_non_latin(self):
|
||||||
p = Path(self.tmpdir())
|
p = Path(self.tmpdir())
|
||||||
@@ -114,7 +135,7 @@ class TCDirectories(TestCase):
|
|||||||
|
|
||||||
def test_set_state_keep_state_dict_size_to_minimum(self):
|
def test_set_state_keep_state_dict_size_to_minimum(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p = Path(phys_test.create_fake_fs(self.tmpdir()))
|
p = create_fake_fs(self.tmppath())
|
||||||
d.add_path(p)
|
d.add_path(p)
|
||||||
d.set_state(p,STATE_REFERENCE)
|
d.set_state(p,STATE_REFERENCE)
|
||||||
d.set_state(p + 'dir1',STATE_REFERENCE)
|
d.set_state(p + 'dir1',STATE_REFERENCE)
|
||||||
@@ -129,17 +150,17 @@ class TCDirectories(TestCase):
|
|||||||
|
|
||||||
def test_get_files(self):
|
def test_get_files(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p = Path(phys_test.create_fake_fs(self.tmpdir()))
|
p = create_fake_fs(self.tmppath())
|
||||||
d.add_path(p)
|
d.add_path(p)
|
||||||
d.set_state(p + 'dir1',STATE_REFERENCE)
|
d.set_state(p + 'dir1',STATE_REFERENCE)
|
||||||
d.set_state(p + 'dir2',STATE_EXCLUDED)
|
d.set_state(p + 'dir2',STATE_EXCLUDED)
|
||||||
files = d.get_files()
|
files = list(d.get_files())
|
||||||
self.assertEqual(5, len(list(files)))
|
self.assertEqual(5, len(files))
|
||||||
for f in files:
|
for f in files:
|
||||||
if f.parent.path == p + 'dir1':
|
if f.path[:-1] == p + 'dir1':
|
||||||
self.assert_(f.is_ref)
|
assert f.is_ref
|
||||||
else:
|
else:
|
||||||
self.assert_(not f.is_ref)
|
assert not f.is_ref
|
||||||
|
|
||||||
def test_get_files_with_inherited_exclusion(self):
|
def test_get_files_with_inherited_exclusion(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
@@ -177,52 +198,28 @@ class TCDirectories(TestCase):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
def test_default_dirclass(self):
|
|
||||||
self.assert_(Directories().dirclass is hsfs.phys.Directory)
|
|
||||||
|
|
||||||
def test_dirclass(self):
|
|
||||||
class MySpecialDirclass(hsfs.phys.Directory): pass
|
|
||||||
d = Directories()
|
|
||||||
d.dirclass = MySpecialDirclass
|
|
||||||
d.add_path(testpath)
|
|
||||||
self.assert_(isinstance(d[0], MySpecialDirclass))
|
|
||||||
|
|
||||||
def test_load_from_file_with_invalid_path(self):
|
def test_load_from_file_with_invalid_path(self):
|
||||||
#This test simulates a load from file resulting in a
|
#This test simulates a load from file resulting in a
|
||||||
#InvalidPath raise. Other directories must be loaded.
|
#InvalidPath raise. Other directories must be loaded.
|
||||||
d1 = Directories()
|
d1 = Directories()
|
||||||
d1.add_path(testpath + 'utils')
|
d1.add_path(testpath + 'utils')
|
||||||
#Will raise InvalidPath upon loading
|
#Will raise InvalidPath upon loading
|
||||||
d1.add_path(self.tmppath()).name = 'does_not_exist'
|
p = self.tmppath()
|
||||||
|
d1.add_path(p)
|
||||||
|
io.rmdir(p)
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
||||||
d1.save_to_file(tmpxml)
|
d1.save_to_file(tmpxml)
|
||||||
d2 = Directories()
|
d2 = Directories()
|
||||||
d2.load_from_file(tmpxml)
|
d2.load_from_file(tmpxml)
|
||||||
self.assertEqual(1, len(d2))
|
self.assertEqual(1, len(d2))
|
||||||
|
|
||||||
def test_load_from_file_with_same_paths(self):
|
|
||||||
#This test simulates a load from file resulting in a
|
|
||||||
#AlreadyExists raise. Other directories must be loaded.
|
|
||||||
d1 = Directories()
|
|
||||||
p1 = self.tmppath()
|
|
||||||
p2 = self.tmppath()
|
|
||||||
d1.add_path(p1)
|
|
||||||
d1.add_path(p2)
|
|
||||||
#Will raise AlreadyExists upon loading
|
|
||||||
d1.add_path(self.tmppath()).name = unicode(p1)
|
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
|
||||||
d1.save_to_file(tmpxml)
|
|
||||||
d2 = Directories()
|
|
||||||
d2.load_from_file(tmpxml)
|
|
||||||
self.assertEqual(2, len(d2))
|
|
||||||
|
|
||||||
def test_unicode_save(self):
|
def test_unicode_save(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p1 = self.tmppath() + u'hello\xe9'
|
p1 = self.tmppath() + u'hello\xe9'
|
||||||
io.mkdir(p1)
|
io.mkdir(p1)
|
||||||
io.mkdir(p1 + u'foo\xe9')
|
io.mkdir(p1 + u'foo\xe9')
|
||||||
d.add_path(p1)
|
d.add_path(p1)
|
||||||
d.set_state(d[0][0].path, STATE_EXCLUDED)
|
d.set_state(p1 + u'foo\xe9', STATE_EXCLUDED)
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
||||||
try:
|
try:
|
||||||
d.save_to_file(tmpxml)
|
d.save_to_file(tmpxml)
|
||||||
@@ -231,7 +228,7 @@ class TCDirectories(TestCase):
|
|||||||
|
|
||||||
def test_get_files_refreshes_its_directories(self):
|
def test_get_files_refreshes_its_directories(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p = Path(phys_test.create_fake_fs(self.tmpdir()))
|
p = create_fake_fs(self.tmppath())
|
||||||
d.add_path(p)
|
d.add_path(p)
|
||||||
files = d.get_files()
|
files = d.get_files()
|
||||||
self.assertEqual(6, len(list(files)))
|
self.assertEqual(6, len(list(files)))
|
||||||
@@ -258,16 +255,6 @@ class TCDirectories(TestCase):
|
|||||||
d.set_state(hidden_dir_path, STATE_NORMAL)
|
d.set_state(hidden_dir_path, STATE_NORMAL)
|
||||||
self.assertEqual(d.get_state(hidden_dir_path), STATE_NORMAL)
|
self.assertEqual(d.get_state(hidden_dir_path), STATE_NORMAL)
|
||||||
|
|
||||||
def test_special_dirclasses(self):
|
|
||||||
# if a path is in special_dirclasses, use this class instead
|
|
||||||
class MySpecialDirclass(hsfs.phys.Directory): pass
|
|
||||||
d = Directories()
|
|
||||||
p1 = self.tmppath()
|
|
||||||
p2 = self.tmppath()
|
|
||||||
d.special_dirclasses[p1] = MySpecialDirclass
|
|
||||||
self.assert_(isinstance(d.add_path(p2), hsfs.phys.Directory))
|
|
||||||
self.assert_(isinstance(d.add_path(p1), MySpecialDirclass))
|
|
||||||
|
|
||||||
def test_default_path_state_override(self):
|
def test_default_path_state_override(self):
|
||||||
# It's possible for a subclass to override the default state of a path
|
# It's possible for a subclass to override the default state of a path
|
||||||
class MyDirectories(Directories):
|
class MyDirectories(Directories):
|
||||||
|
|||||||
@@ -15,16 +15,21 @@ from hsutil import job
|
|||||||
from hsutil.decorators import log_calls
|
from hsutil.decorators import log_calls
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
from .. import engine
|
from .. import engine, fs
|
||||||
from ..engine import *
|
from ..engine import *
|
||||||
|
|
||||||
class NamedObject(object):
|
class NamedObject(object):
|
||||||
def __init__(self, name="foobar", with_words=False):
|
def __init__(self, name="foobar", with_words=False, size=1):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.size = size
|
||||||
|
self.md5partial = name
|
||||||
|
self.md5 = name
|
||||||
if with_words:
|
if with_words:
|
||||||
self.words = getwords(name)
|
self.words = getwords(name)
|
||||||
|
|
||||||
|
|
||||||
|
no = NamedObject
|
||||||
|
|
||||||
def get_match_triangle():
|
def get_match_triangle():
|
||||||
o1 = NamedObject(with_words=True)
|
o1 = NamedObject(with_words=True)
|
||||||
o2 = NamedObject(with_words=True)
|
o2 = NamedObject(with_words=True)
|
||||||
@@ -340,21 +345,13 @@ class TCget_match(TestCase):
|
|||||||
self.assertEqual(int((6.0 / 13.0) * 100),get_match(NamedObject("foo bar",True),NamedObject("bar bleh",True),(WEIGHT_WORDS,)).percentage)
|
self.assertEqual(int((6.0 / 13.0) * 100),get_match(NamedObject("foo bar",True),NamedObject("bar bleh",True),(WEIGHT_WORDS,)).percentage)
|
||||||
|
|
||||||
|
|
||||||
class TCMatchFactory(TestCase):
|
class GetMatches(TestCase):
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
self.assertEqual([],MatchFactory().getmatches([]))
|
eq_(getmatches([]), [])
|
||||||
|
|
||||||
def test_defaults(self):
|
|
||||||
mf = MatchFactory()
|
|
||||||
self.assertEqual(50,mf.common_word_threshold)
|
|
||||||
self.assertEqual(False,mf.weight_words)
|
|
||||||
self.assertEqual(False,mf.match_similar_words)
|
|
||||||
self.assertEqual(False,mf.no_field_order)
|
|
||||||
self.assertEqual(0,mf.min_match_percentage)
|
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
||||||
r = MatchFactory().getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(2,len(r))
|
self.assertEqual(2,len(r))
|
||||||
seek = [m for m in r if m.percentage == 50] #"foo bar" and "bar bleh"
|
seek = [m for m in r if m.percentage == 50] #"foo bar" and "bar bleh"
|
||||||
m = seek[0]
|
m = seek[0]
|
||||||
@@ -367,7 +364,7 @@ class TCMatchFactory(TestCase):
|
|||||||
|
|
||||||
def test_null_and_unrelated_objects(self):
|
def test_null_and_unrelated_objects(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject(""),NamedObject("unrelated object")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject(""),NamedObject("unrelated object")]
|
||||||
r = MatchFactory().getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
m = r[0]
|
m = r[0]
|
||||||
self.assertEqual(50,m.percentage)
|
self.assertEqual(50,m.percentage)
|
||||||
@@ -376,34 +373,33 @@ class TCMatchFactory(TestCase):
|
|||||||
|
|
||||||
def test_twice_the_same_word(self):
|
def test_twice_the_same_word(self):
|
||||||
l = [NamedObject("foo foo bar"),NamedObject("bar bleh")]
|
l = [NamedObject("foo foo bar"),NamedObject("bar bleh")]
|
||||||
r = MatchFactory().getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
|
|
||||||
def test_twice_the_same_word_when_preworded(self):
|
def test_twice_the_same_word_when_preworded(self):
|
||||||
l = [NamedObject("foo foo bar",True),NamedObject("bar bleh",True)]
|
l = [NamedObject("foo foo bar",True),NamedObject("bar bleh",True)]
|
||||||
r = MatchFactory().getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
|
|
||||||
def test_two_words_match(self):
|
def test_two_words_match(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("foo bar bleh")]
|
l = [NamedObject("foo bar"),NamedObject("foo bar bleh")]
|
||||||
r = MatchFactory().getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
|
|
||||||
def test_match_files_with_only_common_words(self):
|
def test_match_files_with_only_common_words(self):
|
||||||
#If a word occurs more than 50 times, it is excluded from the matching process
|
#If a word occurs more than 50 times, it is excluded from the matching process
|
||||||
#The problem with the common_word_threshold is that the files containing only common
|
#The problem with the common_word_threshold is that the files containing only common
|
||||||
#words will never be matched together. We *should* match them.
|
#words will never be matched together. We *should* match them.
|
||||||
mf = MatchFactory()
|
# This test assumes that the common word threashold const is 50
|
||||||
mf.common_word_threshold = 50
|
|
||||||
l = [NamedObject("foo") for i in range(50)]
|
l = [NamedObject("foo") for i in range(50)]
|
||||||
r = mf.getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1225,len(r))
|
self.assertEqual(1225,len(r))
|
||||||
|
|
||||||
def test_use_words_already_there_if_there(self):
|
def test_use_words_already_there_if_there(self):
|
||||||
o1 = NamedObject('foo')
|
o1 = NamedObject('foo')
|
||||||
o2 = NamedObject('bar')
|
o2 = NamedObject('bar')
|
||||||
o2.words = ['foo']
|
o2.words = ['foo']
|
||||||
self.assertEqual(1,len(MatchFactory().getmatches([o1,o2])))
|
eq_(1, len(getmatches([o1,o2])))
|
||||||
|
|
||||||
def test_job(self):
|
def test_job(self):
|
||||||
def do_progress(p,d=''):
|
def do_progress(p,d=''):
|
||||||
@@ -413,75 +409,62 @@ class TCMatchFactory(TestCase):
|
|||||||
j = job.Job(1,do_progress)
|
j = job.Job(1,do_progress)
|
||||||
self.log = []
|
self.log = []
|
||||||
s = "foo bar"
|
s = "foo bar"
|
||||||
MatchFactory().getmatches([NamedObject(s),NamedObject(s),NamedObject(s)],j)
|
getmatches([NamedObject(s), NamedObject(s), NamedObject(s)], j=j)
|
||||||
self.assert_(len(self.log) > 2)
|
self.assert_(len(self.log) > 2)
|
||||||
self.assertEqual(0,self.log[0])
|
self.assertEqual(0,self.log[0])
|
||||||
self.assertEqual(100,self.log[-1])
|
self.assertEqual(100,self.log[-1])
|
||||||
|
|
||||||
def test_weight_words(self):
|
def test_weight_words(self):
|
||||||
mf = MatchFactory()
|
|
||||||
mf.weight_words = True
|
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh")]
|
||||||
m = mf.getmatches(l)[0]
|
m = getmatches(l, weight_words=True)[0]
|
||||||
self.assertEqual(int((6.0 / 13.0) * 100),m.percentage)
|
self.assertEqual(int((6.0 / 13.0) * 100),m.percentage)
|
||||||
|
|
||||||
def test_similar_word(self):
|
def test_similar_word(self):
|
||||||
mf = MatchFactory()
|
|
||||||
mf.match_similar_words = True
|
|
||||||
l = [NamedObject("foobar"),NamedObject("foobars")]
|
l = [NamedObject("foobar"),NamedObject("foobars")]
|
||||||
self.assertEqual(1,len(mf.getmatches(l)))
|
eq_(len(getmatches(l, match_similar_words=True)), 1)
|
||||||
self.assertEqual(100,mf.getmatches(l)[0].percentage)
|
eq_(getmatches(l, match_similar_words=True)[0].percentage, 100)
|
||||||
l = [NamedObject("foobar"),NamedObject("foo")]
|
l = [NamedObject("foobar"),NamedObject("foo")]
|
||||||
self.assertEqual(0,len(mf.getmatches(l))) #too far
|
eq_(len(getmatches(l, match_similar_words=True)), 0) #too far
|
||||||
l = [NamedObject("bizkit"),NamedObject("bizket")]
|
l = [NamedObject("bizkit"),NamedObject("bizket")]
|
||||||
self.assertEqual(1,len(mf.getmatches(l)))
|
eq_(len(getmatches(l, match_similar_words=True)), 1)
|
||||||
l = [NamedObject("foobar"),NamedObject("foosbar")]
|
l = [NamedObject("foobar"),NamedObject("foosbar")]
|
||||||
self.assertEqual(1,len(mf.getmatches(l)))
|
eq_(len(getmatches(l, match_similar_words=True)), 1)
|
||||||
|
|
||||||
def test_single_object_with_similar_words(self):
|
def test_single_object_with_similar_words(self):
|
||||||
mf = MatchFactory()
|
|
||||||
mf.match_similar_words = True
|
|
||||||
l = [NamedObject("foo foos")]
|
l = [NamedObject("foo foos")]
|
||||||
self.assertEqual(0,len(mf.getmatches(l)))
|
eq_(len(getmatches(l, match_similar_words=True)), 0)
|
||||||
|
|
||||||
def test_double_words_get_counted_only_once(self):
|
def test_double_words_get_counted_only_once(self):
|
||||||
mf = MatchFactory()
|
|
||||||
l = [NamedObject("foo bar foo bleh"),NamedObject("foo bar bleh bar")]
|
l = [NamedObject("foo bar foo bleh"),NamedObject("foo bar bleh bar")]
|
||||||
m = mf.getmatches(l)[0]
|
m = getmatches(l)[0]
|
||||||
self.assertEqual(75,m.percentage)
|
self.assertEqual(75,m.percentage)
|
||||||
|
|
||||||
def test_with_fields(self):
|
def test_with_fields(self):
|
||||||
mf = MatchFactory()
|
|
||||||
o1 = NamedObject("foo bar - foo bleh")
|
o1 = NamedObject("foo bar - foo bleh")
|
||||||
o2 = NamedObject("foo bar - bleh bar")
|
o2 = NamedObject("foo bar - bleh bar")
|
||||||
o1.words = getfields(o1.name)
|
o1.words = getfields(o1.name)
|
||||||
o2.words = getfields(o2.name)
|
o2.words = getfields(o2.name)
|
||||||
m = mf.getmatches([o1, o2])[0]
|
m = getmatches([o1, o2])[0]
|
||||||
self.assertEqual(50, m.percentage)
|
self.assertEqual(50, m.percentage)
|
||||||
|
|
||||||
def test_with_fields_no_order(self):
|
def test_with_fields_no_order(self):
|
||||||
mf = MatchFactory()
|
|
||||||
mf.no_field_order = True
|
|
||||||
o1 = NamedObject("foo bar - foo bleh")
|
o1 = NamedObject("foo bar - foo bleh")
|
||||||
o2 = NamedObject("bleh bang - foo bar")
|
o2 = NamedObject("bleh bang - foo bar")
|
||||||
o1.words = getfields(o1.name)
|
o1.words = getfields(o1.name)
|
||||||
o2.words = getfields(o2.name)
|
o2.words = getfields(o2.name)
|
||||||
m = mf.getmatches([o1, o2])[0]
|
m = getmatches([o1, o2], no_field_order=True)[0]
|
||||||
self.assertEqual(50 ,m.percentage)
|
eq_(m.percentage, 50)
|
||||||
|
|
||||||
def test_only_match_similar_when_the_option_is_set(self):
|
def test_only_match_similar_when_the_option_is_set(self):
|
||||||
mf = MatchFactory()
|
|
||||||
mf.match_similar_words = False
|
|
||||||
l = [NamedObject("foobar"),NamedObject("foobars")]
|
l = [NamedObject("foobar"),NamedObject("foobars")]
|
||||||
self.assertEqual(0,len(mf.getmatches(l)))
|
eq_(len(getmatches(l, match_similar_words=False)), 0)
|
||||||
|
|
||||||
def test_dont_recurse_do_match(self):
|
def test_dont_recurse_do_match(self):
|
||||||
# with nosetests, the stack is increased. The number has to be high enough not to be failing falsely
|
# with nosetests, the stack is increased. The number has to be high enough not to be failing falsely
|
||||||
sys.setrecursionlimit(100)
|
sys.setrecursionlimit(100)
|
||||||
mf = MatchFactory()
|
|
||||||
files = [NamedObject('foo bar') for i in range(101)]
|
files = [NamedObject('foo bar') for i in range(101)]
|
||||||
try:
|
try:
|
||||||
mf.getmatches(files)
|
getmatches(files)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
self.fail()
|
self.fail()
|
||||||
finally:
|
finally:
|
||||||
@@ -489,18 +472,9 @@ class TCMatchFactory(TestCase):
|
|||||||
|
|
||||||
def test_min_match_percentage(self):
|
def test_min_match_percentage(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
||||||
mf = MatchFactory()
|
r = getmatches(l, min_match_percentage=50)
|
||||||
mf.min_match_percentage = 50
|
|
||||||
r = mf.getmatches(l)
|
|
||||||
self.assertEqual(1,len(r)) #Only "foo bar" / "bar bleh" should match
|
self.assertEqual(1,len(r)) #Only "foo bar" / "bar bleh" should match
|
||||||
|
|
||||||
def test_limit(self):
|
|
||||||
l = [NamedObject(),NamedObject(),NamedObject()]
|
|
||||||
mf = MatchFactory()
|
|
||||||
mf.limit = 2
|
|
||||||
r = mf.getmatches(l)
|
|
||||||
self.assertEqual(2,len(r))
|
|
||||||
|
|
||||||
def test_MemoryError(self):
|
def test_MemoryError(self):
|
||||||
@log_calls
|
@log_calls
|
||||||
def mocked_match(first, second, flags):
|
def mocked_match(first, second, flags):
|
||||||
@@ -510,14 +484,19 @@ class TCMatchFactory(TestCase):
|
|||||||
|
|
||||||
objects = [NamedObject() for i in range(10)] # results in 45 matches
|
objects = [NamedObject() for i in range(10)] # results in 45 matches
|
||||||
self.mock(engine, 'get_match', mocked_match)
|
self.mock(engine, 'get_match', mocked_match)
|
||||||
mf = MatchFactory()
|
|
||||||
try:
|
try:
|
||||||
r = mf.getmatches(objects)
|
r = getmatches(objects)
|
||||||
except MemoryError:
|
except MemoryError:
|
||||||
self.fail('MemorryError must be handled')
|
self.fail('MemorryError must be handled')
|
||||||
self.assertEqual(42, len(r))
|
self.assertEqual(42, len(r))
|
||||||
|
|
||||||
|
|
||||||
|
class GetMatchesByContents(TestCase):
|
||||||
|
def test_dont_compare_empty_files(self):
|
||||||
|
o1, o2 = no(size=0), no(size=0)
|
||||||
|
assert not getmatches_by_contents([o1, o2])
|
||||||
|
|
||||||
|
|
||||||
class TCGroup(TestCase):
|
class TCGroup(TestCase):
|
||||||
def test_empy(self):
|
def test_empy(self):
|
||||||
g = Group()
|
g = Group()
|
||||||
@@ -738,7 +717,7 @@ class TCget_groups(TestCase):
|
|||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh")]
|
||||||
matches = MatchFactory().getmatches(l)
|
matches = getmatches(l)
|
||||||
m = matches[0]
|
m = matches[0]
|
||||||
r = get_groups(matches)
|
r = get_groups(matches)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
@@ -749,7 +728,7 @@ class TCget_groups(TestCase):
|
|||||||
def test_group_with_multiple_matches(self):
|
def test_group_with_multiple_matches(self):
|
||||||
#This results in 3 matches
|
#This results in 3 matches
|
||||||
l = [NamedObject("foo"),NamedObject("foo"),NamedObject("foo")]
|
l = [NamedObject("foo"),NamedObject("foo"),NamedObject("foo")]
|
||||||
matches = MatchFactory().getmatches(l)
|
matches = getmatches(l)
|
||||||
r = get_groups(matches)
|
r = get_groups(matches)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
g = r[0]
|
g = r[0]
|
||||||
@@ -759,7 +738,7 @@ class TCget_groups(TestCase):
|
|||||||
l = [NamedObject("a b"),NamedObject("a b"),NamedObject("b c"),NamedObject("c d"),NamedObject("c d")]
|
l = [NamedObject("a b"),NamedObject("a b"),NamedObject("b c"),NamedObject("c d"),NamedObject("c d")]
|
||||||
#There will be 2 groups here: group "a b" and group "c d"
|
#There will be 2 groups here: group "a b" and group "c d"
|
||||||
#"b c" can go either of them, but not both.
|
#"b c" can go either of them, but not both.
|
||||||
matches = MatchFactory().getmatches(l)
|
matches = getmatches(l)
|
||||||
r = get_groups(matches)
|
r = get_groups(matches)
|
||||||
self.assertEqual(2,len(r))
|
self.assertEqual(2,len(r))
|
||||||
self.assertEqual(5,len(r[0])+len(r[1]))
|
self.assertEqual(5,len(r[0])+len(r[1]))
|
||||||
@@ -768,7 +747,7 @@ class TCget_groups(TestCase):
|
|||||||
l = [NamedObject("a b"),NamedObject("a b"),NamedObject("a b"),NamedObject("a b")]
|
l = [NamedObject("a b"),NamedObject("a b"),NamedObject("a b"),NamedObject("a b")]
|
||||||
#There will be 2 groups here: group "a b" and group "c d"
|
#There will be 2 groups here: group "a b" and group "c d"
|
||||||
#"b c" can fit in both, but it must be in only one of them
|
#"b c" can fit in both, but it must be in only one of them
|
||||||
matches = MatchFactory().getmatches(l)
|
matches = getmatches(l)
|
||||||
r = get_groups(matches)
|
r = get_groups(matches)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
|
|
||||||
@@ -788,7 +767,7 @@ class TCget_groups(TestCase):
|
|||||||
|
|
||||||
def test_four_sized_group(self):
|
def test_four_sized_group(self):
|
||||||
l = [NamedObject("foobar") for i in xrange(4)]
|
l = [NamedObject("foobar") for i in xrange(4)]
|
||||||
m = MatchFactory().getmatches(l)
|
m = getmatches(l)
|
||||||
r = get_groups(m)
|
r = get_groups(m)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
self.assertEqual(4,len(r[0]))
|
self.assertEqual(4,len(r[0]))
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ from hsutil.path import Path
|
|||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
from hsutil.misc import first
|
from hsutil.misc import first
|
||||||
|
|
||||||
from . import engine_test
|
from . import engine_test, data
|
||||||
from .. import data, engine
|
from .. import engine
|
||||||
from ..results import *
|
from ..results import *
|
||||||
|
|
||||||
class NamedObject(engine_test.NamedObject):
|
class NamedObject(engine_test.NamedObject):
|
||||||
size = 1
|
|
||||||
path = property(lambda x:Path('basepath') + x.name)
|
path = property(lambda x:Path('basepath') + x.name)
|
||||||
is_ref = False
|
is_ref = False
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ class NamedObject(engine_test.NamedObject):
|
|||||||
def GetTestGroups():
|
def GetTestGroups():
|
||||||
objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")]
|
objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")]
|
||||||
objects[1].size = 1024
|
objects[1].size = 1024
|
||||||
matches = engine.MatchFactory().getmatches(objects) #we should have 5 matches
|
matches = engine.getmatches(objects) #we should have 5 matches
|
||||||
groups = engine.get_groups(matches) #We should have 2 groups
|
groups = engine.get_groups(matches) #We should have 2 groups
|
||||||
for g in groups:
|
for g in groups:
|
||||||
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
||||||
@@ -505,7 +504,7 @@ class TCResultsXML(TestCase):
|
|||||||
return objects[1]
|
return objects[1]
|
||||||
|
|
||||||
objects = [NamedObject(u"\xe9foo bar",True),NamedObject("bar bleh",True)]
|
objects = [NamedObject(u"\xe9foo bar",True),NamedObject("bar bleh",True)]
|
||||||
matches = engine.MatchFactory().getmatches(objects) #we should have 5 matches
|
matches = engine.getmatches(objects) #we should have 5 matches
|
||||||
groups = engine.get_groups(matches) #We should have 2 groups
|
groups = engine.get_groups(matches) #We should have 2 groups
|
||||||
for g in groups:
|
for g in groups:
|
||||||
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
|
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
from hsutil import job
|
from hsutil import job, io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
|
from .. import fs
|
||||||
from ..engine import getwords, Match
|
from ..engine import getwords, Match
|
||||||
from ..ignore import IgnoreList
|
from ..ignore import IgnoreList
|
||||||
from ..scanner import *
|
from ..scanner import *
|
||||||
@@ -27,443 +29,439 @@ class NamedObject(object):
|
|||||||
no = NamedObject
|
no = NamedObject
|
||||||
|
|
||||||
#--- Scanner
|
#--- Scanner
|
||||||
def test_empty():
|
class ScannerTestFakeFiles(TestCase):
|
||||||
s = Scanner()
|
def setUp(self):
|
||||||
r = s.GetDupeGroups([])
|
# This is a hack to avoid invalidating all previous tests since the scanner started to test
|
||||||
eq_(r, [])
|
# for file existence before doing the match grouping.
|
||||||
|
self.mock(io, 'exists', lambda _: True)
|
||||||
|
|
||||||
def test_default_settings():
|
def test_empty(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
eq_(s.min_match_percentage, 80)
|
r = s.GetDupeGroups([])
|
||||||
eq_(s.scan_type, SCAN_TYPE_FILENAME)
|
eq_(r, [])
|
||||||
eq_(s.mix_file_kind, True)
|
|
||||||
eq_(s.word_weighting, False)
|
|
||||||
eq_(s.match_similar_words, False)
|
|
||||||
assert isinstance(s.ignore_list, IgnoreList)
|
|
||||||
|
|
||||||
def test_simple_with_default_settings():
|
def test_default_settings(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
eq_(s.min_match_percentage, 80)
|
||||||
r = s.GetDupeGroups(f)
|
eq_(s.scan_type, SCAN_TYPE_FILENAME)
|
||||||
eq_(len(r), 1)
|
eq_(s.mix_file_kind, True)
|
||||||
g = r[0]
|
eq_(s.word_weighting, False)
|
||||||
#'foo bleh' cannot be in the group because the default min match % is 80
|
eq_(s.match_similar_words, False)
|
||||||
eq_(len(g), 2)
|
assert isinstance(s.ignore_list, IgnoreList)
|
||||||
assert g.ref in f[:2]
|
|
||||||
assert g.dupes[0] in f[:2]
|
|
||||||
|
|
||||||
def test_simple_with_lower_min_match():
|
def test_simple_with_default_settings(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.min_match_percentage = 50
|
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
||||||
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
r = s.GetDupeGroups(f)
|
||||||
r = s.GetDupeGroups(f)
|
eq_(len(r), 1)
|
||||||
eq_(len(r), 1)
|
g = r[0]
|
||||||
g = r[0]
|
#'foo bleh' cannot be in the group because the default min match % is 80
|
||||||
eq_(len(g), 3)
|
eq_(len(g), 2)
|
||||||
|
assert g.ref in f[:2]
|
||||||
|
assert g.dupes[0] in f[:2]
|
||||||
|
|
||||||
def test_trim_all_ref_groups():
|
def test_simple_with_lower_min_match(self):
|
||||||
# When all files of a group are ref, don't include that group in the results, but also don't
|
s = Scanner()
|
||||||
# count the files from that group as discarded.
|
s.min_match_percentage = 50
|
||||||
s = Scanner()
|
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
||||||
f = [no('foo'), no('foo'), no('bar'), no('bar')]
|
r = s.GetDupeGroups(f)
|
||||||
f[2].is_ref = True
|
eq_(len(r), 1)
|
||||||
f[3].is_ref = True
|
g = r[0]
|
||||||
r = s.GetDupeGroups(f)
|
eq_(len(g), 3)
|
||||||
eq_(len(r), 1)
|
|
||||||
eq_(s.discarded_file_count, 0)
|
|
||||||
|
|
||||||
def test_priorize():
|
def test_trim_all_ref_groups(self):
|
||||||
s = Scanner()
|
# When all files of a group are ref, don't include that group in the results, but also don't
|
||||||
f = [no('foo'), no('foo'), no('bar'), no('bar')]
|
# count the files from that group as discarded.
|
||||||
f[1].size = 2
|
s = Scanner()
|
||||||
f[2].size = 3
|
f = [no('foo'), no('foo'), no('bar'), no('bar')]
|
||||||
f[3].is_ref = True
|
f[2].is_ref = True
|
||||||
r = s.GetDupeGroups(f)
|
f[3].is_ref = True
|
||||||
g1, g2 = r
|
r = s.GetDupeGroups(f)
|
||||||
assert f[1] in (g1.ref,g2.ref)
|
eq_(len(r), 1)
|
||||||
assert f[0] in (g1.dupes[0],g2.dupes[0])
|
eq_(s.discarded_file_count, 0)
|
||||||
assert f[3] in (g1.ref,g2.ref)
|
|
||||||
assert f[2] in (g1.dupes[0],g2.dupes[0])
|
|
||||||
|
|
||||||
def test_content_scan():
|
def test_priorize(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
f = [no('foo'), no('foo'), no('bar'), no('bar')]
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
f[1].size = 2
|
||||||
f[0].md5 = f[0].md5partial = 'foobar'
|
f[2].size = 3
|
||||||
f[1].md5 = f[1].md5partial = 'foobar'
|
f[3].is_ref = True
|
||||||
f[2].md5 = f[2].md5partial = 'bleh'
|
r = s.GetDupeGroups(f)
|
||||||
r = s.GetDupeGroups(f)
|
g1, g2 = r
|
||||||
eq_(len(r), 1)
|
assert f[1] in (g1.ref,g2.ref)
|
||||||
eq_(len(r[0]), 2)
|
assert f[0] in (g1.dupes[0],g2.dupes[0])
|
||||||
eq_(s.discarded_file_count, 0) # don't count the different md5 as discarded!
|
assert f[3] in (g1.ref,g2.ref)
|
||||||
|
assert f[2] in (g1.dupes[0],g2.dupes[0])
|
||||||
|
|
||||||
def test_content_scan_compare_sizes_first():
|
def test_content_scan(self):
|
||||||
class MyFile(no):
|
s = Scanner()
|
||||||
@property
|
s.scan_type = SCAN_TYPE_CONTENT
|
||||||
def md5(file):
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
raise AssertionError()
|
f[0].md5 = f[0].md5partial = 'foobar'
|
||||||
|
f[1].md5 = f[1].md5partial = 'foobar'
|
||||||
|
f[2].md5 = f[2].md5partial = 'bleh'
|
||||||
|
r = s.GetDupeGroups(f)
|
||||||
|
eq_(len(r), 1)
|
||||||
|
eq_(len(r[0]), 2)
|
||||||
|
eq_(s.discarded_file_count, 0) # don't count the different md5 as discarded!
|
||||||
|
|
||||||
s = Scanner()
|
def test_content_scan_compare_sizes_first(self):
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
class MyFile(no):
|
||||||
f = [MyFile('foo', 1), MyFile('bar', 2)]
|
@property
|
||||||
eq_(len(s.GetDupeGroups(f)), 0)
|
def md5(file):
|
||||||
|
raise AssertionError()
|
||||||
|
|
||||||
def test_min_match_perc_doesnt_matter_for_content_scan():
|
s = Scanner()
|
||||||
s = Scanner()
|
s.scan_type = SCAN_TYPE_CONTENT
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
f = [MyFile('foo', 1), MyFile('bar', 2)]
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
eq_(len(s.GetDupeGroups(f)), 0)
|
||||||
f[0].md5 = f[0].md5partial = 'foobar'
|
|
||||||
f[1].md5 = f[1].md5partial = 'foobar'
|
|
||||||
f[2].md5 = f[2].md5partial = 'bleh'
|
|
||||||
s.min_match_percentage = 101
|
|
||||||
r = s.GetDupeGroups(f)
|
|
||||||
eq_(len(r), 1)
|
|
||||||
eq_(len(r[0]), 2)
|
|
||||||
s.min_match_percentage = 0
|
|
||||||
r = s.GetDupeGroups(f)
|
|
||||||
eq_(len(r), 1)
|
|
||||||
eq_(len(r[0]), 2)
|
|
||||||
|
|
||||||
def test_content_scan_doesnt_put_md5_in_words_at_the_end():
|
def test_min_match_perc_doesnt_matter_for_content_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = SCAN_TYPE_CONTENT
|
||||||
f = [no('foo'),no('bar')]
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
f[0].md5 = f[0].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
f[0].md5 = f[0].md5partial = 'foobar'
|
||||||
f[1].md5 = f[1].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
f[1].md5 = f[1].md5partial = 'foobar'
|
||||||
r = s.GetDupeGroups(f)
|
f[2].md5 = f[2].md5partial = 'bleh'
|
||||||
g = r[0]
|
s.min_match_percentage = 101
|
||||||
eq_(g.ref.words, ['--'])
|
r = s.GetDupeGroups(f)
|
||||||
eq_(g.dupes[0].words, ['--'])
|
eq_(len(r), 1)
|
||||||
|
eq_(len(r[0]), 2)
|
||||||
|
s.min_match_percentage = 0
|
||||||
|
r = s.GetDupeGroups(f)
|
||||||
|
eq_(len(r), 1)
|
||||||
|
eq_(len(r[0]), 2)
|
||||||
|
|
||||||
def test_extension_is_not_counted_in_filename_scan():
|
def test_content_scan_doesnt_put_md5_in_words_at_the_end(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.min_match_percentage = 100
|
s.scan_type = SCAN_TYPE_CONTENT
|
||||||
f = [no('foo.bar'), no('foo.bleh')]
|
f = [no('foo'),no('bar')]
|
||||||
r = s.GetDupeGroups(f)
|
f[0].md5 = f[0].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||||
eq_(len(r), 1)
|
f[1].md5 = f[1].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||||
eq_(len(r[0]), 2)
|
r = s.GetDupeGroups(f)
|
||||||
|
g = r[0]
|
||||||
|
|
||||||
def test_job():
|
def test_extension_is_not_counted_in_filename_scan(self):
|
||||||
def do_progress(progress, desc=''):
|
s = Scanner()
|
||||||
log.append(progress)
|
s.min_match_percentage = 100
|
||||||
return True
|
f = [no('foo.bar'), no('foo.bleh')]
|
||||||
|
r = s.GetDupeGroups(f)
|
||||||
|
eq_(len(r), 1)
|
||||||
|
eq_(len(r[0]), 2)
|
||||||
|
|
||||||
s = Scanner()
|
def test_job(self):
|
||||||
log = []
|
def do_progress(progress, desc=''):
|
||||||
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
log.append(progress)
|
||||||
r = s.GetDupeGroups(f, job.Job(1, do_progress))
|
return True
|
||||||
eq_(log[0], 0)
|
|
||||||
eq_(log[-1], 100)
|
|
||||||
|
|
||||||
def test_mix_file_kind():
|
s = Scanner()
|
||||||
s = Scanner()
|
log = []
|
||||||
s.mix_file_kind = False
|
f = [no('foo bar'), no('foo bar'), no('foo bleh')]
|
||||||
f = [no('foo.1'), no('foo.2')]
|
r = s.GetDupeGroups(f, job.Job(1, do_progress))
|
||||||
r = s.GetDupeGroups(f)
|
eq_(log[0], 0)
|
||||||
eq_(len(r), 0)
|
eq_(log[-1], 100)
|
||||||
|
|
||||||
def test_word_weighting():
|
def test_mix_file_kind(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.min_match_percentage = 75
|
s.mix_file_kind = False
|
||||||
s.word_weighting = True
|
f = [no('foo.1'), no('foo.2')]
|
||||||
f = [no('foo bar'), no('foo bar bleh')]
|
r = s.GetDupeGroups(f)
|
||||||
r = s.GetDupeGroups(f)
|
eq_(len(r), 0)
|
||||||
eq_(len(r), 1)
|
|
||||||
g = r[0]
|
|
||||||
m = g.get_match_of(g.dupes[0])
|
|
||||||
eq_(m.percentage, 75) # 16 letters, 12 matching
|
|
||||||
|
|
||||||
def test_similar_words():
|
def test_word_weighting(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.match_similar_words = True
|
s.min_match_percentage = 75
|
||||||
f = [no('The White Stripes'), no('The Whites Stripe'), no('Limp Bizkit'), no('Limp Bizkitt')]
|
s.word_weighting = True
|
||||||
r = s.GetDupeGroups(f)
|
f = [no('foo bar'), no('foo bar bleh')]
|
||||||
eq_(len(r), 2)
|
r = s.GetDupeGroups(f)
|
||||||
|
eq_(len(r), 1)
|
||||||
|
g = r[0]
|
||||||
|
m = g.get_match_of(g.dupes[0])
|
||||||
|
eq_(m.percentage, 75) # 16 letters, 12 matching
|
||||||
|
|
||||||
def test_fields():
|
def test_similar_words(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_FIELDS
|
s.match_similar_words = True
|
||||||
f = [no('The White Stripes - Little Ghost'), no('The White Stripes - Little Acorn')]
|
f = [no('The White Stripes'), no('The Whites Stripe'), no('Limp Bizkit'), no('Limp Bizkitt')]
|
||||||
r = s.GetDupeGroups(f)
|
r = s.GetDupeGroups(f)
|
||||||
eq_(len(r), 0)
|
eq_(len(r), 2)
|
||||||
|
|
||||||
def test_fields_no_order():
|
def test_fields(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_FIELDS_NO_ORDER
|
s.scan_type = SCAN_TYPE_FIELDS
|
||||||
f = [no('The White Stripes - Little Ghost'), no('Little Ghost - The White Stripes')]
|
f = [no('The White Stripes - Little Ghost'), no('The White Stripes - Little Acorn')]
|
||||||
r = s.GetDupeGroups(f)
|
r = s.GetDupeGroups(f)
|
||||||
eq_(len(r), 1)
|
eq_(len(r), 0)
|
||||||
|
|
||||||
def test_tag_scan():
|
def test_fields_no_order(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_FIELDS_NO_ORDER
|
||||||
o1 = no('foo')
|
f = [no('The White Stripes - Little Ghost'), no('Little Ghost - The White Stripes')]
|
||||||
o2 = no('bar')
|
r = s.GetDupeGroups(f)
|
||||||
o1.artist = 'The White Stripes'
|
eq_(len(r), 1)
|
||||||
o1.title = 'The Air Near My Fingers'
|
|
||||||
o2.artist = 'The White Stripes'
|
|
||||||
o2.title = 'The Air Near My Fingers'
|
|
||||||
r = s.GetDupeGroups([o1,o2])
|
|
||||||
eq_(len(r), 1)
|
|
||||||
|
|
||||||
def test_tag_with_album_scan():
|
def test_tag_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
o1 = no('foo')
|
||||||
o1 = no('foo')
|
o2 = no('bar')
|
||||||
o2 = no('bar')
|
o1.artist = 'The White Stripes'
|
||||||
o3 = no('bleh')
|
o1.title = 'The Air Near My Fingers'
|
||||||
o1.artist = 'The White Stripes'
|
o2.artist = 'The White Stripes'
|
||||||
o1.title = 'The Air Near My Fingers'
|
o2.title = 'The Air Near My Fingers'
|
||||||
o1.album = 'Elephant'
|
r = s.GetDupeGroups([o1,o2])
|
||||||
o2.artist = 'The White Stripes'
|
eq_(len(r), 1)
|
||||||
o2.title = 'The Air Near My Fingers'
|
|
||||||
o2.album = 'Elephant'
|
|
||||||
o3.artist = 'The White Stripes'
|
|
||||||
o3.title = 'The Air Near My Fingers'
|
|
||||||
o3.album = 'foobar'
|
|
||||||
r = s.GetDupeGroups([o1,o2,o3])
|
|
||||||
eq_(len(r), 1)
|
|
||||||
|
|
||||||
def test_that_dash_in_tags_dont_create_new_fields():
|
def test_tag_with_album_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||||
s.min_match_percentage = 50
|
o1 = no('foo')
|
||||||
o1 = no('foo')
|
o2 = no('bar')
|
||||||
o2 = no('bar')
|
o3 = no('bleh')
|
||||||
o1.artist = 'The White Stripes - a'
|
o1.artist = 'The White Stripes'
|
||||||
o1.title = 'The Air Near My Fingers - a'
|
o1.title = 'The Air Near My Fingers'
|
||||||
o1.album = 'Elephant - a'
|
o1.album = 'Elephant'
|
||||||
o2.artist = 'The White Stripes - b'
|
o2.artist = 'The White Stripes'
|
||||||
o2.title = 'The Air Near My Fingers - b'
|
o2.title = 'The Air Near My Fingers'
|
||||||
o2.album = 'Elephant - b'
|
o2.album = 'Elephant'
|
||||||
r = s.GetDupeGroups([o1,o2])
|
o3.artist = 'The White Stripes'
|
||||||
eq_(len(r), 1)
|
o3.title = 'The Air Near My Fingers'
|
||||||
|
o3.album = 'foobar'
|
||||||
|
r = s.GetDupeGroups([o1,o2,o3])
|
||||||
|
eq_(len(r), 1)
|
||||||
|
|
||||||
def test_tag_scan_with_different_scanned():
|
def test_that_dash_in_tags_dont_create_new_fields(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
s.scanned_tags = set(['track', 'year'])
|
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||||
o1 = no('foo')
|
s.min_match_percentage = 50
|
||||||
o2 = no('bar')
|
o1 = no('foo')
|
||||||
o1.artist = 'The White Stripes'
|
o2 = no('bar')
|
||||||
o1.title = 'some title'
|
o1.artist = 'The White Stripes - a'
|
||||||
o1.track = 'foo'
|
o1.title = 'The Air Near My Fingers - a'
|
||||||
o1.year = 'bar'
|
o1.album = 'Elephant - a'
|
||||||
o2.artist = 'The White Stripes'
|
o2.artist = 'The White Stripes - b'
|
||||||
o2.title = 'another title'
|
o2.title = 'The Air Near My Fingers - b'
|
||||||
o2.track = 'foo'
|
o2.album = 'Elephant - b'
|
||||||
o2.year = 'bar'
|
r = s.GetDupeGroups([o1,o2])
|
||||||
r = s.GetDupeGroups([o1, o2])
|
eq_(len(r), 1)
|
||||||
eq_(len(r), 1)
|
|
||||||
|
|
||||||
def test_tag_scan_only_scans_existing_tags():
|
def test_tag_scan_with_different_scanned(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
s.scanned_tags = set(['artist', 'foo'])
|
s.scanned_tags = set(['track', 'year'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
o1.artist = 'The White Stripes'
|
o1.artist = 'The White Stripes'
|
||||||
o1.foo = 'foo'
|
o1.title = 'some title'
|
||||||
o2.artist = 'The White Stripes'
|
o1.track = 'foo'
|
||||||
o2.foo = 'bar'
|
o1.year = 'bar'
|
||||||
r = s.GetDupeGroups([o1, o2])
|
o2.artist = 'The White Stripes'
|
||||||
eq_(len(r), 1) # Because 'foo' is not scanned, they match
|
o2.title = 'another title'
|
||||||
|
o2.track = 'foo'
|
||||||
def test_tag_scan_converts_to_str():
|
o2.year = 'bar'
|
||||||
s = Scanner()
|
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
|
||||||
s.scanned_tags = set(['track'])
|
|
||||||
o1 = no('foo')
|
|
||||||
o2 = no('bar')
|
|
||||||
o1.track = 42
|
|
||||||
o2.track = 42
|
|
||||||
try:
|
|
||||||
r = s.GetDupeGroups([o1, o2])
|
r = s.GetDupeGroups([o1, o2])
|
||||||
except TypeError:
|
eq_(len(r), 1)
|
||||||
raise AssertionError()
|
|
||||||
eq_(len(r), 1)
|
|
||||||
|
|
||||||
def test_tag_scan_non_ascii():
|
def test_tag_scan_only_scans_existing_tags(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
s.scanned_tags = set(['title'])
|
s.scanned_tags = set(['artist', 'foo'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
o1.title = u'foobar\u00e9'
|
o1.artist = 'The White Stripes'
|
||||||
o2.title = u'foobar\u00e9'
|
o1.foo = 'foo'
|
||||||
try:
|
o2.artist = 'The White Stripes'
|
||||||
|
o2.foo = 'bar'
|
||||||
r = s.GetDupeGroups([o1, o2])
|
r = s.GetDupeGroups([o1, o2])
|
||||||
except UnicodeEncodeError:
|
eq_(len(r), 1) # Because 'foo' is not scanned, they match
|
||||||
raise AssertionError()
|
|
||||||
eq_(len(r), 1)
|
|
||||||
|
|
||||||
def test_audio_content_scan():
|
def test_tag_scan_converts_to_str(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
s.scanned_tags = set(['track'])
|
||||||
f[0].md5 = 'foo'
|
o1 = no('foo')
|
||||||
f[1].md5 = 'bar'
|
o2 = no('bar')
|
||||||
f[2].md5 = 'bleh'
|
o1.track = 42
|
||||||
f[0].md5partial = 'foo'
|
o2.track = 42
|
||||||
f[1].md5partial = 'foo'
|
try:
|
||||||
f[2].md5partial = 'bleh'
|
r = s.GetDupeGroups([o1, o2])
|
||||||
f[0].audiosize = 1
|
except TypeError:
|
||||||
f[1].audiosize = 1
|
|
||||||
f[2].audiosize = 1
|
|
||||||
r = s.GetDupeGroups(f)
|
|
||||||
eq_(len(r), 1)
|
|
||||||
eq_(len(r[0]), 2)
|
|
||||||
|
|
||||||
def test_audio_content_scan_compare_sizes_first():
|
|
||||||
class MyFile(no):
|
|
||||||
@property
|
|
||||||
def md5partial(file):
|
|
||||||
raise AssertionError()
|
raise AssertionError()
|
||||||
|
eq_(len(r), 1)
|
||||||
|
|
||||||
s = Scanner()
|
def test_tag_scan_non_ascii(self):
|
||||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
s = Scanner()
|
||||||
f = [MyFile('foo'), MyFile('bar')]
|
s.scan_type = SCAN_TYPE_TAG
|
||||||
f[0].audiosize = 1
|
s.scanned_tags = set(['title'])
|
||||||
f[1].audiosize = 2
|
o1 = no('foo')
|
||||||
eq_(len(s.GetDupeGroups(f)), 0)
|
o2 = no('bar')
|
||||||
|
o1.title = u'foobar\u00e9'
|
||||||
|
o2.title = u'foobar\u00e9'
|
||||||
|
try:
|
||||||
|
r = s.GetDupeGroups([o1, o2])
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
raise AssertionError()
|
||||||
|
eq_(len(r), 1)
|
||||||
|
|
||||||
def test_ignore_list():
|
def test_audio_content_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
f1 = no('foobar')
|
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
||||||
f2 = no('foobar')
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
f3 = no('foobar')
|
f[0].md5 = 'foo'
|
||||||
f1.path = Path('dir1/foobar')
|
f[1].md5 = 'bar'
|
||||||
f2.path = Path('dir2/foobar')
|
f[2].md5 = 'bleh'
|
||||||
f3.path = Path('dir3/foobar')
|
f[0].md5partial = 'foo'
|
||||||
s.ignore_list.Ignore(str(f1.path),str(f2.path))
|
f[1].md5partial = 'foo'
|
||||||
s.ignore_list.Ignore(str(f1.path),str(f3.path))
|
f[2].md5partial = 'bleh'
|
||||||
r = s.GetDupeGroups([f1,f2,f3])
|
f[0].audiosize = 1
|
||||||
eq_(len(r), 1)
|
f[1].audiosize = 1
|
||||||
g = r[0]
|
f[2].audiosize = 1
|
||||||
eq_(len(g.dupes), 1)
|
r = s.GetDupeGroups(f)
|
||||||
assert f1 not in g
|
eq_(len(r), 1)
|
||||||
assert f2 in g
|
eq_(len(r[0]), 2)
|
||||||
assert f3 in g
|
|
||||||
# Ignored matches are not counted as discarded
|
|
||||||
eq_(s.discarded_file_count, 0)
|
|
||||||
|
|
||||||
def test_ignore_list_checks_for_unicode():
|
def test_audio_content_scan_compare_sizes_first(self):
|
||||||
#scanner was calling path_str for ignore list checks. Since the Path changes, it must
|
class MyFile(no):
|
||||||
#be unicode(path)
|
@property
|
||||||
s = Scanner()
|
def md5partial(file):
|
||||||
f1 = no('foobar')
|
raise AssertionError()
|
||||||
f2 = no('foobar')
|
|
||||||
f3 = no('foobar')
|
|
||||||
f1.path = Path(u'foo1\u00e9')
|
|
||||||
f2.path = Path(u'foo2\u00e9')
|
|
||||||
f3.path = Path(u'foo3\u00e9')
|
|
||||||
s.ignore_list.Ignore(unicode(f1.path),unicode(f2.path))
|
|
||||||
s.ignore_list.Ignore(unicode(f1.path),unicode(f3.path))
|
|
||||||
r = s.GetDupeGroups([f1,f2,f3])
|
|
||||||
eq_(len(r), 1)
|
|
||||||
g = r[0]
|
|
||||||
eq_(len(g.dupes), 1)
|
|
||||||
assert f1 not in g
|
|
||||||
assert f2 in g
|
|
||||||
assert f3 in g
|
|
||||||
|
|
||||||
def test_custom_match_factory():
|
s = Scanner()
|
||||||
class MatchFactory(object):
|
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
||||||
def getmatches(self, objects, j=None):
|
f = [MyFile('foo'), MyFile('bar')]
|
||||||
return [Match(objects[0], objects[1], 420)]
|
f[0].audiosize = 1
|
||||||
|
f[1].audiosize = 2
|
||||||
|
eq_(len(s.GetDupeGroups(f)), 0)
|
||||||
|
|
||||||
|
def test_ignore_list(self):
|
||||||
|
s = Scanner()
|
||||||
|
f1 = no('foobar')
|
||||||
|
f2 = no('foobar')
|
||||||
|
f3 = no('foobar')
|
||||||
|
f1.path = Path('dir1/foobar')
|
||||||
|
f2.path = Path('dir2/foobar')
|
||||||
|
f3.path = Path('dir3/foobar')
|
||||||
|
s.ignore_list.Ignore(str(f1.path),str(f2.path))
|
||||||
|
s.ignore_list.Ignore(str(f1.path),str(f3.path))
|
||||||
|
r = s.GetDupeGroups([f1,f2,f3])
|
||||||
|
eq_(len(r), 1)
|
||||||
|
g = r[0]
|
||||||
|
eq_(len(g.dupes), 1)
|
||||||
|
assert f1 not in g
|
||||||
|
assert f2 in g
|
||||||
|
assert f3 in g
|
||||||
|
# Ignored matches are not counted as discarded
|
||||||
|
eq_(s.discarded_file_count, 0)
|
||||||
|
|
||||||
|
def test_ignore_list_checks_for_unicode(self):
|
||||||
|
#scanner was calling path_str for ignore list checks. Since the Path changes, it must
|
||||||
|
#be unicode(path)
|
||||||
|
s = Scanner()
|
||||||
|
f1 = no('foobar')
|
||||||
|
f2 = no('foobar')
|
||||||
|
f3 = no('foobar')
|
||||||
|
f1.path = Path(u'foo1\u00e9')
|
||||||
|
f2.path = Path(u'foo2\u00e9')
|
||||||
|
f3.path = Path(u'foo3\u00e9')
|
||||||
|
s.ignore_list.Ignore(unicode(f1.path),unicode(f2.path))
|
||||||
|
s.ignore_list.Ignore(unicode(f1.path),unicode(f3.path))
|
||||||
|
r = s.GetDupeGroups([f1,f2,f3])
|
||||||
|
eq_(len(r), 1)
|
||||||
|
g = r[0]
|
||||||
|
eq_(len(g.dupes), 1)
|
||||||
|
assert f1 not in g
|
||||||
|
assert f2 in g
|
||||||
|
assert f3 in g
|
||||||
|
|
||||||
|
def test_file_evaluates_to_false(self):
|
||||||
|
# A very wrong way to use any() was added at some point, causing resulting group list
|
||||||
|
# to be empty.
|
||||||
|
class FalseNamedObject(NamedObject):
|
||||||
|
def __nonzero__(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.match_factory = MatchFactory()
|
f1 = FalseNamedObject('foobar')
|
||||||
o1, o2 = no('foo'), no('bar')
|
f2 = FalseNamedObject('foobar')
|
||||||
groups = s.GetDupeGroups([o1, o2])
|
r = s.GetDupeGroups([f1, f2])
|
||||||
eq_(len(groups), 1)
|
eq_(len(r), 1)
|
||||||
g = groups[0]
|
|
||||||
eq_(len(g), 2)
|
|
||||||
g.switch_ref(o1)
|
|
||||||
m = g.get_match_of(o2)
|
|
||||||
eq_(m, (o1, o2, 420))
|
|
||||||
|
|
||||||
def test_file_evaluates_to_false():
|
def test_size_threshold(self):
|
||||||
# A very wrong way to use any() was added at some point, causing resulting group list
|
# Only file equal or higher than the size_threshold in size are scanned
|
||||||
# to be empty.
|
s = Scanner()
|
||||||
class FalseNamedObject(NamedObject):
|
f1 = no('foo', 1)
|
||||||
def __nonzero__(self):
|
f2 = no('foo', 2)
|
||||||
return False
|
f3 = no('foo', 3)
|
||||||
|
s.size_threshold = 2
|
||||||
|
groups = s.GetDupeGroups([f1,f2,f3])
|
||||||
|
eq_(len(groups), 1)
|
||||||
|
[group] = groups
|
||||||
|
eq_(len(group), 2)
|
||||||
|
assert f1 not in group
|
||||||
|
assert f2 in group
|
||||||
|
assert f3 in group
|
||||||
|
|
||||||
|
def test_tie_breaker_path_deepness(self):
|
||||||
|
# If there is a tie in prioritization, path deepness is used as a tie breaker
|
||||||
|
s = Scanner()
|
||||||
|
o1, o2 = no('foo'), no('foo')
|
||||||
|
o1.path = Path('foo')
|
||||||
|
o2.path = Path('foo/bar')
|
||||||
|
[group] = s.GetDupeGroups([o1, o2])
|
||||||
|
assert group.ref is o2
|
||||||
|
|
||||||
|
def test_tie_breaker_copy(self):
|
||||||
|
# if copy is in the words used (even if it has a deeper path), it becomes a dupe
|
||||||
|
s = Scanner()
|
||||||
|
o1, o2 = no('foo bar Copy'), no('foo bar')
|
||||||
|
o1.path = Path('deeper/path')
|
||||||
|
o2.path = Path('foo')
|
||||||
|
[group] = s.GetDupeGroups([o1, o2])
|
||||||
|
assert group.ref is o2
|
||||||
|
|
||||||
|
def test_tie_breaker_same_name_plus_digit(self):
|
||||||
|
# if ref has the same words as dupe, but has some just one extra word which is a digit, it
|
||||||
|
# becomes a dupe
|
||||||
|
s = Scanner()
|
||||||
|
o1, o2 = no('foo bar 42'), no('foo bar')
|
||||||
|
o1.path = Path('deeper/path')
|
||||||
|
o2.path = Path('foo')
|
||||||
|
[group] = s.GetDupeGroups([o1, o2])
|
||||||
|
assert group.ref is o2
|
||||||
|
|
||||||
|
def test_partial_group_match(self):
|
||||||
|
# Count the number od discarded matches (when a file doesn't match all other dupes of the
|
||||||
|
# group) in Scanner.discarded_file_count
|
||||||
|
s = Scanner()
|
||||||
|
o1, o2, o3 = no('a b'), no('a'), no('b')
|
||||||
|
s.min_match_percentage = 50
|
||||||
|
[group] = s.GetDupeGroups([o1, o2, o3])
|
||||||
|
eq_(len(group), 2)
|
||||||
|
assert o1 in group
|
||||||
|
assert o2 in group
|
||||||
|
assert o3 not in group
|
||||||
|
eq_(s.discarded_file_count, 1)
|
||||||
|
|
||||||
|
|
||||||
s = Scanner()
|
class ScannerTest(TestCase):
|
||||||
f1 = FalseNamedObject('foobar')
|
def test_dont_group_files_that_dont_exist(self):
|
||||||
f2 = FalseNamedObject('foobar')
|
# when creating groups, check that files exist first. It's possible that these files have
|
||||||
r = s.GetDupeGroups([f1, f2])
|
# been moved during the scan by the user.
|
||||||
eq_(len(r), 1)
|
# In this test, we have to delete one of the files between the get_matches() part and the
|
||||||
|
# get_groups() part.
|
||||||
|
s = Scanner()
|
||||||
|
s.scan_type = SCAN_TYPE_CONTENT
|
||||||
|
p = self.tmppath()
|
||||||
|
io.open(p + 'file1', 'w').write('foo')
|
||||||
|
io.open(p + 'file2', 'w').write('foo')
|
||||||
|
file1, file2 = fs.get_files(p)
|
||||||
|
def getmatches(*args, **kw):
|
||||||
|
io.remove(file2.path)
|
||||||
|
return [Match(file1, file2, 100)]
|
||||||
|
s._getmatches = getmatches
|
||||||
|
|
||||||
def test_size_threshold():
|
assert not s.GetDupeGroups([file1, file2])
|
||||||
# Only file equal or higher than the size_threshold in size are scanned
|
|
||||||
s = Scanner()
|
|
||||||
f1 = no('foo', 1)
|
|
||||||
f2 = no('foo', 2)
|
|
||||||
f3 = no('foo', 3)
|
|
||||||
s.size_threshold = 2
|
|
||||||
groups = s.GetDupeGroups([f1,f2,f3])
|
|
||||||
eq_(len(groups), 1)
|
|
||||||
[group] = groups
|
|
||||||
eq_(len(group), 2)
|
|
||||||
assert f1 not in group
|
|
||||||
assert f2 in group
|
|
||||||
assert f3 in group
|
|
||||||
|
|
||||||
def test_tie_breaker_path_deepness():
|
|
||||||
# If there is a tie in prioritization, path deepness is used as a tie breaker
|
|
||||||
s = Scanner()
|
|
||||||
o1, o2 = no('foo'), no('foo')
|
|
||||||
o1.path = Path('foo')
|
|
||||||
o2.path = Path('foo/bar')
|
|
||||||
[group] = s.GetDupeGroups([o1, o2])
|
|
||||||
assert group.ref is o2
|
|
||||||
|
|
||||||
def test_tie_breaker_copy():
|
|
||||||
# if copy is in the words used (even if it has a deeper path), it becomes a dupe
|
|
||||||
s = Scanner()
|
|
||||||
o1, o2 = no('foo bar Copy'), no('foo bar')
|
|
||||||
o1.path = Path('deeper/path')
|
|
||||||
o2.path = Path('foo')
|
|
||||||
[group] = s.GetDupeGroups([o1, o2])
|
|
||||||
assert group.ref is o2
|
|
||||||
|
|
||||||
def test_tie_breaker_same_name_plus_digit():
|
|
||||||
# if ref has the same words as dupe, but has some just one extra word which is a digit, it
|
|
||||||
# becomes a dupe
|
|
||||||
s = Scanner()
|
|
||||||
o1, o2 = no('foo bar 42'), no('foo bar')
|
|
||||||
o1.path = Path('deeper/path')
|
|
||||||
o2.path = Path('foo')
|
|
||||||
[group] = s.GetDupeGroups([o1, o2])
|
|
||||||
assert group.ref is o2
|
|
||||||
|
|
||||||
def test_partial_group_match():
|
|
||||||
# Count the number od discarded matches (when a file doesn't match all other dupes of the
|
|
||||||
# group) in Scanner.discarded_file_count
|
|
||||||
s = Scanner()
|
|
||||||
o1, o2, o3 = no('a b'), no('a'), no('b')
|
|
||||||
s.min_match_percentage = 50
|
|
||||||
[group] = s.GetDupeGroups([o1, o2, o3])
|
|
||||||
eq_(len(group), 2)
|
|
||||||
assert o1 in group
|
|
||||||
assert o2 in group
|
|
||||||
assert o3 not in group
|
|
||||||
eq_(s.discarded_file_count, 1)
|
|
||||||
|
|
||||||
|
|
||||||
#--- Scanner ME
|
|
||||||
def test_priorize_me():
|
|
||||||
# in ScannerME, bitrate goes first (right after is_ref) in priorization
|
|
||||||
s = ScannerME()
|
|
||||||
o1, o2 = no('foo'), no('foo')
|
|
||||||
o1.bitrate = 1
|
|
||||||
o2.bitrate = 2
|
|
||||||
[group] = s.GetDupeGroups([o1, o2])
|
|
||||||
assert group.ref is o2
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 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 PyQt4.QtCore import Qt, QCoreApplication, SIGNAL
|
|
||||||
from PyQt4.QtGui import QDialog, QDialogButtonBox, QPixmap
|
|
||||||
|
|
||||||
from about_box_ui import Ui_AboutBox
|
|
||||||
|
|
||||||
class AboutBox(QDialog, Ui_AboutBox):
|
|
||||||
def __init__(self, parent, app):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self.app = app
|
|
||||||
self._setupUi()
|
|
||||||
|
|
||||||
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be done in the Designer
|
|
||||||
self.setWindowTitle(u"About %s" % QCoreApplication.instance().applicationName())
|
|
||||||
self.nameLabel.setText(QCoreApplication.instance().applicationName())
|
|
||||||
self.versionLabel.setText('Version ' + QCoreApplication.instance().applicationVersion())
|
|
||||||
self.logoLabel.setPixmap(QPixmap(':/%s_big' % self.app.LOGO_NAME))
|
|
||||||
self.registerButton = self.buttonBox.addButton("Register", QDialogButtonBox.ActionRole)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def buttonClicked(self, button):
|
|
||||||
if button is self.registerButton:
|
|
||||||
self.app.ask_for_reg_code()
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>AboutBox</class>
|
|
||||||
<widget class="QDialog" name="AboutBox">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>400</width>
|
|
||||||
<height>190</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>About dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="logoLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="pixmap">
|
|
||||||
<pixmap resource="dg.qrc">:/logo_me_big</pixmap>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="nameLabel">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="versionLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Version</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Copyright Hardcoded Software 2009</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Registered To:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="registeredEmailLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>UNREGISTERED</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources>
|
|
||||||
<include location="dg.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>AboutBox</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>248</x>
|
|
||||||
<y>254</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>AboutBox</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>316</x>
|
|
||||||
<y>260</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -16,21 +16,21 @@ import os.path as op
|
|||||||
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
||||||
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
|
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
|
||||||
|
|
||||||
import hsfs as fs
|
|
||||||
from hsutil import job
|
from hsutil import job
|
||||||
from hsutil.reg import RegistrationRequired
|
from hsutil.reg import RegistrationRequired
|
||||||
|
|
||||||
|
from dupeguru import fs
|
||||||
from dupeguru.app import (DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY,
|
from dupeguru.app import (DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY,
|
||||||
JOB_DELETE)
|
JOB_DELETE)
|
||||||
|
|
||||||
|
from qtlib.about_box import AboutBox
|
||||||
from qtlib.progress import Progress
|
from qtlib.progress import Progress
|
||||||
|
from qtlib.reg import Registration
|
||||||
|
|
||||||
from . import platform
|
from . import platform
|
||||||
|
|
||||||
from .main_window import MainWindow
|
from .main_window import MainWindow
|
||||||
from .directories_dialog import DirectoriesDialog
|
from .directories_dialog import DirectoriesDialog
|
||||||
from .about_box import AboutBox
|
|
||||||
from .reg import Registration
|
|
||||||
|
|
||||||
JOBID2TITLE = {
|
JOBID2TITLE = {
|
||||||
JOB_SCAN: "Scanning for duplicates",
|
JOB_SCAN: "Scanning for duplicates",
|
||||||
@@ -54,6 +54,7 @@ class DupeGuru(DupeGuruBase, QObject):
|
|||||||
LOGO_NAME = '<replace this>'
|
LOGO_NAME = '<replace this>'
|
||||||
NAME = '<replace this>'
|
NAME = '<replace this>'
|
||||||
DELTA_COLUMNS = frozenset()
|
DELTA_COLUMNS = frozenset()
|
||||||
|
DEMO_LIMIT_DESC = "In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied."
|
||||||
|
|
||||||
def __init__(self, data_module, appid):
|
def __init__(self, data_module, appid):
|
||||||
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
||||||
@@ -143,9 +144,8 @@ class DupeGuru(DupeGuruBase, QObject):
|
|||||||
DupeGuruBase.apply_filter(self, filter)
|
DupeGuruBase.apply_filter(self, filter)
|
||||||
self.emit(SIGNAL('resultsChanged()'))
|
self.emit(SIGNAL('resultsChanged()'))
|
||||||
|
|
||||||
def ask_for_reg_code(self):
|
def askForRegCode(self):
|
||||||
if self.reg.ask_for_code():
|
self.reg.ask_for_code()
|
||||||
self._setup_ui_as_registered()
|
|
||||||
|
|
||||||
@demo_method
|
@demo_method
|
||||||
def copy_or_move_marked(self, copy):
|
def copy_or_move_marked(self, copy):
|
||||||
@@ -197,7 +197,7 @@ class DupeGuru(DupeGuruBase, QObject):
|
|||||||
|
|
||||||
def rename_dupe(self, dupe, newname):
|
def rename_dupe(self, dupe, newname):
|
||||||
try:
|
try:
|
||||||
dupe.move(dupe.parent, newname)
|
dupe.rename(newname)
|
||||||
return True
|
return True
|
||||||
except (IndexError, fs.FSError) as e:
|
except (IndexError, fs.FSError) as e:
|
||||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<file alias="logo_se">images/dgse_logo_32.png</file>
|
<file alias="logo_se">images/dgse_logo_32.png</file>
|
||||||
<file alias="logo_se_big">images/dgse_logo_128.png</file>
|
<file alias="logo_se_big">images/dgse_logo_128.png</file>
|
||||||
<file alias="folder">images/folderwin32.png</file>
|
<file alias="folder">images/folderwin32.png</file>
|
||||||
<file alias="gear">images/gear.png</file>
|
|
||||||
<file alias="preferences">images/preferences32.png</file>
|
<file alias="preferences">images/preferences32.png</file>
|
||||||
<file alias="actions">images/actions32.png</file>
|
<file alias="actions">images/actions32.png</file>
|
||||||
<file alias="delta">images/delta32.png</file>
|
<file alias="delta">images/delta32.png</file>
|
||||||
|
|||||||
@@ -16,9 +16,18 @@
|
|||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeView" name="treeView">
|
<widget class="QTreeView" name="treeView">
|
||||||
|
<property name="acceptDrops">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="dragDropOverwriteMode">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="dragDropMode">
|
||||||
|
<enum>QAbstractItemView::DropOnly</enum>
|
||||||
|
</property>
|
||||||
<property name="uniformRowHeights">
|
<property name="uniformRowHeights">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@@ -59,6 +68,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove</string>
|
<string>Remove</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Del</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -7,8 +7,11 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint
|
import urllib
|
||||||
from PyQt4.QtGui import QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush
|
|
||||||
|
from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint, QUrl
|
||||||
|
from PyQt4.QtGui import (QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush, QStyle,
|
||||||
|
QStyleOptionComboBox, QStyleOptionViewItemV4)
|
||||||
|
|
||||||
from qtlib.tree_model import TreeNode, TreeModel
|
from qtlib.tree_model import TreeNode, TreeModel
|
||||||
|
|
||||||
@@ -21,6 +24,24 @@ class DirectoriesDelegate(QStyledItemDelegate):
|
|||||||
editor.addItems(STATES)
|
editor.addItems(STATES)
|
||||||
return editor
|
return editor
|
||||||
|
|
||||||
|
def paint(self, painter, option, index):
|
||||||
|
self.initStyleOption(option, index)
|
||||||
|
# No idea why, but this cast is required if we want to have access to the V4 valuess
|
||||||
|
option = QStyleOptionViewItemV4(option)
|
||||||
|
if (index.column() == 1) and (option.state & QStyle.State_Selected):
|
||||||
|
cboption = QStyleOptionComboBox()
|
||||||
|
cboption.rect = option.rect
|
||||||
|
# On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to
|
||||||
|
# fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright.
|
||||||
|
cboption.state |= QStyle.State_Enabled
|
||||||
|
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter)
|
||||||
|
painter.setBrush(option.palette.text())
|
||||||
|
rect = QRect(option.rect)
|
||||||
|
rect.setLeft(rect.left()+4)
|
||||||
|
painter.drawText(rect, Qt.AlignLeft, option.text)
|
||||||
|
else:
|
||||||
|
QStyledItemDelegate.paint(self, painter, option, index)
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
def setEditorData(self, editor, index):
|
||||||
value = index.model().data(index, Qt.EditRole)
|
value = index.model().data(index, Qt.EditRole)
|
||||||
editor.setCurrentIndex(value);
|
editor.setCurrentIndex(value);
|
||||||
@@ -47,19 +68,27 @@ class DirectoryNode(TreeNode):
|
|||||||
return DirectoryNode(self.model, self, ref, row)
|
return DirectoryNode(self.model, self, ref, row)
|
||||||
|
|
||||||
def _getChildren(self):
|
def _getChildren(self):
|
||||||
return self.ref.dirs
|
return self.model.dirs.get_subfolders(self.ref)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
if self.parent is not None:
|
||||||
|
return self.ref[-1]
|
||||||
|
else:
|
||||||
|
return unicode(self.ref)
|
||||||
|
|
||||||
|
|
||||||
class DirectoriesModel(TreeModel):
|
class DirectoriesModel(TreeModel):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self._dirs = app.directories
|
self.app = app
|
||||||
|
self.dirs = app.directories
|
||||||
TreeModel.__init__(self)
|
TreeModel.__init__(self)
|
||||||
|
|
||||||
def _createNode(self, ref, row):
|
def _createNode(self, ref, row):
|
||||||
return DirectoryNode(self, None, ref, row)
|
return DirectoryNode(self, None, ref, row)
|
||||||
|
|
||||||
def _getChildren(self):
|
def _getChildren(self):
|
||||||
return self._dirs
|
return self.dirs
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
return 2
|
return 2
|
||||||
@@ -70,23 +99,37 @@ class DirectoriesModel(TreeModel):
|
|||||||
node = index.internalPointer()
|
node = index.internalPointer()
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
if index.column() == 0:
|
if index.column() == 0:
|
||||||
return node.ref.name
|
return node.name
|
||||||
else:
|
else:
|
||||||
return STATES[self._dirs.get_state(node.ref.path)]
|
return STATES[self.dirs.get_state(node.ref)]
|
||||||
elif role == Qt.EditRole and index.column() == 1:
|
elif role == Qt.EditRole and index.column() == 1:
|
||||||
return self._dirs.get_state(node.ref.path)
|
return self.dirs.get_state(node.ref)
|
||||||
elif role == Qt.ForegroundRole:
|
elif role == Qt.ForegroundRole:
|
||||||
state = self._dirs.get_state(node.ref.path)
|
state = self.dirs.get_state(node.ref)
|
||||||
if state == 1:
|
if state == 1:
|
||||||
return QBrush(Qt.blue)
|
return QBrush(Qt.blue)
|
||||||
elif state == 2:
|
elif state == 2:
|
||||||
return QBrush(Qt.red)
|
return QBrush(Qt.red)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def dropMimeData(self, mimeData, action, row, column, parentIndex):
|
||||||
|
# the data in mimeData is urlencoded **in utf-8**!!! which means that urllib.unquote has
|
||||||
|
# to be called on the utf-8 encoded string, and *only then*, decoded to unicode.
|
||||||
|
if not mimeData.hasFormat('text/uri-list'):
|
||||||
|
return False
|
||||||
|
data = str(mimeData.data('text/uri-list'))
|
||||||
|
unquoted = urllib.unquote(data)
|
||||||
|
urls = unicode(unquoted, 'utf-8').split('\r\n')
|
||||||
|
paths = [unicode(QUrl(url).toLocalFile()) for url in urls if url]
|
||||||
|
for path in paths:
|
||||||
|
self.app.add_directory(path)
|
||||||
|
self.reset()
|
||||||
|
return True
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return 0
|
return Qt.ItemIsEnabled | Qt.ItemIsDropEnabled
|
||||||
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDropEnabled
|
||||||
if index.column() == 1:
|
if index.column() == 1:
|
||||||
result |= Qt.ItemIsEditable
|
result |= Qt.ItemIsEditable
|
||||||
return result
|
return result
|
||||||
@@ -97,10 +140,18 @@ class DirectoriesModel(TreeModel):
|
|||||||
return HEADERS[section]
|
return HEADERS[section]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def mimeTypes(self):
|
||||||
|
return ['text/uri-list']
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
def setData(self, index, value, role):
|
||||||
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
|
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
|
||||||
return False
|
return False
|
||||||
node = index.internalPointer()
|
node = index.internalPointer()
|
||||||
self._dirs.set_state(node.ref.path, value)
|
self.dirs.set_state(node.ref, value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def supportedDropActions(self):
|
||||||
|
# Normally, the correct action should be ActionLink, but the drop doesn't work. It doesn't
|
||||||
|
# work with ActionMove either. So screw that, and accept anything.
|
||||||
|
return Qt.ActionMask
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 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 hashlib import md5
|
|
||||||
|
|
||||||
from PyQt4.QtGui import QDialog
|
|
||||||
|
|
||||||
from reg_submit_dialog import RegSubmitDialog
|
|
||||||
from reg_demo_dialog import RegDemoDialog
|
|
||||||
|
|
||||||
class Registration(object):
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def ask_for_code(self):
|
|
||||||
dialog = RegSubmitDialog(self.app.main_window, self.app.is_code_valid)
|
|
||||||
result = dialog.exec_()
|
|
||||||
code = unicode(dialog.codeEdit.text())
|
|
||||||
email = unicode(dialog.emailEdit.text())
|
|
||||||
dialog.setParent(None) # free it
|
|
||||||
if result == QDialog.Accepted and self.app.is_code_valid(code, email):
|
|
||||||
self.app.set_registration(code, email)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def show_nag(self):
|
|
||||||
dialog = RegDemoDialog(self.app.main_window, self)
|
|
||||||
dialog.exec_()
|
|
||||||
dialog.setParent(None) # free it
|
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-10
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 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 PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
|
|
||||||
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
|
|
||||||
|
|
||||||
from reg_demo_dialog_ui import Ui_RegDemoDialog
|
|
||||||
|
|
||||||
class RegDemoDialog(QDialog, Ui_RegDemoDialog):
|
|
||||||
def __init__(self, parent, reg):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self.reg = reg
|
|
||||||
self._setupUi()
|
|
||||||
|
|
||||||
self.connect(self.enterCodeButton, SIGNAL('clicked()'), self.enterCodeClicked)
|
|
||||||
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be setup in the Designer
|
|
||||||
appname = QCoreApplication.instance().applicationName()
|
|
||||||
title = self.windowTitle()
|
|
||||||
title = title.replace('$appname', appname)
|
|
||||||
self.setWindowTitle(title)
|
|
||||||
title = self.titleLabel.text()
|
|
||||||
title = title.replace('$appname', appname)
|
|
||||||
self.titleLabel.setText(title)
|
|
||||||
desc = self.demoDescLabel.text()
|
|
||||||
desc = desc.replace('$appname', appname)
|
|
||||||
self.demoDescLabel.setText(desc)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def enterCodeClicked(self):
|
|
||||||
if self.reg.ask_for_code():
|
|
||||||
self.accept()
|
|
||||||
|
|
||||||
def purchaseClicked(self):
|
|
||||||
url = QUrl('http://www.hardcoded.net/purchase.htm')
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>RegDemoDialog</class>
|
|
||||||
<widget class="QDialog" name="RegDemoDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>387</width>
|
|
||||||
<height>161</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>$appname Demo Version</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="titleLabel">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>$appname Demo Version</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="demoDescLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>You are currently running a demo version of $appname. This version has limited functionalities, and you need to buy it to have access to these functionalities.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="tryButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Try Demo</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="enterCodeButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Enter Code</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="purchaseButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Purchase</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>tryButton</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>RegDemoDialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>112</x>
|
|
||||||
<y>161</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>201</x>
|
|
||||||
<y>94</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 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 PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
|
|
||||||
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
|
|
||||||
|
|
||||||
from reg_submit_dialog_ui import Ui_RegSubmitDialog
|
|
||||||
|
|
||||||
class RegSubmitDialog(QDialog, Ui_RegSubmitDialog):
|
|
||||||
def __init__(self, parent, is_valid_func):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self._setupUi()
|
|
||||||
self.is_valid_func = is_valid_func
|
|
||||||
|
|
||||||
self.connect(self.submitButton, SIGNAL('clicked()'), self.submitClicked)
|
|
||||||
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be setup in the Designer
|
|
||||||
appname = QCoreApplication.instance().applicationName()
|
|
||||||
prompt = self.promptLabel.text()
|
|
||||||
prompt = prompt.replace('$appname', appname)
|
|
||||||
self.promptLabel.setText(prompt)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def purchaseClicked(self):
|
|
||||||
url = QUrl('http://www.hardcoded.net/purchase.htm')
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def submitClicked(self):
|
|
||||||
code = unicode(self.codeEdit.text())
|
|
||||||
email = unicode(self.emailEdit.text())
|
|
||||||
title = "Registration"
|
|
||||||
if self.is_valid_func(code, email):
|
|
||||||
msg = "This code is valid. Thanks!"
|
|
||||||
QMessageBox.information(self, title, msg)
|
|
||||||
self.accept()
|
|
||||||
else:
|
|
||||||
msg = "This code is invalid"
|
|
||||||
QMessageBox.warning(self, title, msg)
|
|
||||||
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>RegSubmitDialog</class>
|
|
||||||
<widget class="QDialog" name="RegSubmitDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>365</width>
|
|
||||||
<height>134</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Enter your registration code</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="promptLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Please enter your $appname registration code and registered e-mail (the e-mail you used for the purchase), then press "Submit".</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetNoConstraint</enum>
|
|
||||||
</property>
|
|
||||||
<property name="fieldGrowthPolicy">
|
|
||||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="labelAlignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="formAlignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Registration code:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Registered e-mail:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="codeEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="emailEdit"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="purchaseButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Purchase</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="cancelButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Cancel</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="submitButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Submit</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>cancelButton</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>RegSubmitDialog</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>260</x>
|
|
||||||
<y>159</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>198</x>
|
|
||||||
<y>97</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -153,7 +153,7 @@ class ResultsModel(TreeModel):
|
|||||||
if index.column() == 0:
|
if index.column() == 0:
|
||||||
value = unicode(value.toString())
|
value = unicode(value.toString())
|
||||||
if self._app.rename_dupe(node.dupe, value):
|
if self._app.rename_dupe(node.dupe, value):
|
||||||
node.reset()
|
node.invalidate()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
images/gear.png
BIN
images/gear.png
Binary file not shown.
|
Before Width: | Height: | Size: 394 B |
@@ -14,16 +14,10 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase
|
@interface AppDelegate : AppDelegateBase
|
||||||
{
|
{
|
||||||
IBOutlet NSButton *presetsButton;
|
|
||||||
IBOutlet NSPopUpButton *presetsPopup;
|
|
||||||
IBOutlet ResultWindow *result;
|
|
||||||
|
|
||||||
DirectoryPanel *_directoryPanel;
|
DirectoryPanel *_directoryPanel;
|
||||||
}
|
}
|
||||||
- (IBAction)openWebsite:(id)sender;
|
- (IBAction)openWebsite:(id)sender;
|
||||||
- (IBAction)popupPresets:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
- (IBAction)toggleDirectories:(id)sender;
|
||||||
- (IBAction)usePreset:(id)sender;
|
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel;
|
- (DirectoryPanel *)directoryPanel;
|
||||||
- (PyDupeGuru *)py;
|
- (PyDupeGuru *)py;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "cocoalib/Utils.h"
|
#import "cocoalib/Utils.h"
|
||||||
#import "cocoalib/ValueTransformers.h"
|
#import "cocoalib/ValueTransformers.h"
|
||||||
#import "cocoalib/Dialogs.h"
|
#import "cocoalib/Dialogs.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
@@ -60,50 +61,17 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_me"]];
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_me"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)popupPresets:(id)sender
|
|
||||||
{
|
|
||||||
[presetsPopup selectItem: nil];
|
|
||||||
[[presetsPopup cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
- (IBAction)toggleDirectories:(id)sender
|
||||||
{
|
{
|
||||||
[[self directoryPanel] toggleVisible:sender];
|
[[self directoryPanel] toggleVisible:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)usePreset:(id)sender
|
|
||||||
|
- (DetailsPanelBase *)detailsPanel
|
||||||
{
|
{
|
||||||
NSUserDefaultsController *ud = [NSUserDefaultsController sharedUserDefaultsController];
|
if (!_detailsPanel)
|
||||||
[ud revertToInitialValues:nil];
|
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
||||||
NSUserDefaults *d = [ud defaults];
|
return _detailsPanel;
|
||||||
switch ([sender tag])
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
[d setInteger:5 forKey:@"scanType"];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//case 1 is defaults
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
[d setInteger:2 forKey:@"scanType"];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
{
|
|
||||||
[d setInteger:0 forKey:@"scanType"];
|
|
||||||
[d setInteger:50 forKey:@"minMatchPercentage"];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 4:
|
|
||||||
{
|
|
||||||
[d setInteger:0 forKey:@"scanType"];
|
|
||||||
[d setInteger:50 forKey:@"minMatchPercentage"];
|
|
||||||
[d setBool:YES forKey:@"matchSimilarWords"];
|
|
||||||
[d setBool:YES forKey:@"wordWeighting"];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel
|
- (DirectoryPanel *)directoryPanel
|
||||||
@@ -115,23 +83,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
|
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
|
||||||
|
|
||||||
//Delegate
|
//Delegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[[ProgressController mainProgressController] setWorker:py];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
//Restore Columns
|
|
||||||
NSArray *columnsOrder = [ud arrayForKey:@"columnsOrder"];
|
|
||||||
NSDictionary *columnsWidth = [ud dictionaryForKey:@"columnsWidth"];
|
|
||||||
if ([columnsOrder count])
|
|
||||||
[result restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
//Reg stuff
|
|
||||||
if ([RegistrationInterface showNagWithApp:[self py] name:APPNAME limitDescription:LIMIT_DESC])
|
|
||||||
[unlockMenuItem setTitle:@"Thanks for buying dupeGuru ME!"];
|
|
||||||
//Restore results
|
|
||||||
[py loadIgnoreList];
|
|
||||||
[py loadResults];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
if (![[result window] isVisible])
|
if (![[result window] isVisible])
|
||||||
|
|||||||
18
me/cocoa/English.lproj/Details.nib/classes.nib
generated
18
me/cocoa/English.lproj/Details.nib/classes.nib
generated
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
IBClasses = (
|
|
||||||
{
|
|
||||||
CLASS = DetailsPanel;
|
|
||||||
LANGUAGE = ObjC;
|
|
||||||
OUTLETS = {detailsTable = NSTableView; };
|
|
||||||
SUPERCLASS = NSWindowController;
|
|
||||||
},
|
|
||||||
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
|
|
||||||
{
|
|
||||||
CLASS = TableView;
|
|
||||||
LANGUAGE = ObjC;
|
|
||||||
OUTLETS = {py = PyApp; };
|
|
||||||
SUPERCLASS = NSTableView;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
IBVersion = 1;
|
|
||||||
}
|
|
||||||
16
me/cocoa/English.lproj/Details.nib/info.nib
generated
16
me/cocoa/English.lproj/Details.nib/info.nib
generated
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBDocumentLocation</key>
|
|
||||||
<string>432 54 356 240 0 0 1024 746 </string>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>443.0</string>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>5</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>8I127</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
me/cocoa/English.lproj/Details.nib/keyedobjects.nib
generated
BIN
me/cocoa/English.lproj/Details.nib/keyedobjects.nib
generated
Binary file not shown.
64
me/cocoa/English.lproj/Directories.nib/classes.nib
generated
64
me/cocoa/English.lproj/Directories.nib/classes.nib
generated
@@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBClasses</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>FirstResponder</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>addiTunes</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>askForDirectory</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>changeDirectoryState</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>popupAddDirectoryMenu</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeSelectedDirectory</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleVisible</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>DirectoryPanel</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>addButtonPopUp</key>
|
|
||||||
<string>NSPopUpButton</string>
|
|
||||||
<key>directories</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
<key>removeButton</key>
|
|
||||||
<string>NSButton</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>DirectoryPanelBase</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>IBVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
20
me/cocoa/English.lproj/Directories.nib/info.nib
generated
20
me/cocoa/English.lproj/Directories.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>629</string>
|
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
|
||||||
<string>../../dupeguru.xcodeproj</string>
|
|
||||||
<key>IBOldestOS</key>
|
|
||||||
<integer>5</integer>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>5</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>9B18</string>
|
|
||||||
<key>targetFramework</key>
|
|
||||||
<string>IBCocoaFramework</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
me/cocoa/English.lproj/Directories.nib/keyedobjects.nib
generated
BIN
me/cocoa/English.lproj/Directories.nib/keyedobjects.nib
generated
Binary file not shown.
Binary file not shown.
257
me/cocoa/English.lproj/MainMenu.nib/classes.nib
generated
257
me/cocoa/English.lproj/MainMenu.nib/classes.nib
generated
@@ -1,257 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBClasses</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSControl</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>openWebsite</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>popupPresets</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDirectories</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>unlockApp</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>usePreset</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>AppDelegate</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>defaultsController</key>
|
|
||||||
<string>NSUserDefaultsController</string>
|
|
||||||
<key>presetsButton</key>
|
|
||||||
<string>NSButton</string>
|
|
||||||
<key>presetsPopup</key>
|
|
||||||
<string>NSPopUpButton</string>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>recentDirectories</key>
|
|
||||||
<string>RecentDirectories</string>
|
|
||||||
<key>result</key>
|
|
||||||
<string>ResultWindow</string>
|
|
||||||
<key>unlockMenuItem</key>
|
|
||||||
<string>NSMenuItem</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>AppDelegateBase</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>MatchesView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>PyDupeGuruBase</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>PyDupeGuruBase</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>changeDelta</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>changePowerMarker</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>clearIgnoreList</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>collapseAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>copyMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>deleteMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>expandAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>exportToXHTML</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>filter</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>ignoreSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markInvert</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markNone</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markToggle</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>moveMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>openSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>refresh</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeDeadTracks</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>renameSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>resetColumnsToDefault</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>revealSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>showPreferencesPanel</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>startDuplicateScan</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>switchSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleColumn</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDelta</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDetailsPanel</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>togglePowerMarker</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>ResultWindow</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>actionMenu</key>
|
|
||||||
<string>NSPopUpButton</string>
|
|
||||||
<key>actionMenuView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>app</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>columnsMenu</key>
|
|
||||||
<string>NSMenu</string>
|
|
||||||
<key>deltaSwitch</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>deltaSwitchView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>filterField</key>
|
|
||||||
<string>NSSearchField</string>
|
|
||||||
<key>filterFieldView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>matches</key>
|
|
||||||
<string>MatchesView</string>
|
|
||||||
<key>pmSwitch</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>pmSwitchView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>preferencesPanel</key>
|
|
||||||
<string>NSWindow</string>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>stats</key>
|
|
||||||
<string>NSTextField</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>ResultWindowBase</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>FirstResponder</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>checkForUpdates</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>SUUpdater</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>clearMenu</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>menuClick</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>RecentDirectories</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>delegate</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>menu</key>
|
|
||||||
<string>NSMenu</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>ResultWindowBase</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSWindowController</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>IBVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
20
me/cocoa/English.lproj/MainMenu.nib/info.nib
generated
20
me/cocoa/English.lproj/MainMenu.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>629</string>
|
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
|
||||||
<string>../../dupeguru.xcodeproj</string>
|
|
||||||
<key>IBOldestOS</key>
|
|
||||||
<integer>5</integer>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>598</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>9E17</string>
|
|
||||||
<key>targetFramework</key>
|
|
||||||
<string>IBCocoaFramework</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
me/cocoa/English.lproj/MainMenu.nib/keyedobjects.nib
generated
BIN
me/cocoa/English.lproj/MainMenu.nib/keyedobjects.nib
generated
Binary file not shown.
@@ -28,6 +28,8 @@
|
|||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>© Hardcoded Software, 2009</string>
|
||||||
<key>SUFeedURL</key>
|
<key>SUFeedURL</key>
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
|
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
|
||||||
<key>SUPublicDSAKeyFile</key>
|
<key>SUPublicDSAKeyFile</key>
|
||||||
|
|||||||
@@ -9,19 +9,13 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "cocoalib/Outline.h"
|
#import "cocoalib/Outline.h"
|
||||||
#import "dgbase/ResultWindow.h"
|
#import "dgbase/ResultWindow.h"
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase
|
@interface ResultWindow : ResultWindowBase
|
||||||
{
|
{
|
||||||
IBOutlet NSPopUpButton *actionMenu;
|
|
||||||
IBOutlet NSMenu *columnsMenu;
|
|
||||||
IBOutlet NSSearchField *filterField;
|
IBOutlet NSSearchField *filterField;
|
||||||
IBOutlet NSWindow *preferencesPanel;
|
|
||||||
|
|
||||||
NSString *_lastAction;
|
NSString *_lastAction;
|
||||||
DetailsPanel *_detailsPanel;
|
|
||||||
NSMutableArray *_resultColumns;
|
|
||||||
NSMutableIndexSet *_deltaColumns;
|
NSMutableIndexSet *_deltaColumns;
|
||||||
}
|
}
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
- (IBAction)clearIgnoreList:(id)sender;
|
||||||
@@ -38,15 +32,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)removeMarked:(id)sender;
|
- (IBAction)removeMarked:(id)sender;
|
||||||
- (IBAction)removeSelected:(id)sender;
|
- (IBAction)removeSelected:(id)sender;
|
||||||
- (IBAction)renameSelected:(id)sender;
|
- (IBAction)renameSelected:(id)sender;
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender;
|
|
||||||
- (IBAction)revealSelected:(id)sender;
|
- (IBAction)revealSelected:(id)sender;
|
||||||
- (IBAction)showPreferencesPanel:(id)sender;
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender;
|
- (IBAction)startDuplicateScan:(id)sender;
|
||||||
- (IBAction)toggleColumn:(id)sender;
|
|
||||||
- (IBAction)toggleDelta:(id)sender;
|
- (IBAction)toggleDelta:(id)sender;
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender;
|
|
||||||
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn;
|
|
||||||
- (void)initResultColumns;
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)awakeFromNib
|
- (void)awakeFromNib
|
||||||
{
|
{
|
||||||
[super awakeFromNib];
|
[super awakeFromNib];
|
||||||
_detailsPanel = nil;
|
[[self window] setTitle:@"dupeGuru Music Edition"];
|
||||||
_displayDelta = NO;
|
_displayDelta = NO;
|
||||||
_powerMode = NO;
|
_powerMode = NO;
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
|
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
|
||||||
@@ -29,23 +29,8 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
||||||
[matches setTarget:self];
|
[matches setTarget:self];
|
||||||
[matches setDoubleAction:@selector(openSelected:)];
|
[matches setDoubleAction:@selector(openSelected:)];
|
||||||
[[actionMenu itemAtIndex:0] setImage:[NSImage imageNamed: @"gear"]];
|
|
||||||
[self initResultColumns];
|
|
||||||
[self refreshStats];
|
[self refreshStats];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
||||||
|
|
||||||
NSToolbar *t = [[[NSToolbar alloc] initWithIdentifier:@"ResultWindowToolbar"] autorelease];
|
|
||||||
[t setAllowsUserCustomization:YES];
|
|
||||||
[t setAutosavesConfiguration:YES];
|
|
||||||
[t setDisplayMode:NSToolbarDisplayModeIconAndLabel];
|
|
||||||
[t setDelegate:self];
|
|
||||||
[[self window] setToolbar:t];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overrides */
|
|
||||||
- (NSString *)logoImageName
|
|
||||||
{
|
|
||||||
return @"dgme_logo_32";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -182,11 +167,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[py revealSelected];
|
[py revealSelected];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)showPreferencesPanel:(id)sender
|
|
||||||
{
|
|
||||||
[preferencesPanel makeKeyAndOrderFront:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender
|
- (IBAction)startDuplicateScan:(id)sender
|
||||||
{
|
{
|
||||||
if ([matches numberOfRows] > 0)
|
if ([matches numberOfRows] > 0)
|
||||||
@@ -219,26 +199,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleColumn:(id)sender
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = sender;
|
|
||||||
NSString *colId = [NSString stringWithFormat:@"%d",[mi tag]];
|
|
||||||
NSTableColumn *col = [matches tableColumnWithIdentifier:colId];
|
|
||||||
if (col == nil)
|
|
||||||
{
|
|
||||||
//Add Column
|
|
||||||
col = [_resultColumns objectAtIndex:[mi tag]];
|
|
||||||
[matches addTableColumn:col];
|
|
||||||
[mi setState:NSOnState];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Remove column
|
|
||||||
[matches removeTableColumn:col];
|
|
||||||
[mi setState:NSOffState];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDelta:(id)sender
|
- (IBAction)toggleDelta:(id)sender
|
||||||
{
|
{
|
||||||
if ([deltaSwitch selectedSegment] == 1)
|
if ([deltaSwitch selectedSegment] == 1)
|
||||||
@@ -248,39 +208,22 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[self changeDelta:sender];
|
[self changeDelta:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender
|
|
||||||
{
|
|
||||||
if (!_detailsPanel)
|
|
||||||
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
|
||||||
if ([[_detailsPanel window] isVisible])
|
|
||||||
[[_detailsPanel window] close];
|
|
||||||
else
|
|
||||||
[[_detailsPanel window] orderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn
|
|
||||||
{
|
|
||||||
NSNumber *n = [NSNumber numberWithInt:aIdentifier];
|
|
||||||
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[n stringValue]];
|
|
||||||
[col setWidth:aWidth];
|
|
||||||
[col setEditable:NO];
|
|
||||||
[[col dataCell] setFont:[[aColumn dataCell] font]];
|
|
||||||
[[col headerCell] setStringValue:aTitle];
|
|
||||||
[col setResizingMask:NSTableColumnUserResizingMask];
|
|
||||||
[col setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:[n stringValue] ascending:YES]];
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initResultColumns
|
- (void)initResultColumns
|
||||||
{
|
{
|
||||||
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
||||||
_resultColumns = [[NSMutableArray alloc] init];
|
_resultColumns = [[NSMutableArray alloc] init];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"2"]]; // Size
|
NSTableColumn *sizeCol = [self getColumnForIdentifier:2 title:@"Size (MB)" width:63 refCol:refCol];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"3"]]; // Time
|
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"4"]]; // Bitrate
|
[_resultColumns addObject:sizeCol];
|
||||||
|
NSTableColumn *timeCol = [self getColumnForIdentifier:3 title:@"Time" width:50 refCol:refCol];
|
||||||
|
[[timeCol dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
[_resultColumns addObject:timeCol];
|
||||||
|
NSTableColumn *brCol = [self getColumnForIdentifier:4 title:@"Bitrate" width:50 refCol:refCol];
|
||||||
|
[[brCol dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
[_resultColumns addObject:brCol];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Creation" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Creation" width:120 refCol:refCol]];
|
||||||
@@ -292,40 +235,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Year" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Year" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Track Number" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Track Number" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Comment" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Comment" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"16"]]; // Match %
|
[_resultColumns addObject:[self getColumnForIdentifier:16 title:@"Match %" width:57 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Words Used" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Words Used" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
|
|
||||||
{
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSNumber *width;
|
|
||||||
NSMenuItem *mi;
|
|
||||||
//Remove all columns
|
|
||||||
NSEnumerator *e = [[columnsMenu itemArray] objectEnumerator];
|
|
||||||
while (mi = [e nextObject])
|
|
||||||
{
|
|
||||||
if ([mi state] == NSOnState)
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
//Add columns and set widths
|
|
||||||
e = [aColumnsOrder objectEnumerator];
|
|
||||||
while (colId = [e nextObject])
|
|
||||||
{
|
|
||||||
if (![colId isEqual:@"mark"])
|
|
||||||
{
|
|
||||||
col = [_resultColumns objectAtIndex:[colId intValue]];
|
|
||||||
width = [aColumnsWidth objectForKey:[col identifier]];
|
|
||||||
mi = [columnsMenu itemWithTag:[colId intValue]];
|
|
||||||
if (width)
|
|
||||||
[col setWidth:[width floatValue]];
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
/* Delegate */
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
/* End PBXAppleScriptBuildPhase section */
|
/* End PBXAppleScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
|
|
||||||
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
|
|
||||||
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
||||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
|
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
|
||||||
@@ -29,7 +27,8 @@
|
|||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
||||||
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE3AA46509DB207900DB3A21 /* Directories.nib */; };
|
CE3FBDD31094637800B72D77 /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3FBDD11094637800B72D77 /* DetailsPanel.xib */; };
|
||||||
|
CE3FBDD41094637800B72D77 /* DirectoryPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */; };
|
||||||
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
||||||
CE49DEF70FDFEB810098617B /* NSCharacterSet_Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF50FDFEB810098617B /* NSCharacterSet_Extensions.m */; };
|
CE49DEF70FDFEB810098617B /* NSCharacterSet_Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF50FDFEB810098617B /* NSCharacterSet_Extensions.m */; };
|
||||||
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
|
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
|
||||||
@@ -51,13 +50,11 @@
|
|||||||
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
||||||
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */; };
|
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */; };
|
||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||||
CEA7D2C50FDFED340037CD8C /* dgme_logo_32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEA7D2C40FDFED340037CD8C /* dgme_logo_32.png */; };
|
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
|
||||||
CECA899909DB12CA00A3D774 /* Details.nib in Resources */ = {isa = PBXBuildFile; fileRef = CECA899709DB12CA00A3D774 /* Details.nib */; };
|
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
|
||||||
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
||||||
CED2A6880A05102700AC4C3F /* power_marker32.png in Resources */ = {isa = PBXBuildFile; fileRef = CED2A6870A05102600AC4C3F /* power_marker32.png */; };
|
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
||||||
CEF7823809C8AA0200EF38FF /* gear.png in Resources */ = {isa = PBXBuildFile; fileRef = CEF7823709C8AA0200EF38FF /* gear.png */; };
|
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
||||||
@@ -79,11 +76,9 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
|
||||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
||||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
||||||
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
|
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
|
||||||
29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
|
|
||||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
||||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
||||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
|
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -95,7 +90,8 @@
|
|||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = py/dist/dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = py/dist/dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
||||||
CE3AA46609DB207900DB3A21 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Directories.nib; sourceTree = "<group>"; };
|
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailsPanel.xib; sourceTree = "<group>"; };
|
||||||
|
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DirectoryPanel.xib; sourceTree = "<group>"; };
|
||||||
CE49DEF20FDFEB810098617B /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
|
CE49DEF20FDFEB810098617B /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE49DEF40FDFEB810098617B /* NSCharacterSet_Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSCharacterSet_Extensions.h; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.h; sourceTree = SOURCE_ROOT; };
|
CE49DEF40FDFEB810098617B /* NSCharacterSet_Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSCharacterSet_Extensions.h; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -136,13 +132,11 @@
|
|||||||
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = dgbase/dsa_pub.pem; sourceTree = "<group>"; };
|
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = dgbase/dsa_pub.pem; sourceTree = "<group>"; };
|
||||||
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
||||||
CEA7D2C40FDFED340037CD8C /* dgme_logo_32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = dgme_logo_32.png; path = images/dgme_logo_32.png; sourceTree = SOURCE_ROOT; };
|
CE900AD1109B238600754048 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = ../../xib/Preferences.xib; sourceTree = "<group>"; };
|
||||||
CECA899809DB12CA00A3D774 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Details.nib; sourceTree = "<group>"; };
|
CE900AD6109B2A9B00754048 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
||||||
CED2A6870A05102600AC4C3F /* power_marker32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = power_marker32.png; path = images/power_marker32.png; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
||||||
CEF7823709C8AA0200EF38FF /* gear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = gear.png; path = images/gear.png; sourceTree = "<group>"; };
|
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = images/folder32.png; sourceTree = SOURCE_ROOT; };
|
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = images/folder32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = images/details32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = images/details32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -231,16 +225,13 @@
|
|||||||
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE3FBDD01094637800B72D77 /* xib */,
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */,
|
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */,
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */,
|
CE381CF509915304003581CE /* dg_cocoa.plugin */,
|
||||||
CEFC294309C89E0000D9F998 /* images */,
|
CEFC294309C89E0000D9F998 /* images */,
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */,
|
CEEB135109C837A2004D2330 /* dupeguru.icns */,
|
||||||
8D1107310486CEB800E47090 /* Info.plist */,
|
8D1107310486CEB800E47090 /* Info.plist */,
|
||||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
|
|
||||||
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */,
|
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */,
|
||||||
CECA899709DB12CA00A3D774 /* Details.nib */,
|
|
||||||
CE3AA46509DB207900DB3A21 /* Directories.nib */,
|
|
||||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
|
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -254,6 +245,18 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
CE3FBDD01094637800B72D77 /* xib */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
CE900AD6109B2A9B00754048 /* MainMenu.xib */,
|
||||||
|
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */,
|
||||||
|
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */,
|
||||||
|
CE900AD1109B238600754048 /* Preferences.xib */,
|
||||||
|
);
|
||||||
|
name = xib;
|
||||||
|
path = dgbase/xib;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
CE49DEF10FDFEB810098617B /* brsinglelineformatter */ = {
|
CE49DEF10FDFEB810098617B /* brsinglelineformatter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -316,9 +319,6 @@
|
|||||||
CEFC294309C89E0000D9F998 /* images */ = {
|
CEFC294309C89E0000D9F998 /* images */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CEA7D2C40FDFED340037CD8C /* dgme_logo_32.png */,
|
|
||||||
CED2A6870A05102600AC4C3F /* power_marker32.png */,
|
|
||||||
CEF7823709C8AA0200EF38FF /* gear.png */,
|
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */,
|
CEFC295309C89FF200D9F998 /* details32.png */,
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */,
|
CEFC295409C89FF200D9F998 /* preferences32.png */,
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */,
|
CEFC294509C89E3D00D9F998 /* folder32.png */,
|
||||||
@@ -371,23 +371,20 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */,
|
|
||||||
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
|
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
|
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */,
|
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */,
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
|
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
|
||||||
CEF7823809C8AA0200EF38FF /* gear.png in Resources */,
|
|
||||||
CECA899909DB12CA00A3D774 /* Details.nib in Resources */,
|
|
||||||
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */,
|
|
||||||
CED2A6880A05102700AC4C3F /* power_marker32.png in Resources */,
|
|
||||||
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */,
|
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */,
|
||||||
CE515E030FC6C13E00EC695D /* progress.nib in Resources */,
|
CE515E030FC6C13E00EC695D /* progress.nib in Resources */,
|
||||||
CE515E040FC6C13E00EC695D /* registration.nib in Resources */,
|
CE515E040FC6C13E00EC695D /* registration.nib in Resources */,
|
||||||
CEA7D2C50FDFED340037CD8C /* dgme_logo_32.png in Resources */,
|
|
||||||
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */,
|
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */,
|
||||||
|
CE3FBDD31094637800B72D77 /* DetailsPanel.xib in Resources */,
|
||||||
|
CE3FBDD41094637800B72D77 /* DirectoryPanel.xib in Resources */,
|
||||||
|
CE900AD2109B238600754048 /* Preferences.xib in Resources */,
|
||||||
|
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -424,30 +421,6 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
089C165DFE840E0CC02AAC07 /* English */,
|
|
||||||
);
|
|
||||||
name = InfoPlist.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
29B97319FDCFA39411CA2CEA /* English */,
|
|
||||||
);
|
|
||||||
name = MainMenu.nib;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
CE3AA46509DB207900DB3A21 /* Directories.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CE3AA46609DB207900DB3A21 /* English */,
|
|
||||||
);
|
|
||||||
name = Directories.nib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */ = {
|
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -472,38 +445,9 @@
|
|||||||
name = registration.nib;
|
name = registration.nib;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
};
|
};
|
||||||
CECA899709DB12CA00A3D774 /* Details.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CECA899809DB12CA00A3D774 /* English */,
|
|
||||||
);
|
|
||||||
name = Details.nib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
C01FCF4B08A954540054247B /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
|
||||||
"$(FRAMEWORK_SEARCH_PATHS)",
|
|
||||||
"$(SRCROOT)/../../../cocoalib/build/Release",
|
|
||||||
"\"$(SRCROOT)/../../base/cocoa/build/Release\"",
|
|
||||||
);
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
|
||||||
GCC_MODEL_TUNING = G5;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
INFOPLIST_FILE = Info.plist;
|
|
||||||
INSTALL_PATH = "$(HOME)/Applications";
|
|
||||||
PRODUCT_NAME = dupeGuru;
|
|
||||||
WRAPPER_EXTENSION = app;
|
|
||||||
ZERO_LINK = YES;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
C01FCF4C08A954540054247B /* Release */ = {
|
C01FCF4C08A954540054247B /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -521,32 +465,16 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
C01FCF4F08A954540054247B /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
GCC_C_LANGUAGE_STANDARD = c99;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.4;
|
|
||||||
PREBINDING = NO;
|
|
||||||
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
C01FCF5008A954540054247B /* Release */ = {
|
C01FCF5008A954540054247B /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)";
|
ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)";
|
||||||
ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386";
|
ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386";
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = c99;
|
GCC_C_LANGUAGE_STANDARD = c99;
|
||||||
GCC_VERSION = 4.0;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.4;
|
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||||
PREBINDING = NO;
|
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
|
||||||
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
|
|
||||||
STRIP_INSTALLED_PRODUCT = NO;
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -556,7 +484,6 @@
|
|||||||
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
|
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
C01FCF4B08A954540054247B /* Debug */,
|
|
||||||
C01FCF4C08A954540054247B /* Release */,
|
C01FCF4C08A954540054247B /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -565,7 +492,6 @@
|
|||||||
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
|
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
C01FCF4F08A954540054247B /* Debug */,
|
|
||||||
C01FCF5008A954540054247B /* Release */,
|
C01FCF5008A954540054247B /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
|
|||||||
@@ -8,12 +8,13 @@
|
|||||||
import objc
|
import objc
|
||||||
from AppKit import *
|
from AppKit import *
|
||||||
|
|
||||||
from dupeguru import app_me_cocoa, scanner
|
from dupeguru_me.app_cocoa import DupeGuruME
|
||||||
|
from dupeguru.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
||||||
|
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
||||||
|
|
||||||
# Fix py2app imports which chokes on relative imports
|
# Fix py2app imports which chokes on relative imports
|
||||||
from dupeguru import app, app_cocoa, data, directories, engine, export, ignore, results, scanner
|
from dupeguru_me import app_cocoa, data, fs, scanner
|
||||||
from hsfs import auto, stats, tree, music
|
from dupeguru import app, app_cocoa, data, directories, engine, export, ignore, results, scanner, fs
|
||||||
from hsfs.phys import music
|
|
||||||
from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
||||||
from hsutil import conflict
|
from hsutil import conflict
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ class PyApp(NSObject):
|
|||||||
class PyDupeGuru(PyApp):
|
class PyDupeGuru(PyApp):
|
||||||
def init(self):
|
def init(self):
|
||||||
self = super(PyDupeGuru,self).init()
|
self = super(PyDupeGuru,self).init()
|
||||||
self.app = app_me_cocoa.DupeGuruME()
|
self.app = DupeGuruME()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
#---Directories
|
#---Directories
|
||||||
@@ -180,12 +181,12 @@ class PyDupeGuru(PyApp):
|
|||||||
def setScanType_(self, scan_type):
|
def setScanType_(self, scan_type):
|
||||||
try:
|
try:
|
||||||
self.app.scanner.scan_type = [
|
self.app.scanner.scan_type = [
|
||||||
scanner.SCAN_TYPE_FILENAME,
|
SCAN_TYPE_FILENAME,
|
||||||
scanner.SCAN_TYPE_FIELDS,
|
SCAN_TYPE_FIELDS,
|
||||||
scanner.SCAN_TYPE_FIELDS_NO_ORDER,
|
SCAN_TYPE_FIELDS_NO_ORDER,
|
||||||
scanner.SCAN_TYPE_TAG,
|
SCAN_TYPE_TAG,
|
||||||
scanner.SCAN_TYPE_CONTENT,
|
SCAN_TYPE_CONTENT,
|
||||||
scanner.SCAN_TYPE_CONTENT_AUDIO
|
SCAN_TYPE_CONTENT_AUDIO
|
||||||
][scan_type]
|
][scan_type]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ if op.exists('build'):
|
|||||||
if op.exists('dist'):
|
if op.exists('dist'):
|
||||||
shutil.rmtree('dist')
|
shutil.rmtree('dist')
|
||||||
|
|
||||||
|
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.5'
|
||||||
print_and_do('python -u setup.py py2app')
|
print_and_do('python -u setup.py py2app')
|
||||||
2611
me/cocoa/xib/Preferences.xib
Normal file
2611
me/cocoa/xib/Preferences.xib
Normal file
File diff suppressed because it is too large
Load Diff
0
me/py/__init__.py
Normal file
0
me/py/__init__.py
Normal file
@@ -7,29 +7,29 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import os.path as op
|
|
||||||
import logging
|
import logging
|
||||||
from appscript import app, k, CommandError
|
from appscript import app, k, CommandError
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from hsutil.cocoa import as_fetch
|
from hsutil.cocoa import as_fetch
|
||||||
import hsfs.phys.music
|
|
||||||
|
|
||||||
import app_cocoa, data_me, scanner
|
from dupeguru.app_cocoa import JOBID2TITLE, DupeGuru as DupeGuruBase
|
||||||
|
|
||||||
|
from . import data, scanner, fs
|
||||||
|
|
||||||
JOB_REMOVE_DEAD_TRACKS = 'jobRemoveDeadTracks'
|
JOB_REMOVE_DEAD_TRACKS = 'jobRemoveDeadTracks'
|
||||||
JOB_SCAN_DEAD_TRACKS = 'jobScanDeadTracks'
|
JOB_SCAN_DEAD_TRACKS = 'jobScanDeadTracks'
|
||||||
|
|
||||||
app_cocoa.JOBID2TITLE.update({
|
JOBID2TITLE.update({
|
||||||
JOB_REMOVE_DEAD_TRACKS: "Removing dead tracks from your iTunes Library",
|
JOB_REMOVE_DEAD_TRACKS: "Removing dead tracks from your iTunes Library",
|
||||||
JOB_SCAN_DEAD_TRACKS: "Scanning the iTunes Library",
|
JOB_SCAN_DEAD_TRACKS: "Scanning the iTunes Library",
|
||||||
})
|
})
|
||||||
|
|
||||||
class DupeGuruME(app_cocoa.DupeGuru):
|
class DupeGuruME(DupeGuruBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
app_cocoa.DupeGuru.__init__(self, data_me, 'dupeGuru Music Edition', appid=1)
|
DupeGuruBase.__init__(self, data, 'dupeGuru Music Edition', appid=1)
|
||||||
self.scanner = scanner.ScannerME()
|
self.scanner = scanner.ScannerME()
|
||||||
self.directories.dirclass = hsfs.phys.music.Directory
|
self.directories.fileclasses = [fs.Mp3File, fs.Mp4File, fs.WmaFile, fs.OggFile, fs.FlacFile, fs.AiffFile]
|
||||||
self.dead_tracks = []
|
self.dead_tracks = []
|
||||||
|
|
||||||
def remove_dead_tracks(self):
|
def remove_dead_tracks(self):
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsutil.str import format_time, FT_MINUTES, format_size
|
from hsutil.str import format_time, FT_MINUTES, format_size
|
||||||
from .data import (format_path, format_timestamp, format_words, format_perc,
|
from dupeguru.data import (format_path, format_timestamp, format_words, format_perc,
|
||||||
format_dupe_count, cmp_value)
|
format_dupe_count, cmp_value)
|
||||||
|
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
@@ -76,7 +76,7 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
str(dupe.track),
|
str(dupe.track),
|
||||||
dupe.comment,
|
dupe.comment,
|
||||||
format_perc(percentage),
|
format_perc(percentage),
|
||||||
format_words(dupe.words),
|
format_words(dupe.words) if hasattr(dupe, 'words') else '',
|
||||||
format_dupe_count(dupe_count)
|
format_dupe_count(dupe_count)
|
||||||
]
|
]
|
||||||
|
|
||||||
183
me/py/fs.py
Normal file
183
me/py/fs.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-10-23
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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 hsmedia import mpeg, wma, mp4, ogg, flac, aiff
|
||||||
|
from hsutil.str import get_file_ext
|
||||||
|
from dupeguru import fs
|
||||||
|
|
||||||
|
TAG_FIELDS = ['audiosize', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
|
||||||
|
'album', 'genre', 'year', 'track', 'comment']
|
||||||
|
|
||||||
|
class MusicFile(fs.File):
|
||||||
|
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
|
||||||
|
INITIAL_INFO.update({
|
||||||
|
'audiosize': 0,
|
||||||
|
'bitrate' : 0,
|
||||||
|
'duration' : 0,
|
||||||
|
'samplerate':0,
|
||||||
|
'artist' : '',
|
||||||
|
'album' : '',
|
||||||
|
'title' : '',
|
||||||
|
'genre' : '',
|
||||||
|
'comment' : '',
|
||||||
|
'year' : '',
|
||||||
|
'track' : 0,
|
||||||
|
})
|
||||||
|
HANDLED_EXTS = set()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_handle(cls, path):
|
||||||
|
if not fs.File.can_handle(path):
|
||||||
|
return False
|
||||||
|
return get_file_ext(path[-1]) in cls.HANDLED_EXTS
|
||||||
|
|
||||||
|
|
||||||
|
class Mp3File(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['mp3'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
fileinfo = mpeg.Mpeg(unicode(self.path))
|
||||||
|
self._md5partial_offset = fileinfo.audio_offset
|
||||||
|
self._md5partial_size = fileinfo.audio_size
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
fileinfo = mpeg.Mpeg(unicode(self.path))
|
||||||
|
self.audiosize = fileinfo.audio_size
|
||||||
|
self.bitrate = fileinfo.bitrate
|
||||||
|
self.duration = fileinfo.duration
|
||||||
|
self.samplerate = fileinfo.sample_rate
|
||||||
|
i1 = fileinfo.id3v1
|
||||||
|
# id3v1, even when non-existant, gives empty values. not id3v2. if id3v2 don't exist,
|
||||||
|
# just replace it with id3v1
|
||||||
|
i2 = fileinfo.id3v2
|
||||||
|
if not i2.exists:
|
||||||
|
i2 = i1
|
||||||
|
self.artist = i2.artist or i1.artist
|
||||||
|
self.album = i2.album or i1.album
|
||||||
|
self.title = i2.title or i1.title
|
||||||
|
self.genre = i2.genre or i1.genre
|
||||||
|
self.comment = i2.comment or i1.comment
|
||||||
|
self.year = i2.year or i1.year
|
||||||
|
self.track = i2.track or i1.track
|
||||||
|
|
||||||
|
class WmaFile(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['wma'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
dec = wma.WMADecoder(unicode(self.path))
|
||||||
|
self._md5partial_offset = dec.audio_offset
|
||||||
|
self._md5partial_size = dec.audio_size
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
dec = wma.WMADecoder(unicode(self.path))
|
||||||
|
self.audiosize = dec.audio_size
|
||||||
|
self.bitrate = dec.bitrate
|
||||||
|
self.duration = dec.duration
|
||||||
|
self.samplerate = dec.sample_rate
|
||||||
|
self.artist = dec.artist
|
||||||
|
self.album = dec.album
|
||||||
|
self.title = dec.title
|
||||||
|
self.genre = dec.genre
|
||||||
|
self.comment = dec.comment
|
||||||
|
self.year = dec.year
|
||||||
|
self.track = dec.track
|
||||||
|
|
||||||
|
class Mp4File(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['m4a', 'm4p'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
dec = mp4.File(unicode(self.path))
|
||||||
|
self._md5partial_offset = dec.audio_offset
|
||||||
|
self._md5partial_size = dec.audio_size
|
||||||
|
dec.close()
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
dec = mp4.File(unicode(self.path))
|
||||||
|
self.audiosize = dec.audio_size
|
||||||
|
self.bitrate = dec.bitrate
|
||||||
|
self.duration = dec.duration
|
||||||
|
self.samplerate = dec.sample_rate
|
||||||
|
self.artist = dec.artist
|
||||||
|
self.album = dec.album
|
||||||
|
self.title = dec.title
|
||||||
|
self.genre = dec.genre
|
||||||
|
self.comment = dec.comment
|
||||||
|
self.year = dec.year
|
||||||
|
self.track = dec.track
|
||||||
|
dec.close()
|
||||||
|
|
||||||
|
class OggFile(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['ogg'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
dec = ogg.Vorbis(unicode(self.path))
|
||||||
|
self._md5partial_offset = dec.audio_offset
|
||||||
|
self._md5partial_size = dec.audio_size
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
dec = ogg.Vorbis(unicode(self.path))
|
||||||
|
self.audiosize = dec.audio_size
|
||||||
|
self.bitrate = dec.bitrate
|
||||||
|
self.duration = dec.duration
|
||||||
|
self.samplerate = dec.sample_rate
|
||||||
|
self.artist = dec.artist
|
||||||
|
self.album = dec.album
|
||||||
|
self.title = dec.title
|
||||||
|
self.genre = dec.genre
|
||||||
|
self.comment = dec.comment
|
||||||
|
self.year = dec.year
|
||||||
|
self.track = dec.track
|
||||||
|
|
||||||
|
class FlacFile(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['flac'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
dec = flac.FLAC(unicode(self.path))
|
||||||
|
self._md5partial_offset = dec.audio_offset
|
||||||
|
self._md5partial_size = dec.audio_size
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
dec = flac.FLAC(unicode(self.path))
|
||||||
|
self.audiosize = dec.audio_size
|
||||||
|
self.bitrate = dec.bitrate
|
||||||
|
self.duration = dec.duration
|
||||||
|
self.samplerate = dec.sample_rate
|
||||||
|
self.artist = dec.artist
|
||||||
|
self.album = dec.album
|
||||||
|
self.title = dec.title
|
||||||
|
self.genre = dec.genre
|
||||||
|
self.comment = dec.comment
|
||||||
|
self.year = dec.year
|
||||||
|
self.track = dec.track
|
||||||
|
|
||||||
|
class AiffFile(MusicFile):
|
||||||
|
HANDLED_EXTS = set(['aif', 'aiff', 'aifc'])
|
||||||
|
def _read_info(self, field):
|
||||||
|
if field == 'md5partial':
|
||||||
|
dec = aiff.File(unicode(self.path))
|
||||||
|
self._md5partial_offset = dec.audio_offset
|
||||||
|
self._md5partial_size = dec.audio_size
|
||||||
|
MusicFile._read_info(self, field)
|
||||||
|
if field in TAG_FIELDS:
|
||||||
|
dec = aiff.File(unicode(self.path))
|
||||||
|
self.audiosize = dec.audio_size
|
||||||
|
self.bitrate = dec.bitrate
|
||||||
|
self.duration = dec.duration
|
||||||
|
self.samplerate = dec.sample_rate
|
||||||
|
tag = dec.tag
|
||||||
|
if tag is not None:
|
||||||
|
self.artist = tag.artist
|
||||||
|
self.album = tag.album
|
||||||
|
self.title = tag.title
|
||||||
|
self.genre = tag.genre
|
||||||
|
self.comment = tag.comment
|
||||||
|
self.year = tag.year
|
||||||
|
self.track = tag.track
|
||||||
|
|
||||||
16
me/py/scanner.py
Normal file
16
me/py/scanner.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/03/03
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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 dupeguru.scanner import Scanner as ScannerBase
|
||||||
|
|
||||||
|
class ScannerME(ScannerBase):
|
||||||
|
@staticmethod
|
||||||
|
def _key_func(dupe):
|
||||||
|
return (not dupe.is_ref, -dupe.bitrate, -dupe.size)
|
||||||
|
|
||||||
0
me/py/tests/__init__.py
Normal file
0
me/py/tests/__init__.py
Normal file
33
me/py/tests/scanner_test.py
Normal file
33
me/py/tests/scanner_test.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-10-23
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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.path import Path
|
||||||
|
|
||||||
|
from dupeguru.engine import getwords
|
||||||
|
from ..scanner import *
|
||||||
|
|
||||||
|
class NamedObject(object):
|
||||||
|
def __init__(self, name="foobar", size=1):
|
||||||
|
self.name = name
|
||||||
|
self.size = size
|
||||||
|
self.path = Path('')
|
||||||
|
self.words = getwords(name)
|
||||||
|
|
||||||
|
|
||||||
|
no = NamedObject
|
||||||
|
|
||||||
|
def test_priorize_me():
|
||||||
|
# in ScannerME, bitrate goes first (right after is_ref) in priorization
|
||||||
|
s = ScannerME()
|
||||||
|
o1, o2 = no('foo'), no('foo')
|
||||||
|
o1.bitrate = 1
|
||||||
|
o2.bitrate = 2
|
||||||
|
[group] = s.GetDupeGroups([o1, o2])
|
||||||
|
assert group.ref is o2
|
||||||
@@ -7,9 +7,7 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import hsfs.phys.music
|
from dupeguru_me import data, scanner, fs
|
||||||
|
|
||||||
from dupeguru import data_me, scanner
|
|
||||||
|
|
||||||
from base.app import DupeGuru as DupeGuruBase
|
from base.app import DupeGuru as DupeGuruBase
|
||||||
from details_dialog import DetailsDialog
|
from details_dialog import DetailsDialog
|
||||||
@@ -23,11 +21,11 @@ class DupeGuru(DupeGuruBase):
|
|||||||
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
|
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DupeGuruBase.__init__(self, data_me, appid=1)
|
DupeGuruBase.__init__(self, data, appid=1)
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
self.scanner = scanner.ScannerME()
|
self.scanner = scanner.ScannerME()
|
||||||
self.directories.dirclass = hsfs.phys.music.Directory
|
self.directories.fileclasses = [fs.Mp3File, fs.Mp4File, fs.WmaFile, fs.OggFile, fs.FlacFile, fs.AiffFile]
|
||||||
DupeGuruBase._setup(self)
|
DupeGuruBase._setup(self)
|
||||||
|
|
||||||
def _update_options(self):
|
def _update_options(self):
|
||||||
|
|||||||
@@ -8,20 +8,14 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "dgbase/AppDelegate.h"
|
#import "dgbase/AppDelegate.h"
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase
|
@interface AppDelegate : AppDelegateBase
|
||||||
{
|
{
|
||||||
IBOutlet ResultWindow *result;
|
|
||||||
|
|
||||||
DetailsPanel *_detailsPanel;
|
|
||||||
DirectoryPanel *_directoryPanel;
|
DirectoryPanel *_directoryPanel;
|
||||||
}
|
}
|
||||||
- (IBAction)openWebsite:(id)sender;
|
- (IBAction)openWebsite:(id)sender;
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
- (IBAction)toggleDirectories:(id)sender;
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel;
|
- (DirectoryPanel *)directoryPanel;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "ValueTransformers.h"
|
#import "ValueTransformers.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
+ (void)initialize
|
+ (void)initialize
|
||||||
@@ -36,29 +37,22 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
_directoryPanel = nil;
|
_directoryPanel = nil;
|
||||||
_detailsPanel = nil;
|
|
||||||
_appName = APPNAME;
|
_appName = APPNAME;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (DetailsPanelBase *)detailsPanel
|
||||||
|
{
|
||||||
|
if (!_detailsPanel)
|
||||||
|
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
||||||
|
return _detailsPanel;
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)openWebsite:(id)sender
|
- (IBAction)openWebsite:(id)sender
|
||||||
{
|
{
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_pe"]];
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_pe"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender
|
|
||||||
{
|
|
||||||
if (!_detailsPanel)
|
|
||||||
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
|
||||||
if ([[_detailsPanel window] isVisible])
|
|
||||||
[[_detailsPanel window] close];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[[_detailsPanel window] orderFront:nil];
|
|
||||||
[_detailsPanel refresh];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
- (IBAction)toggleDirectories:(id)sender
|
||||||
{
|
{
|
||||||
[[self directoryPanel] toggleVisible:sender];
|
[[self directoryPanel] toggleVisible:sender];
|
||||||
@@ -75,19 +69,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
//Delegate
|
//Delegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
[[ProgressController mainProgressController] setWorker:py];
|
NSMenu *actionsMenu = [[[NSApp mainMenu] itemWithTitle:@"Actions"] submenu];
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
// index 2 is just after "Clear Ingore List"
|
||||||
//Restore Columns
|
NSMenuItem *mi = [actionsMenu insertItemWithTitle:@"Clear Picture Cache" action:@selector(clearPictureCache:) keyEquivalent:@"P" atIndex:2];
|
||||||
NSArray *columnsOrder = [ud arrayForKey:@"columnsOrder"];
|
[mi setTarget:result];
|
||||||
NSDictionary *columnsWidth = [ud dictionaryForKey:@"columnsWidth"];
|
[mi setKeyEquivalentModifierMask:NSCommandKeyMask|NSShiftKeyMask];
|
||||||
if ([columnsOrder count])
|
[super applicationDidFinishLaunching:aNotification];
|
||||||
[result restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
//Reg stuff
|
|
||||||
if ([RegistrationInterface showNagWithApp:[self py] name:APPNAME limitDescription:LIMIT_DESC])
|
|
||||||
[unlockMenuItem setTitle:@"Thanks for buying dupeGuru Picture Edition!"];
|
|
||||||
//Restore results
|
|
||||||
[py loadIgnoreList];
|
|
||||||
[py loadResults];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
||||||
|
|||||||
24
pe/cocoa/English.lproj/Details.nib/classes.nib
generated
24
pe/cocoa/English.lproj/Details.nib/classes.nib
generated
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
IBClasses = (
|
|
||||||
{
|
|
||||||
CLASS = DetailsPanel;
|
|
||||||
LANGUAGE = ObjC;
|
|
||||||
OUTLETS = {
|
|
||||||
detailsTable = NSTableView;
|
|
||||||
dupeImage = NSImageView;
|
|
||||||
dupeProgressIndicator = NSProgressIndicator;
|
|
||||||
refImage = NSImageView;
|
|
||||||
refProgressIndicator = NSProgressIndicator;
|
|
||||||
};
|
|
||||||
SUPERCLASS = NSWindowController;
|
|
||||||
},
|
|
||||||
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
|
|
||||||
{
|
|
||||||
CLASS = TableView;
|
|
||||||
LANGUAGE = ObjC;
|
|
||||||
OUTLETS = {py = PyApp; };
|
|
||||||
SUPERCLASS = NSTableView;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
IBVersion = 1;
|
|
||||||
}
|
|
||||||
16
pe/cocoa/English.lproj/Details.nib/info.nib
generated
16
pe/cocoa/English.lproj/Details.nib/info.nib
generated
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBDocumentLocation</key>
|
|
||||||
<string>701 68 356 240 0 0 1440 878 </string>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>446.1</string>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>5</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>8R2232</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
pe/cocoa/English.lproj/Details.nib/keyedobjects.nib
generated
BIN
pe/cocoa/English.lproj/Details.nib/keyedobjects.nib
generated
Binary file not shown.
62
pe/cocoa/English.lproj/Directories.nib/classes.nib
generated
62
pe/cocoa/English.lproj/Directories.nib/classes.nib
generated
@@ -1,62 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBClasses</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>FirstResponder</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>askForDirectory</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>changeDirectoryState</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>popupAddDirectoryMenu</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeSelectedDirectory</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleVisible</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>DirectoryPanel</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>addButtonPopUp</key>
|
|
||||||
<string>NSPopUpButton</string>
|
|
||||||
<key>directories</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
<key>removeButton</key>
|
|
||||||
<string>NSButton</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>DirectoryPanelBase</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>IBVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
20
pe/cocoa/English.lproj/Directories.nib/info.nib
generated
20
pe/cocoa/English.lproj/Directories.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>629</string>
|
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
|
||||||
<string>../../dupeguru.xcodeproj</string>
|
|
||||||
<key>IBOldestOS</key>
|
|
||||||
<integer>5</integer>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>5</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>9B18</string>
|
|
||||||
<key>targetFramework</key>
|
|
||||||
<string>IBCocoaFramework</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
pe/cocoa/English.lproj/Directories.nib/keyedobjects.nib
generated
BIN
pe/cocoa/English.lproj/Directories.nib/keyedobjects.nib
generated
Binary file not shown.
Binary file not shown.
235
pe/cocoa/English.lproj/MainMenu.nib/classes.nib
generated
235
pe/cocoa/English.lproj/MainMenu.nib/classes.nib
generated
@@ -1,235 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBClasses</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSControl</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>openWebsite</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDetailsPanel</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDirectories</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>unlockApp</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>AppDelegate</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>recentDirectories</key>
|
|
||||||
<string>RecentDirectories</string>
|
|
||||||
<key>result</key>
|
|
||||||
<string>ResultWindow</string>
|
|
||||||
<key>unlockMenuItem</key>
|
|
||||||
<string>NSMenuItem</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>MatchesView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>changeDelta</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>changePowerMarker</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>clearIgnoreList</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>clearPictureCache</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>collapseAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>copyMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>deleteMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>expandAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>exportToXHTML</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>filter</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>ignoreSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markAll</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markInvert</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markNone</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>markToggle</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>moveMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>openSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>refresh</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeMarked</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>removeSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>renameSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>resetColumnsToDefault</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>revealSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>showPreferencesPanel</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>startDuplicateScan</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>switchSelected</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleColumn</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDelta</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDetailsPanel</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>toggleDirectories</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>togglePowerMarker</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>ResultWindow</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>actionMenu</key>
|
|
||||||
<string>NSPopUpButton</string>
|
|
||||||
<key>actionMenuView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>app</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>columnsMenu</key>
|
|
||||||
<string>NSMenu</string>
|
|
||||||
<key>deltaSwitch</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>deltaSwitchView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>filterField</key>
|
|
||||||
<string>NSSearchField</string>
|
|
||||||
<key>filterFieldView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>matches</key>
|
|
||||||
<string>MatchesView</string>
|
|
||||||
<key>pmSwitch</key>
|
|
||||||
<string>NSSegmentedControl</string>
|
|
||||||
<key>pmSwitchView</key>
|
|
||||||
<string>NSView</string>
|
|
||||||
<key>preferencesPanel</key>
|
|
||||||
<string>NSWindow</string>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyDupeGuru</string>
|
|
||||||
<key>stats</key>
|
|
||||||
<string>NSTextField</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSWindowController</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>FirstResponder</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>checkForUpdates</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>SUUpdater</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>clearMenu</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>menuClick</key>
|
|
||||||
<string>id</string>
|
|
||||||
</dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>RecentDirectories</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>delegate</key>
|
|
||||||
<string>id</string>
|
|
||||||
<key>menu</key>
|
|
||||||
<string>NSMenu</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSObject</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CLASS</key>
|
|
||||||
<string>OutlineView</string>
|
|
||||||
<key>LANGUAGE</key>
|
|
||||||
<string>ObjC</string>
|
|
||||||
<key>OUTLETS</key>
|
|
||||||
<dict>
|
|
||||||
<key>py</key>
|
|
||||||
<string>PyApp</string>
|
|
||||||
</dict>
|
|
||||||
<key>SUPERCLASS</key>
|
|
||||||
<string>NSOutlineView</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>IBVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
20
pe/cocoa/English.lproj/MainMenu.nib/info.nib
generated
20
pe/cocoa/English.lproj/MainMenu.nib/info.nib
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IBFramework Version</key>
|
|
||||||
<string>629</string>
|
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
|
||||||
<string>../../dupeguru.xcodeproj</string>
|
|
||||||
<key>IBOldestOS</key>
|
|
||||||
<integer>4</integer>
|
|
||||||
<key>IBOpenObjects</key>
|
|
||||||
<array>
|
|
||||||
<integer>524</integer>
|
|
||||||
</array>
|
|
||||||
<key>IBSystem Version</key>
|
|
||||||
<string>9B18</string>
|
|
||||||
<key>targetFramework</key>
|
|
||||||
<string>IBCocoaFramework</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
pe/cocoa/English.lproj/MainMenu.nib/keyedobjects.nib
generated
BIN
pe/cocoa/English.lproj/MainMenu.nib/keyedobjects.nib
generated
Binary file not shown.
@@ -23,11 +23,13 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>hsft</string>
|
<string>hsft</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.7.7</string>
|
<string>1.8.0</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>© Hardcoded Software, 2009</string>
|
||||||
<key>SUFeedURL</key>
|
<key>SUFeedURL</key>
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru_pe.appcast</string>
|
<string>http://www.hardcoded.net/updates/dupeguru_pe.appcast</string>
|
||||||
<key>SUPublicDSAKeyFile</key>
|
<key>SUPublicDSAKeyFile</key>
|
||||||
|
|||||||
@@ -9,16 +9,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "Outline.h"
|
#import "Outline.h"
|
||||||
#import "dgbase/ResultWindow.h"
|
#import "dgbase/ResultWindow.h"
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase
|
@interface ResultWindow : ResultWindowBase
|
||||||
{
|
{
|
||||||
IBOutlet NSPopUpButton *actionMenu;
|
|
||||||
IBOutlet NSMenu *columnsMenu;
|
|
||||||
IBOutlet NSSearchField *filterField;
|
IBOutlet NSSearchField *filterField;
|
||||||
IBOutlet NSWindow *preferencesPanel;
|
|
||||||
|
|
||||||
NSMutableArray *_resultColumns;
|
|
||||||
NSMutableIndexSet *_deltaColumns;
|
NSMutableIndexSet *_deltaColumns;
|
||||||
}
|
}
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
- (IBAction)clearIgnoreList:(id)sender;
|
||||||
@@ -35,16 +30,8 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)removeMarked:(id)sender;
|
- (IBAction)removeMarked:(id)sender;
|
||||||
- (IBAction)removeSelected:(id)sender;
|
- (IBAction)removeSelected:(id)sender;
|
||||||
- (IBAction)renameSelected:(id)sender;
|
- (IBAction)renameSelected:(id)sender;
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender;
|
|
||||||
- (IBAction)revealSelected:(id)sender;
|
- (IBAction)revealSelected:(id)sender;
|
||||||
- (IBAction)showPreferencesPanel:(id)sender;
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender;
|
- (IBAction)startDuplicateScan:(id)sender;
|
||||||
- (IBAction)toggleColumn:(id)sender;
|
|
||||||
- (IBAction)toggleDelta:(id)sender;
|
- (IBAction)toggleDelta:(id)sender;
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
- (IBAction)toggleDirectories:(id)sender;
|
||||||
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn;
|
|
||||||
- (void)initResultColumns;
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)awakeFromNib
|
- (void)awakeFromNib
|
||||||
{
|
{
|
||||||
[super awakeFromNib];
|
[super awakeFromNib];
|
||||||
|
[[self window] setTitle:@"dupeGuru Picture Edition"];
|
||||||
_displayDelta = NO;
|
_displayDelta = NO;
|
||||||
_powerMode = NO;
|
_powerMode = NO;
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain];
|
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain];
|
||||||
@@ -29,23 +30,8 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
||||||
[matches setTarget:self];
|
[matches setTarget:self];
|
||||||
[matches setDoubleAction:@selector(openSelected:)];
|
[matches setDoubleAction:@selector(openSelected:)];
|
||||||
[[actionMenu itemAtIndex:0] setImage:[NSImage imageNamed: @"gear"]];
|
|
||||||
[self initResultColumns];
|
|
||||||
[self refreshStats];
|
[self refreshStats];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
||||||
|
|
||||||
NSToolbar *t = [[[NSToolbar alloc] initWithIdentifier:@"ResultWindowToolbar"] autorelease];
|
|
||||||
[t setAllowsUserCustomization:YES];
|
|
||||||
[t setAutosavesConfiguration:YES];
|
|
||||||
[t setDisplayMode:NSToolbarDisplayModeIconAndLabel];
|
|
||||||
[t setDelegate:self];
|
|
||||||
[[self window] setToolbar:t];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overrides */
|
|
||||||
- (NSString *)logoImageName
|
|
||||||
{
|
|
||||||
return @"dgpe_logo_32";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -170,7 +156,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[columnsOrder addObject:@"4"];
|
[columnsOrder addObject:@"4"];
|
||||||
[columnsOrder addObject:@"7"];
|
[columnsOrder addObject:@"7"];
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||||
[columnsWidth setObject:i2n(125) forKey:@"0"];
|
[columnsWidth setObject:i2n(121) forKey:@"0"];
|
||||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||||
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
||||||
@@ -184,11 +170,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[py revealSelected];
|
[py revealSelected];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)showPreferencesPanel:(id)sender
|
|
||||||
{
|
|
||||||
[preferencesPanel makeKeyAndOrderFront:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender
|
- (IBAction)startDuplicateScan:(id)sender
|
||||||
{
|
{
|
||||||
if ([matches numberOfRows] > 0)
|
if ([matches numberOfRows] > 0)
|
||||||
@@ -215,26 +196,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleColumn:(id)sender
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = sender;
|
|
||||||
NSString *colId = [NSString stringWithFormat:@"%d",[mi tag]];
|
|
||||||
NSTableColumn *col = [matches tableColumnWithIdentifier:colId];
|
|
||||||
if (col == nil)
|
|
||||||
{
|
|
||||||
//Add Column
|
|
||||||
col = [_resultColumns objectAtIndex:[mi tag]];
|
|
||||||
[matches addTableColumn:col];
|
|
||||||
[mi setState:NSOnState];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Remove column
|
|
||||||
[matches removeTableColumn:col];
|
|
||||||
[mi setState:NSOffState];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDelta:(id)sender
|
- (IBAction)toggleDelta:(id)sender
|
||||||
{
|
{
|
||||||
if ([deltaSwitch selectedSegment] == 1)
|
if ([deltaSwitch selectedSegment] == 1)
|
||||||
@@ -244,75 +205,29 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[self changeDelta:sender];
|
[self changeDelta:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender
|
|
||||||
{
|
|
||||||
[(AppDelegate *)app toggleDetailsPanel:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
- (IBAction)toggleDirectories:(id)sender
|
||||||
{
|
{
|
||||||
[(AppDelegate *)app toggleDirectories:sender];
|
[(AppDelegate *)app toggleDirectories:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn
|
|
||||||
{
|
|
||||||
NSNumber *n = [NSNumber numberWithInt:aIdentifier];
|
|
||||||
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[n stringValue]];
|
|
||||||
[col setWidth:aWidth];
|
|
||||||
[col setEditable:NO];
|
|
||||||
[[col dataCell] setFont:[[aColumn dataCell] font]];
|
|
||||||
[[col headerCell] setStringValue:aTitle];
|
|
||||||
[col setResizingMask:NSTableColumnUserResizingMask];
|
|
||||||
[col setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:[n stringValue] ascending:YES]];
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initResultColumns
|
- (void)initResultColumns
|
||||||
{
|
{
|
||||||
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
||||||
_resultColumns = [[NSMutableArray alloc] init];
|
_resultColumns = [[NSMutableArray alloc] init];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"1"]]; // Directory
|
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"2"]]; // Size
|
NSTableColumn *sizeCol = [self getColumnForIdentifier:2 title:@"Size (KB)" width:63 refCol:refCol];
|
||||||
|
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
[_resultColumns addObject:sizeCol];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Dimensions" width:80 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Dimensions" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Creation" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Creation" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Modification" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Modification" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"7"]]; // Match %
|
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Match %" width:58 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
|
|
||||||
{
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSNumber *width;
|
|
||||||
NSMenuItem *mi;
|
|
||||||
//Remove all columns
|
|
||||||
NSEnumerator *e = [[columnsMenu itemArray] objectEnumerator];
|
|
||||||
while (mi = [e nextObject])
|
|
||||||
{
|
|
||||||
if ([mi state] == NSOnState)
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
//Add columns and set widths
|
|
||||||
e = [aColumnsOrder objectEnumerator];
|
|
||||||
while (colId = [e nextObject])
|
|
||||||
{
|
|
||||||
if (![colId isEqual:@"mark"])
|
|
||||||
{
|
|
||||||
col = [_resultColumns objectAtIndex:[colId intValue]];
|
|
||||||
width = [aColumnsWidth objectForKey:[col identifier]];
|
|
||||||
mi = [columnsMenu itemWithTag:[colId intValue]];
|
|
||||||
if (width)
|
|
||||||
[col setWidth:[width floatValue]];
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
/* Delegate */
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 42;
|
objectVersion = 44;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
|
|
||||||
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
|
|
||||||
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
||||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||||
|
CE031751109B340A00517EE6 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031750109B340A00517EE6 /* Preferences.xib */; };
|
||||||
|
CE031754109B345200517EE6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031753109B345200517EE6 /* MainMenu.xib */; };
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */; };
|
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */; };
|
||||||
CE0C46AA0FA0647E000BE99B /* PictureBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C46A90FA0647E000BE99B /* PictureBlocks.m */; };
|
CE0C46AA0FA0647E000BE99B /* PictureBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C46A90FA0647E000BE99B /* PictureBlocks.m */; };
|
||||||
CE15C8A80ADEB8B50061D4A5 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
CE15C8A80ADEB8B50061D4A5 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
||||||
@@ -18,10 +18,11 @@
|
|||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
||||||
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE3AA46509DB207900DB3A21 /* Directories.nib */; };
|
|
||||||
CE6044EC0FE6796200B71262 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6044EB0FE6796200B71262 /* DetailsPanel.m */; };
|
CE6044EC0FE6796200B71262 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6044EB0FE6796200B71262 /* DetailsPanel.m */; };
|
||||||
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
||||||
CE6E0F3D1054EC62008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */; };
|
CE6E0F3D1054EC62008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */; };
|
||||||
|
CE77C89E10946C6D0078B0DB /* DirectoryPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE77C89C10946C6D0078B0DB /* DirectoryPanel.xib */; };
|
||||||
|
CE77C8A810946CE20078B0DB /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE77C8A710946CE20078B0DB /* DetailsPanel.xib */; };
|
||||||
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1C0FC192D60086DCA6 /* Dialogs.m */; };
|
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1C0FC192D60086DCA6 /* Dialogs.m */; };
|
||||||
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */; };
|
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */; };
|
||||||
CE80DB300FC192D60086DCA6 /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB200FC192D60086DCA6 /* Outline.m */; };
|
CE80DB300FC192D60086DCA6 /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB200FC192D60086DCA6 /* Outline.m */; };
|
||||||
@@ -42,11 +43,9 @@
|
|||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||||
CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */; };
|
CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */; };
|
||||||
CEBAE4280FDA97E000B7887D /* NSCharacterSet_Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4260FDA97E000B7887D /* NSCharacterSet_Extensions.m */; };
|
CEBAE4280FDA97E000B7887D /* NSCharacterSet_Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4260FDA97E000B7887D /* NSCharacterSet_Extensions.m */; };
|
||||||
CECA899909DB12CA00A3D774 /* Details.nib in Resources */ = {isa = PBXBuildFile; fileRef = CECA899709DB12CA00A3D774 /* Details.nib */; };
|
|
||||||
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
||||||
CEF7823809C8AA0200EF38FF /* gear.png in Resources */ = {isa = PBXBuildFile; fileRef = CEF7823709C8AA0200EF38FF /* gear.png */; };
|
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
||||||
@@ -69,15 +68,15 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
|
||||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
||||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
||||||
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
|
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
|
||||||
29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
|
|
||||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
||||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
||||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
|
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
|
||||||
8D1107320486CEB800E47090 /* dupeGuru PE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru PE.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
8D1107320486CEB800E47090 /* dupeGuru PE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru PE.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
CE031750109B340A00517EE6 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = ../../xib/Preferences.xib; sourceTree = "<group>"; };
|
||||||
|
CE031753109B345200517EE6 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_pe_help; path = help/dupeguru_pe_help; sourceTree = SOURCE_ROOT; };
|
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_pe_help; path = help/dupeguru_pe_help; sourceTree = SOURCE_ROOT; };
|
||||||
CE0C46A80FA0647E000BE99B /* PictureBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PictureBlocks.h; sourceTree = "<group>"; };
|
CE0C46A80FA0647E000BE99B /* PictureBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PictureBlocks.h; sourceTree = "<group>"; };
|
||||||
CE0C46A90FA0647E000BE99B /* PictureBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PictureBlocks.m; sourceTree = "<group>"; };
|
CE0C46A90FA0647E000BE99B /* PictureBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PictureBlocks.m; sourceTree = "<group>"; };
|
||||||
@@ -87,12 +86,13 @@
|
|||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = py/dist/dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = py/dist/dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
||||||
CE3AA46609DB207900DB3A21 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Directories.nib; sourceTree = "<group>"; };
|
|
||||||
CE6044EA0FE6796200B71262 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = dgbase/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
CE6044EA0FE6796200B71262 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = dgbase/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE6044EB0FE6796200B71262 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsPanel.m; path = dgbase/DetailsPanel.m; sourceTree = SOURCE_ROOT; };
|
CE6044EB0FE6796200B71262 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsPanel.m; path = dgbase/DetailsPanel.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = dgbase/dsa_pub.pem; sourceTree = "<group>"; };
|
CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = dgbase/dsa_pub.pem; sourceTree = "<group>"; };
|
||||||
|
CE77C89C10946C6D0078B0DB /* DirectoryPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DirectoryPanel.xib; sourceTree = "<group>"; };
|
||||||
|
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = DetailsPanel.xib; path = ../../xib/DetailsPanel.xib; sourceTree = "<group>"; };
|
||||||
CE80DB1B0FC192D60086DCA6 /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
|
CE80DB1B0FC192D60086DCA6 /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -132,11 +132,9 @@
|
|||||||
CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
||||||
CEBAE4250FDA97E000B7887D /* NSCharacterSet_Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSCharacterSet_Extensions.h; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.h; sourceTree = SOURCE_ROOT; };
|
CEBAE4250FDA97E000B7887D /* NSCharacterSet_Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSCharacterSet_Extensions.h; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.h; sourceTree = SOURCE_ROOT; };
|
||||||
CEBAE4260FDA97E000B7887D /* NSCharacterSet_Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSCharacterSet_Extensions.m; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.m; sourceTree = SOURCE_ROOT; };
|
CEBAE4260FDA97E000B7887D /* NSCharacterSet_Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSCharacterSet_Extensions.m; path = cocoalib/brsinglelineformatter/NSCharacterSet_Extensions.m; sourceTree = SOURCE_ROOT; };
|
||||||
CECA899809DB12CA00A3D774 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Details.nib; sourceTree = "<group>"; };
|
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
||||||
CEF7823709C8AA0200EF38FF /* gear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = gear.png; path = images/gear.png; sourceTree = "<group>"; };
|
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = images/folder32.png; sourceTree = SOURCE_ROOT; };
|
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = images/folder32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = images/details32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = images/details32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -228,16 +226,13 @@
|
|||||||
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE77C89A10946C6D0078B0DB /* xib */,
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */,
|
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */,
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */,
|
CE381CF509915304003581CE /* dg_cocoa.plugin */,
|
||||||
CEFC294309C89E0000D9F998 /* images */,
|
CEFC294309C89E0000D9F998 /* images */,
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */,
|
CEEB135109C837A2004D2330 /* dupeguru.icns */,
|
||||||
8D1107310486CEB800E47090 /* Info.plist */,
|
8D1107310486CEB800E47090 /* Info.plist */,
|
||||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
|
|
||||||
CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */,
|
CE6E0F3C1054EC62008D9390 /* dsa_pub.pem */,
|
||||||
CECA899709DB12CA00A3D774 /* Details.nib */,
|
|
||||||
CE3AA46509DB207900DB3A21 /* Directories.nib */,
|
|
||||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
|
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -251,6 +246,18 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
CE77C89A10946C6D0078B0DB /* xib */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
CE031753109B345200517EE6 /* MainMenu.xib */,
|
||||||
|
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */,
|
||||||
|
CE77C89C10946C6D0078B0DB /* DirectoryPanel.xib */,
|
||||||
|
CE031750109B340A00517EE6 /* Preferences.xib */,
|
||||||
|
);
|
||||||
|
name = xib;
|
||||||
|
path = dgbase/xib;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
CE80DB1A0FC192AB0086DCA6 /* cocoalib */ = {
|
CE80DB1A0FC192AB0086DCA6 /* cocoalib */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -318,7 +325,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CEFCDE2C0AB0418600C33A93 /* dgpe_logo_32.png */,
|
CEFCDE2C0AB0418600C33A93 /* dgpe_logo_32.png */,
|
||||||
CEF7823709C8AA0200EF38FF /* gear.png */,
|
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */,
|
CEFC295309C89FF200D9F998 /* details32.png */,
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */,
|
CEFC295409C89FF200D9F998 /* preferences32.png */,
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */,
|
CEFC294509C89E3D00D9F998 /* folder32.png */,
|
||||||
@@ -354,7 +360,7 @@
|
|||||||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||||
compatibilityVersion = "Xcode 2.4";
|
compatibilityVersion = "Xcode 3.0";
|
||||||
hasScannedForEncodings = 1;
|
hasScannedForEncodings = 1;
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -370,22 +376,21 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */,
|
|
||||||
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
|
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
|
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */,
|
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */,
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
|
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
|
||||||
CEF7823809C8AA0200EF38FF /* gear.png in Resources */,
|
|
||||||
CECA899909DB12CA00A3D774 /* Details.nib in Resources */,
|
|
||||||
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */,
|
|
||||||
CEFCDE2D0AB0418600C33A93 /* dgpe_logo_32.png in Resources */,
|
CEFCDE2D0AB0418600C33A93 /* dgpe_logo_32.png in Resources */,
|
||||||
CE80DB760FC194760086DCA6 /* ErrorReportWindow.xib in Resources */,
|
CE80DB760FC194760086DCA6 /* ErrorReportWindow.xib in Resources */,
|
||||||
CE80DB770FC194760086DCA6 /* progress.nib in Resources */,
|
CE80DB770FC194760086DCA6 /* progress.nib in Resources */,
|
||||||
CE80DB780FC194760086DCA6 /* registration.nib in Resources */,
|
CE80DB780FC194760086DCA6 /* registration.nib in Resources */,
|
||||||
CE6E0F3D1054EC62008D9390 /* dsa_pub.pem in Resources */,
|
CE6E0F3D1054EC62008D9390 /* dsa_pub.pem in Resources */,
|
||||||
|
CE77C89E10946C6D0078B0DB /* DirectoryPanel.xib in Resources */,
|
||||||
|
CE77C8A810946CE20078B0DB /* DetailsPanel.xib in Resources */,
|
||||||
|
CE031751109B340A00517EE6 /* Preferences.xib in Resources */,
|
||||||
|
CE031754109B345200517EE6 /* MainMenu.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -425,30 +430,6 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
089C165DFE840E0CC02AAC07 /* English */,
|
|
||||||
);
|
|
||||||
name = InfoPlist.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
29B97319FDCFA39411CA2CEA /* English */,
|
|
||||||
);
|
|
||||||
name = MainMenu.nib;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
CE3AA46509DB207900DB3A21 /* Directories.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CE3AA46609DB207900DB3A21 /* English */,
|
|
||||||
);
|
|
||||||
name = Directories.nib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CE80DB700FC194760086DCA6 /* ErrorReportWindow.xib */ = {
|
CE80DB700FC194760086DCA6 /* ErrorReportWindow.xib */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -473,39 +454,9 @@
|
|||||||
name = registration.nib;
|
name = registration.nib;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
};
|
};
|
||||||
CECA899709DB12CA00A3D774 /* Details.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CECA899809DB12CA00A3D774 /* English */,
|
|
||||||
);
|
|
||||||
name = Details.nib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
C01FCF4B08A954540054247B /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
|
||||||
"$(FRAMEWORK_SEARCH_PATHS)",
|
|
||||||
"$(SRCROOT)/cocoalib/build/Release",
|
|
||||||
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
|
|
||||||
);
|
|
||||||
FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/dgbase/build/Release\"";
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
|
||||||
GCC_MODEL_TUNING = G5;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
INFOPLIST_FILE = Info.plist;
|
|
||||||
INSTALL_PATH = "$(HOME)/Applications";
|
|
||||||
PRODUCT_NAME = dupeGuru;
|
|
||||||
WRAPPER_EXTENSION = app;
|
|
||||||
ZERO_LINK = YES;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
C01FCF4C08A954540054247B /* Release */ = {
|
C01FCF4C08A954540054247B /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -526,31 +477,16 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
C01FCF4F08A954540054247B /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
GCC_C_LANGUAGE_STANDARD = c99;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.4;
|
|
||||||
PREBINDING = NO;
|
|
||||||
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
C01FCF5008A954540054247B /* Release */ = {
|
C01FCF5008A954540054247B /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)";
|
ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)";
|
||||||
ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386";
|
ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386";
|
||||||
FRAMEWORK_SEARCH_PATHS = "";
|
|
||||||
GCC_C_LANGUAGE_STANDARD = c99;
|
GCC_C_LANGUAGE_STANDARD = c99;
|
||||||
GCC_VERSION = 4.0;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.4;
|
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
||||||
PREBINDING = NO;
|
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
|
||||||
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -560,7 +496,6 @@
|
|||||||
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
|
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
C01FCF4B08A954540054247B /* Debug */,
|
|
||||||
C01FCF4C08A954540054247B /* Release */,
|
C01FCF4C08A954540054247B /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
@@ -569,7 +504,6 @@
|
|||||||
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
|
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
C01FCF4F08A954540054247B /* Debug */,
|
|
||||||
C01FCF5008A954540054247B /* Release */,
|
C01FCF5008A954540054247B /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
|
|||||||
@@ -5,13 +5,15 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, 'py') # for hsutil and hsdocgen
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from help import gen
|
||||||
|
|
||||||
print "Generating help"
|
print "Generating help"
|
||||||
os.chdir('help')
|
gen.generate()
|
||||||
os.system('python -u gen.py')
|
os.system('/Developer/Applications/Utilities/Help\\ Indexer.app/Contents/MacOS/Help\\ Indexer help/dupeguru_pe_help')
|
||||||
os.system('/Developer/Applications/Utilities/Help\\ Indexer.app/Contents/MacOS/Help\\ Indexer dupeguru_pe_help')
|
|
||||||
os.chdir('..')
|
|
||||||
|
|
||||||
print "Generating py plugin"
|
print "Generating py plugin"
|
||||||
os.chdir('py')
|
os.chdir('py')
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from dupeguru_pe import app_cocoa as app_pe_cocoa
|
|||||||
# Fix py2app imports which chokes on relative imports
|
# Fix py2app imports which chokes on relative imports
|
||||||
from dupeguru import app, app_cocoa, data, directories, engine, export, ignore, results, scanner
|
from dupeguru import app, app_cocoa, data, directories, engine, export, ignore, results, scanner
|
||||||
from dupeguru_pe import block, cache, matchbase, data
|
from dupeguru_pe import block, cache, matchbase, data
|
||||||
from hsfs import auto, stats, tree
|
|
||||||
from hsutil import conflict
|
from hsutil import conflict
|
||||||
|
|
||||||
class PyApp(NSObject):
|
class PyApp(NSObject):
|
||||||
@@ -39,7 +38,7 @@ class PyDupeGuru(PyApp):
|
|||||||
self.app.scanner.ignore_list.Clear()
|
self.app.scanner.ignore_list.Clear()
|
||||||
|
|
||||||
def clearPictureCache(self):
|
def clearPictureCache(self):
|
||||||
self.app.scanner.match_factory.cached_blocks.clear()
|
self.app.scanner.cached_blocks.clear()
|
||||||
|
|
||||||
def doScan(self):
|
def doScan(self):
|
||||||
return self.app.start_scanning()
|
return self.app.start_scanning()
|
||||||
@@ -172,10 +171,10 @@ class PyDupeGuru(PyApp):
|
|||||||
|
|
||||||
#---Properties
|
#---Properties
|
||||||
def setMatchScaled_(self,match_scaled):
|
def setMatchScaled_(self,match_scaled):
|
||||||
self.app.scanner.match_factory.match_scaled = match_scaled
|
self.app.scanner.match_scaled = match_scaled
|
||||||
|
|
||||||
def setMinMatchPercentage_(self,percentage):
|
def setMinMatchPercentage_(self,percentage):
|
||||||
self.app.scanner.match_factory.threshold = int(percentage)
|
self.app.scanner.threshold = int(percentage)
|
||||||
|
|
||||||
def setMixFileKind_(self,mix_file_kind):
|
def setMixFileKind_(self,mix_file_kind):
|
||||||
self.app.scanner.mix_file_kind = mix_file_kind
|
self.app.scanner.mix_file_kind = mix_file_kind
|
||||||
|
|||||||
1472
pe/cocoa/xib/DetailsPanel.xib
Normal file
1472
pe/cocoa/xib/DetailsPanel.xib
Normal file
File diff suppressed because it is too large
Load Diff
1650
pe/cocoa/xib/Preferences.xib
Normal file
1650
pe/cocoa/xib/Preferences.xib
Normal file
File diff suppressed because it is too large
Load Diff
0
pe/help/__init__.py
Normal file
0
pe/help/__init__.py
Normal file
@@ -1,3 +1,16 @@
|
|||||||
|
- date: 2009-12-16
|
||||||
|
version: 1.8.0
|
||||||
|
description: |
|
||||||
|
* Added drag & drop support in the Directories panel. (#9)
|
||||||
|
* Fixed a bug causing dupeGuru to be confused if a scanned file was moved during the scan. (#72)
|
||||||
|
* Clarified how directories' state are set by painting a combo box in the state cells. [Windows]
|
||||||
|
(#76)
|
||||||
|
* Fixed some crashes. (#78 and #79)
|
||||||
|
* Dropped Mac OS X Tiger support.
|
||||||
|
- date: 2009-10-24
|
||||||
|
version: 1.7.8
|
||||||
|
description: |
|
||||||
|
* Fixed a bug sometimes causing some duplicates to be ignored during the scans. (#73)
|
||||||
- date: 2009-10-14
|
- date: 2009-10-14
|
||||||
version: 1.7.7
|
version: 1.7.7
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import os
|
import os.path as op
|
||||||
|
|
||||||
from hsdocgen import generate_help, filters
|
from hsdocgen import generate_help, filters
|
||||||
|
|
||||||
tix = filters.tixgen("https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}")
|
def generate(windows=False):
|
||||||
|
tix = filters.tixgen("https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}")
|
||||||
generate_help.main('.', 'dupeguru_pe_help', force_render=True, tix=tix)
|
basepath = op.dirname(__file__)
|
||||||
|
generate_help.main(basepath, op.join(basepath, 'dupeguru_pe_help'), force_render=True, tix=tix, windows=windows)
|
||||||
|
|||||||
@@ -61,4 +61,14 @@ Enable the [Power Marker](power_marker.htm) mode and click on the Directory colu
|
|||||||
* **Windows**: Click on **Actions --> Apply Filter**, then type "copy", then click OK.
|
* **Windows**: Click on **Actions --> Apply Filter**, then type "copy", then click OK.
|
||||||
* **Mac OS X**: Type "copy" in the "Filter" field in the toolbar.
|
* **Mac OS X**: Type "copy" in the "Filter" field in the toolbar.
|
||||||
* Click on **Mark --> Mark All**.
|
* Click on **Mark --> Mark All**.
|
||||||
|
|
||||||
|
### I tried to send my duplicates to Trash, but dupeGuru is telling me it can't do it. Why? What can I do?
|
||||||
|
|
||||||
|
Most of the time, the reason why dupeGuru can't send files to Trash is because of file permissions. You need *write* permissions on files you want to send to Trash. If you're not familiar with the command line, you can use utilities such as [BatChmod](http://macchampion.com/arbysoft/BatchMod) to fix your permissions.
|
||||||
|
|
||||||
|
If dupeGuru still gives you troubles after fixing your permissions, there have been some cases where using "Move Marked to..." as a workaround did the trick. So instead of sending your files to Trash, you send them to a temporary folder with the "Move Marked to..." action, and then you delete that temporary folder manually.
|
||||||
|
|
||||||
|
If you're trying to delete *iPhoto* pictures, then the reason for the failure is different. The deletion fails because dupeGuru can't communicate with iPhoto. Be aware that for the deletion to work correctly, you're not supposed to play around iPhoto while dupeGuru is working. Also, sometimes, the Applescript system doesn't seem to know where to find iPhoto to launch it. It might help in these cases to launch iPhoto *before* you send your duplicates to Trash.
|
||||||
|
|
||||||
|
If all of this fail, [contact HS support](http://www.hardcoded.net/support), we'll figure it out.
|
||||||
</%text>
|
</%text>
|
||||||
@@ -7,41 +7,43 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path as op
|
import os.path as op
|
||||||
import logging
|
import logging
|
||||||
import plistlib
|
import plistlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import objc
|
|
||||||
from Foundation import *
|
from Foundation import *
|
||||||
from AppKit import *
|
from AppKit import *
|
||||||
from appscript import app, k
|
from appscript import app, k, CommandError
|
||||||
|
|
||||||
from hsutil import job, io
|
from hsutil import io
|
||||||
import hsfs as fs
|
|
||||||
from hsfs import phys, InvalidPath
|
|
||||||
from hsutil import files
|
|
||||||
from hsutil.str import get_file_ext
|
from hsutil.str import get_file_ext
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.cocoa import as_fetch
|
from hsutil.cocoa import as_fetch
|
||||||
|
|
||||||
|
from dupeguru import fs
|
||||||
from dupeguru import app_cocoa, directories
|
from dupeguru import app_cocoa, directories
|
||||||
from . import data, matchbase
|
from . import data
|
||||||
from .cache import string_to_colors, Cache
|
from .cache import string_to_colors, Cache
|
||||||
|
from .scanner import ScannerPE
|
||||||
|
|
||||||
mainBundle = NSBundle.mainBundle()
|
mainBundle = NSBundle.mainBundle()
|
||||||
PictureBlocks = mainBundle.classNamed_('PictureBlocks')
|
PictureBlocks = mainBundle.classNamed_('PictureBlocks')
|
||||||
assert PictureBlocks is not None
|
assert PictureBlocks is not None
|
||||||
|
|
||||||
class Photo(phys.File):
|
class Photo(fs.File):
|
||||||
INITIAL_INFO = phys.File.INITIAL_INFO.copy()
|
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
|
||||||
INITIAL_INFO.update({
|
INITIAL_INFO.update({
|
||||||
'dimensions': (0,0),
|
'dimensions': (0,0),
|
||||||
})
|
})
|
||||||
|
HANDLED_EXTS = set(['png', 'jpg', 'jpeg', 'gif', 'psd', 'bmp', 'tiff', 'tif', 'nef', 'cr2'])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_handle(cls, path):
|
||||||
|
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
|
||||||
|
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
super(Photo, self)._read_info(field)
|
fs.File._read_info(self, field)
|
||||||
if field == 'dimensions':
|
if field == 'dimensions':
|
||||||
size = PictureBlocks.getImageSize_(unicode(self.path))
|
size = PictureBlocks.getImageSize_(unicode(self.path))
|
||||||
self.dimensions = (size.width, size.height)
|
self.dimensions = (size.width, size.height)
|
||||||
@@ -49,7 +51,7 @@ class Photo(phys.File):
|
|||||||
def get_blocks(self, block_count_per_side):
|
def get_blocks(self, block_count_per_side):
|
||||||
try:
|
try:
|
||||||
blocks = PictureBlocks.getBlocksFromImagePath_blockCount_(unicode(self.path), block_count_per_side)
|
blocks = PictureBlocks.getBlocksFromImagePath_blockCount_(unicode(self.path), block_count_per_side)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
raise IOError('The reading of "%s" failed with "%s"' % (unicode(self.path), unicode(e)))
|
raise IOError('The reading of "%s" failed with "%s"' % (unicode(self.path), unicode(e)))
|
||||||
if not blocks:
|
if not blocks:
|
||||||
raise IOError('The picture %s could not be read' % unicode(self.path))
|
raise IOError('The picture %s could not be read' % unicode(self.path))
|
||||||
@@ -57,89 +59,86 @@ class Photo(phys.File):
|
|||||||
|
|
||||||
|
|
||||||
class IPhoto(Photo):
|
class IPhoto(Photo):
|
||||||
def __init__(self, parent, whole_path):
|
|
||||||
super(IPhoto, self).__init__(parent, whole_path[-1])
|
|
||||||
self.whole_path = whole_path
|
|
||||||
|
|
||||||
def _build_path(self):
|
|
||||||
return self.whole_path
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_path(self):
|
def display_path(self):
|
||||||
return super(IPhoto, self)._build_path()
|
return Path(('iPhoto Library', self.name))
|
||||||
|
|
||||||
|
def get_iphoto_database_path():
|
||||||
|
ud = NSUserDefaults.standardUserDefaults()
|
||||||
|
prefs = ud.persistentDomainForName_('com.apple.iApps')
|
||||||
|
if prefs is None:
|
||||||
|
raise directories.InvalidPathError()
|
||||||
|
if 'iPhotoRecentDatabases' not in prefs:
|
||||||
|
raise directories.InvalidPathError()
|
||||||
|
plisturl = NSURL.URLWithString_(prefs['iPhotoRecentDatabases'][0])
|
||||||
|
return Path(plisturl.path())
|
||||||
|
|
||||||
class Directory(phys.Directory):
|
def get_iphoto_pictures(plistpath):
|
||||||
cls_file_class = Photo
|
if not io.exists(plistpath):
|
||||||
cls_supported_exts = ('png', 'jpg', 'jpeg', 'gif', 'psd', 'bmp', 'tiff', 'nef', 'cr2')
|
raise InvalidPath(self)
|
||||||
|
s = io.open(plistpath).read()
|
||||||
def _fetch_subitems(self):
|
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
||||||
subdirs, subfiles = super(Directory,self)._fetch_subitems()
|
s = s.replace('\x10', '')
|
||||||
return subdirs, [name for name in subfiles if get_file_ext(name) in self.cls_supported_exts]
|
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
|
||||||
|
# any & char that is not a &-based entity (&, ", etc.). based on TextMate's XML
|
||||||
|
# bundle's regexp
|
||||||
class IPhotoLibrary(fs.Directory):
|
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
||||||
def __init__(self, plistpath):
|
if count:
|
||||||
self.plistpath = plistpath
|
logging.warning("%d invalid XML entities replacement made", count)
|
||||||
self.refpath = plistpath[:-1]
|
plist = plistlib.readPlistFromString(s)
|
||||||
# the AlbumData.xml file lives right in the library path
|
result = []
|
||||||
super(IPhotoLibrary, self).__init__(None, 'iPhoto Library')
|
for photo_data in plist['Master Image List'].values():
|
||||||
if not io.exists(plistpath):
|
|
||||||
raise InvalidPath(self)
|
|
||||||
|
|
||||||
def _update_photo(self, photo_data):
|
|
||||||
if photo_data['MediaType'] != 'Image':
|
if photo_data['MediaType'] != 'Image':
|
||||||
return
|
continue
|
||||||
photo_path = Path(photo_data['ImagePath'])
|
photo_path = Path(photo_data['ImagePath'])
|
||||||
subpath = photo_path[len(self.refpath):-1]
|
photo = IPhoto(photo_path)
|
||||||
subdir = self
|
result.append(photo)
|
||||||
for element in subpath:
|
return result
|
||||||
try:
|
|
||||||
subdir = subdir[element]
|
class Directories(directories.Directories):
|
||||||
except KeyError:
|
def __init__(self):
|
||||||
subdir = fs.Directory(subdir, element)
|
directories.Directories.__init__(self, fileclasses=[Photo])
|
||||||
try:
|
try:
|
||||||
IPhoto(subdir, photo_path)
|
self.iphoto_libpath = get_iphoto_database_path()
|
||||||
except fs.AlreadyExistsError:
|
self.set_state(self.iphoto_libpath[:-1], directories.STATE_EXCLUDED)
|
||||||
# it's possible for 2 entries in the plist to point to the same path. Ignore one of them.
|
except directories.InvalidPathError:
|
||||||
pass
|
self.iphoto_libpath = None
|
||||||
|
|
||||||
def update(self):
|
def _get_files(self, from_path):
|
||||||
self.clear()
|
if from_path == Path('iPhoto Library'):
|
||||||
s = open(unicode(self.plistpath)).read()
|
if self.iphoto_libpath is None:
|
||||||
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
return []
|
||||||
s = s.replace('\x10', '')
|
is_ref = self.get_state(from_path) == directories.STATE_REFERENCE
|
||||||
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
|
photos = get_iphoto_pictures(self.iphoto_libpath)
|
||||||
# any & char that is not a &-based entity (&, ", etc.). based on TextMate's XML
|
for photo in photos:
|
||||||
# bundle's regexp
|
photo.is_ref = is_ref
|
||||||
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
return photos
|
||||||
if count:
|
else:
|
||||||
logging.warning("%d invalid XML entities replacement made", count)
|
return directories.Directories._get_files(self, from_path)
|
||||||
plist = plistlib.readPlistFromString(s)
|
|
||||||
for photo_data in plist['Master Image List'].values():
|
|
||||||
self._update_photo(photo_data)
|
|
||||||
|
|
||||||
def force_update(self): # Don't update
|
@staticmethod
|
||||||
pass
|
def get_subfolders(path):
|
||||||
|
if path == Path('iPhoto Library'):
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return directories.Directories.get_subfolders(path)
|
||||||
|
|
||||||
|
def add_path(self, path):
|
||||||
|
if path == Path('iPhoto Library'):
|
||||||
|
if path in self:
|
||||||
|
raise AlreadyThereError()
|
||||||
|
self._dirs.append(path)
|
||||||
|
else:
|
||||||
|
directories.Directories.add_path(self, path)
|
||||||
|
|
||||||
|
|
||||||
class DupeGuruPE(app_cocoa.DupeGuru):
|
class DupeGuruPE(app_cocoa.DupeGuru):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
app_cocoa.DupeGuru.__init__(self, data, 'dupeGuru Picture Edition', appid=5)
|
app_cocoa.DupeGuru.__init__(self, data, 'dupeGuru Picture Edition', appid=5)
|
||||||
self.scanner.match_factory = matchbase.AsyncMatchFactory()
|
self.scanner = ScannerPE()
|
||||||
self.directories.dirclass = Directory
|
self.directories = Directories()
|
||||||
self.directories.special_dirclasses[Path('iPhoto Library')] = lambda _, __: self._create_iphoto_library()
|
|
||||||
p = op.join(self.appdata, 'cached_pictures.db')
|
p = op.join(self.appdata, 'cached_pictures.db')
|
||||||
self.scanner.match_factory.cached_blocks = Cache(p)
|
self.scanner.cached_blocks = Cache(p)
|
||||||
|
|
||||||
def _create_iphoto_library(self):
|
|
||||||
ud = NSUserDefaults.standardUserDefaults()
|
|
||||||
prefs = ud.persistentDomainForName_('com.apple.iApps')
|
|
||||||
if 'iPhotoRecentDatabases' not in prefs:
|
|
||||||
raise directories.InvalidPathError
|
|
||||||
plisturl = NSURL.URLWithString_(prefs['iPhotoRecentDatabases'][0])
|
|
||||||
plistpath = Path(plisturl.path())
|
|
||||||
return IPhotoLibrary(plistpath)
|
|
||||||
|
|
||||||
def _do_delete(self, j):
|
def _do_delete(self, j):
|
||||||
def op(dupe):
|
def op(dupe):
|
||||||
@@ -150,12 +149,18 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
|||||||
self.path2iphoto = {}
|
self.path2iphoto = {}
|
||||||
if any(isinstance(dupe, IPhoto) for dupe in marked):
|
if any(isinstance(dupe, IPhoto) for dupe in marked):
|
||||||
j = j.start_subjob([6, 4], "Probing iPhoto. Don\'t touch it during the operation!")
|
j = j.start_subjob([6, 4], "Probing iPhoto. Don\'t touch it during the operation!")
|
||||||
a = app('iPhoto')
|
try:
|
||||||
a.activate(timeout=0)
|
a = app('iPhoto')
|
||||||
a.select(a.photo_library_album(timeout=0), timeout=0)
|
a.activate(timeout=0)
|
||||||
photos = as_fetch(a.photo_library_album().photos, k.item)
|
a.select(a.photo_library_album(timeout=0), timeout=0)
|
||||||
for photo in j.iter_with_progress(photos):
|
photos = as_fetch(a.photo_library_album().photos, k.item)
|
||||||
self.path2iphoto[unicode(photo.image_path(timeout=0))] = photo
|
for photo in j.iter_with_progress(photos):
|
||||||
|
try:
|
||||||
|
self.path2iphoto[unicode(photo.image_path(timeout=0))] = photo
|
||||||
|
except CommandError:
|
||||||
|
pass
|
||||||
|
except (CommandError, RuntimeError):
|
||||||
|
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.last_op_error_count = self.results.perform_on_marked(op, True)
|
||||||
del self.path2iphoto
|
del self.path2iphoto
|
||||||
@@ -164,50 +169,33 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
|||||||
if isinstance(dupe, IPhoto):
|
if isinstance(dupe, IPhoto):
|
||||||
if unicode(dupe.path) in self.path2iphoto:
|
if unicode(dupe.path) in self.path2iphoto:
|
||||||
photo = self.path2iphoto[unicode(dupe.path)]
|
photo = self.path2iphoto[unicode(dupe.path)]
|
||||||
a = app('iPhoto')
|
try:
|
||||||
a.remove(photo, timeout=0)
|
a = app('iPhoto')
|
||||||
return True
|
a.remove(photo, timeout=0)
|
||||||
|
return True
|
||||||
|
except (CommandError, RuntimeError):
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
logging.warning("Could not find photo {0} in iPhoto Library", unicode(dupe.path))
|
logging.warning(u"Could not find photo %s in iPhoto Library", unicode(dupe.path))
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
return app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
|
return 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'))
|
||||||
for d in self.directories:
|
|
||||||
if isinstance(d, IPhotoLibrary):
|
|
||||||
d.update()
|
|
||||||
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
|
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
|
||||||
|
|
||||||
def _get_file(self, str_path):
|
def _get_file(self, str_path):
|
||||||
p = Path(str_path)
|
p = Path(str_path)
|
||||||
for d in self.directories:
|
if (self.directories.iphoto_libpath is not None) and (p in self.directories.iphoto_libpath[:-1]):
|
||||||
result = None
|
return IPhoto(p)
|
||||||
if p in d.path:
|
return app_cocoa.DupeGuru._get_file(self, str_path)
|
||||||
result = d.find_path(p[d.path:])
|
|
||||||
if isinstance(d, IPhotoLibrary) and p in d.refpath:
|
|
||||||
result = d.find_path(p[d.refpath:])
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
|
|
||||||
def add_directory(self, d):
|
|
||||||
result = app_cocoa.DupeGuru.add_directory(self, d)
|
|
||||||
if (result == 0) and (d == 'iPhoto Library'):
|
|
||||||
[iphotolib] = [dir for dir in self.directories if dir.path == d]
|
|
||||||
iphotolib.update()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def copy_or_move(self, dupe, copy, destination, dest_type):
|
def copy_or_move(self, dupe, copy, destination, dest_type):
|
||||||
if isinstance(dupe, IPhoto):
|
if isinstance(dupe, IPhoto):
|
||||||
copy = True
|
copy = True
|
||||||
return app_cocoa.DupeGuru.copy_or_move(self, dupe, copy, destination, dest_type)
|
return app_cocoa.DupeGuru.copy_or_move(self, dupe, copy, destination, dest_type)
|
||||||
|
|
||||||
def start_scanning(self):
|
|
||||||
for directory in self.directories:
|
|
||||||
if isinstance(directory, IPhotoLibrary):
|
|
||||||
self.directories.set_state(directory.refpath, directories.STATE_EXCLUDED)
|
|
||||||
return app_cocoa.DupeGuru.start_scanning(self)
|
|
||||||
|
|
||||||
def selected_dupe_path(self):
|
def selected_dupe_path(self):
|
||||||
if not self.selected_dupes:
|
if not self.selected_dupes:
|
||||||
return None
|
return None
|
||||||
@@ -217,5 +205,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
|||||||
if not self.selected_dupes:
|
if not self.selected_dupes:
|
||||||
return None
|
return None
|
||||||
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
|
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
|
||||||
|
if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides
|
||||||
|
return None
|
||||||
return ref.path
|
return ref.path
|
||||||
|
|
||||||
|
|||||||
@@ -20,58 +20,42 @@ from .block import avgdiff, DifferentBlockCountError, NoBlocksError
|
|||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
|
|
||||||
MIN_ITERATIONS = 3
|
MIN_ITERATIONS = 3
|
||||||
|
BLOCK_COUNT_PER_SIDE = 15
|
||||||
|
|
||||||
# Enough so that we're sure that the main thread will not wait after a result.get() call
|
# Enough so that we're sure that the main thread will not wait after a result.get() call
|
||||||
# cpucount*2 should be enough to be sure that the spawned process will not wait after the results
|
# cpucount*2 should be enough to be sure that the spawned process will not wait after the results
|
||||||
# collection made by the main process.
|
# collection made by the main process.
|
||||||
RESULTS_QUEUE_LIMIT = multiprocessing.cpu_count() * 2
|
RESULTS_QUEUE_LIMIT = multiprocessing.cpu_count() * 2
|
||||||
|
|
||||||
def get_match(first,second,percentage):
|
def prepare_pictures(pictures, cached_blocks, j=job.nulljob):
|
||||||
|
# The MemoryError handlers in there use logging without first caring about whether or not
|
||||||
|
# there is enough memory left to carry on the operation because it is assumed that the
|
||||||
|
# MemoryError happens when trying to read an image file, which is freed from memory by the
|
||||||
|
# time that MemoryError is raised.
|
||||||
|
prepared = [] # only pictures for which there was no error getting blocks
|
||||||
|
try:
|
||||||
|
for picture in j.iter_with_progress(pictures, 'Analyzed %d/%d pictures'):
|
||||||
|
picture.dimensions
|
||||||
|
picture.unicode_path = unicode(picture.path)
|
||||||
|
try:
|
||||||
|
if picture.unicode_path not in cached_blocks:
|
||||||
|
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
|
||||||
|
cached_blocks[picture.unicode_path] = blocks
|
||||||
|
prepared.append(picture)
|
||||||
|
except IOError as e:
|
||||||
|
logging.warning(unicode(e))
|
||||||
|
except MemoryError:
|
||||||
|
logging.warning(u'Ran out of memory while reading %s of size %d' % (picture.unicode_path, picture.size))
|
||||||
|
if picture.size < 10 * 1024 * 1024: # We're really running out of memory
|
||||||
|
raise
|
||||||
|
except MemoryError:
|
||||||
|
logging.warning('Ran out of memory while preparing pictures')
|
||||||
|
return prepared
|
||||||
|
|
||||||
|
def get_match(first, second, percentage):
|
||||||
if percentage < 0:
|
if percentage < 0:
|
||||||
percentage = 0
|
percentage = 0
|
||||||
return Match(first,second,percentage)
|
return Match(first, second, percentage)
|
||||||
|
|
||||||
class MatchFactory(object):
|
|
||||||
cached_blocks = None
|
|
||||||
block_count_per_side = 15
|
|
||||||
threshold = 75
|
|
||||||
match_scaled = False
|
|
||||||
|
|
||||||
def _do_getmatches(self, files, j):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def getmatches(self, files, j=job.nulljob):
|
|
||||||
# The MemoryError handlers in there use logging without first caring about whether or not
|
|
||||||
# there is enough memory left to carry on the operation because it is assumed that the
|
|
||||||
# MemoryError happens when trying to read an image file, which is freed from memory by the
|
|
||||||
# time that MemoryError is raised.
|
|
||||||
j = j.start_subjob([3, 7])
|
|
||||||
logging.info('Preparing %d files' % len(files))
|
|
||||||
prepared = self.prepare_files(files, j)
|
|
||||||
logging.info('Finished preparing %d files' % len(prepared))
|
|
||||||
return self._do_getmatches(prepared, j)
|
|
||||||
|
|
||||||
def prepare_files(self, files, j=job.nulljob):
|
|
||||||
prepared = [] # only files for which there was no error getting blocks
|
|
||||||
try:
|
|
||||||
for picture in j.iter_with_progress(files, 'Analyzed %d/%d pictures'):
|
|
||||||
picture.dimensions
|
|
||||||
picture.unicode_path = unicode(picture.path)
|
|
||||||
try:
|
|
||||||
if picture.unicode_path not in self.cached_blocks:
|
|
||||||
blocks = picture.get_blocks(self.block_count_per_side)
|
|
||||||
self.cached_blocks[picture.unicode_path] = blocks
|
|
||||||
prepared.append(picture)
|
|
||||||
except IOError as e:
|
|
||||||
logging.warning(unicode(e))
|
|
||||||
except MemoryError:
|
|
||||||
logging.warning(u'Ran out of memory while reading %s of size %d' % (picture.unicode_path, picture.size))
|
|
||||||
if picture.size < 10 * 1024 * 1024: # We're really running out of memory
|
|
||||||
raise
|
|
||||||
except MemoryError:
|
|
||||||
logging.warning('Ran out of memory while preparing files')
|
|
||||||
return prepared
|
|
||||||
|
|
||||||
|
|
||||||
def async_compare(ref_id, other_ids, dbname, threshold):
|
def async_compare(ref_id, other_ids, dbname, threshold):
|
||||||
cache = Cache(dbname, threaded=False)
|
cache = Cache(dbname, threaded=False)
|
||||||
@@ -90,52 +74,54 @@ def async_compare(ref_id, other_ids, dbname, threshold):
|
|||||||
cache.con.close()
|
cache.con.close()
|
||||||
return results
|
return results
|
||||||
|
|
||||||
class AsyncMatchFactory(MatchFactory):
|
def getmatches(pictures, cached_blocks, threshold=75, match_scaled=False, j=job.nulljob):
|
||||||
def _do_getmatches(self, pictures, j):
|
def empty_out_queue(queue, into):
|
||||||
def empty_out_queue(queue, into):
|
try:
|
||||||
try:
|
while True:
|
||||||
while True:
|
into.append(queue.get(block=False))
|
||||||
into.append(queue.get(block=False))
|
except Empty:
|
||||||
except Empty:
|
pass
|
||||||
pass
|
|
||||||
|
|
||||||
j = j.start_subjob([9, 1], 'Preparing for matching')
|
j = j.start_subjob([3, 7])
|
||||||
cache = self.cached_blocks
|
pictures = prepare_pictures(pictures, cached_blocks, j)
|
||||||
id2picture = {}
|
j = j.start_subjob([9, 1], 'Preparing for matching')
|
||||||
dimensions2pictures = defaultdict(set)
|
cache = cached_blocks
|
||||||
for picture in pictures:
|
id2picture = {}
|
||||||
try:
|
dimensions2pictures = defaultdict(set)
|
||||||
picture.cache_id = cache.get_id(picture.unicode_path)
|
for picture in pictures:
|
||||||
id2picture[picture.cache_id] = picture
|
try:
|
||||||
if not self.match_scaled:
|
picture.cache_id = cache.get_id(picture.unicode_path)
|
||||||
dimensions2pictures[picture.dimensions].add(picture)
|
id2picture[picture.cache_id] = picture
|
||||||
except ValueError:
|
if not match_scaled:
|
||||||
pass
|
dimensions2pictures[picture.dimensions].add(picture)
|
||||||
pictures = [p for p in pictures if hasattr(p, 'cache_id')]
|
except ValueError:
|
||||||
pool = multiprocessing.Pool()
|
pass
|
||||||
async_results = []
|
pictures = [p for p in pictures if hasattr(p, 'cache_id')]
|
||||||
matches = []
|
pool = multiprocessing.Pool()
|
||||||
pictures_copy = set(pictures)
|
async_results = []
|
||||||
for ref in j.iter_with_progress(pictures, 'Matched %d/%d pictures'):
|
matches = []
|
||||||
others = pictures_copy if self.match_scaled else dimensions2pictures[ref.dimensions]
|
pictures_copy = set(pictures)
|
||||||
others.remove(ref)
|
for ref in j.iter_with_progress(pictures, 'Matched %d/%d pictures'):
|
||||||
if others:
|
others = pictures_copy if match_scaled else dimensions2pictures[ref.dimensions]
|
||||||
cache_ids = [f.cache_id for f in others]
|
others.remove(ref)
|
||||||
args = (ref.cache_id, cache_ids, self.cached_blocks.dbname, self.threshold)
|
if others:
|
||||||
async_results.append(pool.apply_async(async_compare, args))
|
cache_ids = [f.cache_id for f in others]
|
||||||
if len(async_results) > RESULTS_QUEUE_LIMIT:
|
args = (ref.cache_id, cache_ids, cached_blocks.dbname, threshold)
|
||||||
result = async_results.pop(0)
|
async_results.append(pool.apply_async(async_compare, args))
|
||||||
matches.extend(result.get())
|
if len(async_results) > RESULTS_QUEUE_LIMIT:
|
||||||
|
result = async_results.pop(0)
|
||||||
result = []
|
matches.extend(result.get())
|
||||||
for ref_id, other_id, percentage in j.iter_with_progress(matches, 'Verified %d/%d matches', every=10):
|
for result in async_results: # process the rest of the results
|
||||||
ref = id2picture[ref_id]
|
matches.extend(result.get())
|
||||||
other = id2picture[other_id]
|
|
||||||
if percentage == 100 and ref.md5 != other.md5:
|
|
||||||
percentage = 99
|
|
||||||
if percentage >= self.threshold:
|
|
||||||
result.append(get_match(ref, other, percentage))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for ref_id, other_id, percentage in j.iter_with_progress(matches, 'Verified %d/%d matches', every=10):
|
||||||
|
ref = id2picture[ref_id]
|
||||||
|
other = id2picture[other_id]
|
||||||
|
if percentage == 100 and ref.md5 != other.md5:
|
||||||
|
percentage = 99
|
||||||
|
if percentage >= threshold:
|
||||||
|
result.append(get_match(ref, other, percentage))
|
||||||
|
return result
|
||||||
|
|
||||||
multiprocessing.freeze_support()
|
multiprocessing.freeze_support()
|
||||||
22
pe/py/scanner.py
Normal file
22
pe/py/scanner.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-10-18
|
||||||
|
# $Id$
|
||||||
|
# Copyright 2009 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 dupeguru.scanner import Scanner
|
||||||
|
|
||||||
|
from . import matchbase
|
||||||
|
|
||||||
|
class ScannerPE(Scanner):
|
||||||
|
cached_blocks = None
|
||||||
|
match_scaled = False
|
||||||
|
threshold = 75
|
||||||
|
|
||||||
|
def _getmatches(self, files, j):
|
||||||
|
return matchbase.getmatches(files, self.cached_blocks, self.threshold, self.match_scaled, j)
|
||||||
|
|
||||||
45
pe/qt/app.py
45
pe/qt/app.py
@@ -8,16 +8,17 @@
|
|||||||
# 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
|
||||||
|
|
||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
|
|
||||||
from hsfs import phys
|
|
||||||
from hsutil.str import get_file_ext
|
from hsutil.str import get_file_ext
|
||||||
|
|
||||||
|
from dupeguru import fs
|
||||||
from dupeguru_pe import data as data_pe
|
from dupeguru_pe import data as data_pe
|
||||||
from dupeguru_pe.cache import Cache
|
from dupeguru_pe.cache import Cache
|
||||||
from dupeguru_pe.matchbase import AsyncMatchFactory
|
from dupeguru_pe.scanner import ScannerPE
|
||||||
|
|
||||||
from block import getblocks
|
from block import getblocks
|
||||||
from base.app import DupeGuru as DupeGuruBase
|
from base.app import DupeGuru as DupeGuruBase
|
||||||
@@ -26,17 +27,26 @@ from main_window import MainWindow
|
|||||||
from preferences import Preferences
|
from preferences import Preferences
|
||||||
from preferences_dialog import PreferencesDialog
|
from preferences_dialog import PreferencesDialog
|
||||||
|
|
||||||
class File(phys.File):
|
class File(fs.File):
|
||||||
INITIAL_INFO = phys.File.INITIAL_INFO.copy()
|
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
|
||||||
INITIAL_INFO.update({
|
INITIAL_INFO.update({
|
||||||
'dimensions': (0,0),
|
'dimensions': (0,0),
|
||||||
})
|
})
|
||||||
|
HANDLED_EXTS = set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif'])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_handle(cls, path):
|
||||||
|
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
|
||||||
|
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
super(File, self)._read_info(field)
|
fs.File._read_info(self, field)
|
||||||
if field == 'dimensions':
|
if field == 'dimensions':
|
||||||
im = PIL.Image.open(unicode(self.path))
|
try:
|
||||||
self.dimensions = im.size
|
im = PIL.Image.open(unicode(self.path))
|
||||||
|
self.dimensions = im.size
|
||||||
|
except IOError:
|
||||||
|
self.dimensions = (0, 0)
|
||||||
|
logging.warning(u"Could not read image '%s'", unicode(self.path))
|
||||||
|
|
||||||
def get_blocks(self, block_count_per_side):
|
def get_blocks(self, block_count_per_side):
|
||||||
image = QImage(unicode(self.path))
|
image = QImage(unicode(self.path))
|
||||||
@@ -44,34 +54,25 @@ class File(phys.File):
|
|||||||
return getblocks(image, block_count_per_side)
|
return getblocks(image, block_count_per_side)
|
||||||
|
|
||||||
|
|
||||||
class Directory(phys.Directory):
|
|
||||||
cls_file_class = File
|
|
||||||
cls_supported_exts = ('png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff')
|
|
||||||
|
|
||||||
def _fetch_subitems(self):
|
|
||||||
subdirs, subfiles = super(Directory, self)._fetch_subitems()
|
|
||||||
return subdirs, [name for name in subfiles if get_file_ext(name) in self.cls_supported_exts]
|
|
||||||
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
class DupeGuru(DupeGuruBase):
|
||||||
LOGO_NAME = 'logo_pe'
|
LOGO_NAME = 'logo_pe'
|
||||||
NAME = 'dupeGuru Picture Edition'
|
NAME = 'dupeGuru Picture Edition'
|
||||||
VERSION = '1.7.7'
|
VERSION = '1.8.0'
|
||||||
DELTA_COLUMNS = frozenset([2, 5, 6])
|
DELTA_COLUMNS = frozenset([2, 5, 6])
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DupeGuruBase.__init__(self, data_pe, appid=5)
|
DupeGuruBase.__init__(self, data_pe, appid=5)
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
self.scanner.match_factory = AsyncMatchFactory()
|
self.scanner = ScannerPE()
|
||||||
self.directories.dirclass = Directory
|
self.directories.fileclasses = [File]
|
||||||
self.scanner.match_factory.cached_blocks = Cache(op.join(self.appdata, 'cached_pictures.db'))
|
self.scanner.cached_blocks = Cache(op.join(self.appdata, 'cached_pictures.db'))
|
||||||
DupeGuruBase._setup(self)
|
DupeGuruBase._setup(self)
|
||||||
|
|
||||||
def _update_options(self):
|
def _update_options(self):
|
||||||
DupeGuruBase._update_options(self)
|
DupeGuruBase._update_options(self)
|
||||||
self.scanner.match_factory.match_scaled = self.prefs.match_scaled
|
self.scanner.match_scaled = self.prefs.match_scaled
|
||||||
self.scanner.match_factory.threshold = self.prefs.filter_hardness
|
self.scanner.threshold = self.prefs.filter_hardness
|
||||||
|
|
||||||
def _create_details_dialog(self, parent):
|
def _create_details_dialog(self, parent):
|
||||||
return DetailsDialog(parent, self)
|
return DetailsDialog(parent, self)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'start.py'],
|
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'start.py'],
|
||||||
pathex=['C:\\src\\dupeguru\\pe\\qt'])
|
pathex=[])
|
||||||
pyz = PYZ(a.pure)
|
pyz = PYZ(a.pure)
|
||||||
exe = EXE(pyz,
|
exe = EXE(pyz,
|
||||||
a.scripts,
|
a.scripts,
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ import os.path as op
|
|||||||
|
|
||||||
from hsutil.build import print_and_do, build_all_qt_ui
|
from hsutil.build import print_and_do, build_all_qt_ui
|
||||||
|
|
||||||
|
from help import gen
|
||||||
|
|
||||||
build_all_qt_ui(op.join('qtlib', 'ui'))
|
build_all_qt_ui(op.join('qtlib', 'ui'))
|
||||||
build_all_qt_ui('base')
|
build_all_qt_ui('base')
|
||||||
build_all_qt_ui('.')
|
build_all_qt_ui('.')
|
||||||
|
os.chdir('base')
|
||||||
|
print_and_do("pyrcc4 dg.qrc > dg_rc.py")
|
||||||
|
os.chdir('..')
|
||||||
|
|
||||||
def move(src, dst):
|
def move(src, dst):
|
||||||
if not op.exists(src):
|
if not op.exists(src):
|
||||||
@@ -38,5 +43,5 @@ move(op.join('modules', 'block', '_block.so'), op.join('.', '_block.so'))
|
|||||||
move(op.join('modules', 'block', '_block.pyd'), op.join('.', '_block.pyd'))
|
move(op.join('modules', 'block', '_block.pyd'), op.join('.', '_block.pyd'))
|
||||||
|
|
||||||
os.chdir('help')
|
os.chdir('help')
|
||||||
print_and_do('python gen.py')
|
gen.generate(windows=True)
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
@@ -23,6 +23,6 @@ class MainWindow(MainWindowBase):
|
|||||||
title = "Clear Picture Cache"
|
title = "Clear Picture Cache"
|
||||||
msg = "Do you really want to remove all your cached picture analysis?"
|
msg = "Do you really want to remove all your cached picture analysis?"
|
||||||
if self._confirm(title, msg, QMessageBox.No):
|
if self._confirm(title, msg, QMessageBox.No):
|
||||||
self.app.scanner.match_factory.cached_blocks.clear()
|
self.app.scanner.cached_blocks.clear()
|
||||||
QMessageBox.information(self, title, "Picture cache cleared.")
|
QMessageBox.information(self, title, "Picture cache cleared.")
|
||||||
|
|
||||||
@@ -14,6 +14,9 @@ import base.dg_rc
|
|||||||
|
|
||||||
from app import DupeGuru
|
from app import DupeGuru
|
||||||
|
|
||||||
|
# This is a workaround for a pyinstaller problem where compiled dupeguru can't read tiff files
|
||||||
|
from PIL import TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
app.setWindowIcon(QIcon(QPixmap(":/logo_pe")))
|
app.setWindowIcon(QIcon(QPixmap(":/logo_pe")))
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "dgbase/AppDelegate.h"
|
#import "dgbase/AppDelegate.h"
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase
|
@interface AppDelegate : AppDelegateBase
|
||||||
{
|
{
|
||||||
IBOutlet ResultWindow *result;
|
|
||||||
|
|
||||||
DirectoryPanel *_directoryPanel;
|
DirectoryPanel *_directoryPanel;
|
||||||
}
|
}
|
||||||
- (IBAction)openWebsite:(id)sender;
|
- (IBAction)openWebsite:(id)sender;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user