1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2025-03-10 05:34:36 +00:00

[#92 state:fixed] Added an action to delete duplicates and then create hardlinks to group ref.

This commit is contained in:
Virgil Dupras 2010-09-25 15:37:18 +02:00
parent 01db7c4948
commit 359f9c0680
12 changed files with 150 additions and 38 deletions

View File

@ -39,6 +39,7 @@ http://www.hardcoded.net/licenses/hs_license
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType; - (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
- (void)deleteMarked; - (void)deleteMarked;
- (void)hardlinkMarked;
- (void)removeMarked; - (void)removeMarked;
//Data //Data

View File

@ -38,6 +38,7 @@ http://www.hardcoded.net/licenses/hs_license
- (NSDictionary *)getColumnsWidth; - (NSDictionary *)getColumnsWidth;
- (void)initResultColumns; - (void)initResultColumns;
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth; - (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted;
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender; - (IBAction)clearIgnoreList:(id)sender;
@ -45,6 +46,7 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)changePowerMarker:(id)sender; - (IBAction)changePowerMarker:(id)sender;
- (IBAction)copyMarked:(id)sender; - (IBAction)copyMarked:(id)sender;
- (IBAction)deleteMarked:(id)sender; - (IBAction)deleteMarked:(id)sender;
- (IBAction)hardlinkMarked:(id)sender;
- (IBAction)exportToXHTML:(id)sender; - (IBAction)exportToXHTML:(id)sender;
- (IBAction)filter:(id)sender; - (IBAction)filter:(id)sender;
- (IBAction)ignoreSelected:(id)sender; - (IBAction)ignoreSelected:(id)sender;

View File

@ -126,6 +126,29 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted
{
NSInteger mark_count = [[py getMarkCount] intValue];
if (!mark_count) {
return;
}
NSString *msg = @"You are about to send %d files to Trash. Continue?";
if (hardlinkDeleted) {
msg = @"You are about to send %d files to Trash (and hardlink them afterwards). Continue?";
}
if ([Dialogs askYesNo:[NSString stringWithFormat:msg,mark_count]] == NSAlertSecondButtonReturn) { // NO
return;
}
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
if (hardlinkDeleted) {
[py hardlinkMarked];
}
else {
[py deleteMarked];
}
}
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender - (IBAction)clearIgnoreList:(id)sender
{ {
@ -168,14 +191,12 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)deleteMarked:(id)sender - (IBAction)deleteMarked:(id)sender
{ {
NSInteger mark_count = [[py getMarkCount] intValue]; [self sendMarkedToTrash:NO];
if (!mark_count) }
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO - (IBAction)hardlinkMarked:(id)sender
return; {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [self sendMarkedToTrash:YES];
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
[py deleteMarked];
} }
- (IBAction)exportToXHTML:(id)sender - (IBAction)exportToXHTML:(id)sender

View File

@ -12,8 +12,8 @@
</object> </object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<integer value="2"/> <integer value="1147"/>
<integer value="29"/> <integer value="598"/>
</object> </object>
<object class="NSArray" key="IBDocument.PluginDependencies"> <object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
@ -87,7 +87,6 @@
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrame">{{7, 14}, {67, 25}}</string> <string key="NSFrame">{{7, 14}, {67, 25}}</string>
<reference key="NSSuperview"/> <reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="431579725"> <object class="NSSegmentedCell" key="NSCell" id="431579725">
<int key="NSCellFlags">67239424</int> <int key="NSCellFlags">67239424</int>
@ -183,7 +182,6 @@
<int key="NSvFlags">258</int> <int key="NSvFlags">258</int>
<string key="NSFrame">{{0, 14}, {81, 22}}</string> <string key="NSFrame">{{0, 14}, {81, 22}}</string>
<reference key="NSSuperview"/> <reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSSearchFieldCell" key="NSCell" id="484816507"> <object class="NSSearchFieldCell" key="NSCell" id="484816507">
<int key="NSCellFlags">343014976</int> <int key="NSCellFlags">343014976</int>
@ -329,7 +327,6 @@
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrame">{{1, 14}, {40, 25}}</string> <string key="NSFrame">{{1, 14}, {40, 25}}</string>
<reference key="NSSuperview"/> <reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="436420677"> <object class="NSPopUpButtonCell" key="NSCell" id="436420677">
<int key="NSCellFlags">-2076049856</int> <int key="NSCellFlags">-2076049856</int>
@ -385,6 +382,16 @@
<string key="NSAction">_popUpItemAction:</string> <string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="436420677"/> <reference key="NSTarget" ref="436420677"/>
</object> </object>
<object class="NSMenuItem" id="103810273">
<reference key="NSMenu" ref="106411576"/>
<string key="NSTitle">Delete Marked and Replace with Hardlinks</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="852972005"/>
<reference key="NSMixedImage" ref="218295580"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="436420677"/>
</object>
<object class="NSMenuItem" id="707934795"> <object class="NSMenuItem" id="707934795">
<reference key="NSMenu" ref="106411576"/> <reference key="NSMenu" ref="106411576"/>
<string key="NSTitle">Move Marked to...</string> <string key="NSTitle">Move Marked to...</string>
@ -505,6 +512,7 @@
</object> </object>
</object> </object>
</object> </object>
<int key="NSSelectedIndex">2</int>
<bool key="NSPullDown">YES</bool> <bool key="NSPullDown">YES</bool>
<int key="NSPreferredEdge">3</int> <int key="NSPreferredEdge">3</int>
<bool key="NSUsesItemFromMenu">YES</bool> <bool key="NSUsesItemFromMenu">YES</bool>
@ -535,7 +543,6 @@
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrame">{{4, 14}, {67, 25}}</string> <string key="NSFrame">{{4, 14}, {67, 25}}</string>
<reference key="NSSuperview"/> <reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="211272396"> <object class="NSSegmentedCell" key="NSCell" id="211272396">
<int key="NSCellFlags">67239424</int> <int key="NSCellFlags">67239424</int>
@ -1226,6 +1233,15 @@
<reference key="NSOnImage" ref="852972005"/> <reference key="NSOnImage" ref="852972005"/>
<reference key="NSMixedImage" ref="218295580"/> <reference key="NSMixedImage" ref="218295580"/>
</object> </object>
<object class="NSMenuItem" id="514384201">
<reference key="NSMenu" ref="600111647"/>
<string key="NSTitle">Delete Marked and Replace with Hardlinks</string>
<string key="NSKeyEquiv">T</string>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="852972005"/>
<reference key="NSMixedImage" ref="218295580"/>
</object>
<object class="NSMenuItem" id="207129050"> <object class="NSMenuItem" id="207129050">
<reference key="NSMenu" ref="600111647"/> <reference key="NSMenu" ref="600111647"/>
<string key="NSTitle">Move Marked to...</string> <string key="NSTitle">Move Marked to...</string>
@ -2274,6 +2290,22 @@
</object> </object>
<int key="connectionID">1226</int> <int key="connectionID">1226</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">hardlinkMarked:</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="514384201"/>
</object>
<int key="connectionID">1229</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">hardlinkMarked:</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="103810273"/>
</object>
<int key="connectionID">1231</int>
</object>
</object> </object>
<object class="IBMutableOrderedSet" key="objectRecords"> <object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects"> <object class="NSArray" key="orderedObjects">
@ -2556,6 +2588,7 @@
<reference ref="564101661"/> <reference ref="564101661"/>
<reference ref="747820446"/> <reference ref="747820446"/>
<reference ref="517397504"/> <reference ref="517397504"/>
<reference ref="514384201"/>
</object> </object>
<reference key="parent" ref="528113253"/> <reference key="parent" ref="528113253"/>
</object> </object>
@ -2925,6 +2958,7 @@
<reference ref="707934795"/> <reference ref="707934795"/>
<reference ref="13829058"/> <reference ref="13829058"/>
<reference ref="698110866"/> <reference ref="698110866"/>
<reference ref="103810273"/>
</object> </object>
<reference key="parent" ref="436420677"/> <reference key="parent" ref="436420677"/>
</object> </object>
@ -3172,6 +3206,16 @@
<reference key="object" ref="267036250"/> <reference key="object" ref="267036250"/>
<reference key="parent" ref="201009225"/> <reference key="parent" ref="201009225"/>
</object> </object>
<object class="IBObjectRecord">
<int key="objectID">1227</int>
<reference key="object" ref="514384201"/>
<reference key="parent" ref="600111647"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1230</int>
<reference key="object" ref="103810273"/>
<reference key="parent" ref="106411576"/>
</object>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="flattenedProperties"> <object class="NSMutableDictionary" key="flattenedProperties">
@ -3232,6 +3276,10 @@
<string>1222.IBPluginDependency</string> <string>1222.IBPluginDependency</string>
<string>1223.IBPluginDependency</string> <string>1223.IBPluginDependency</string>
<string>1224.IBPluginDependency</string> <string>1224.IBPluginDependency</string>
<string>1227.IBPluginDependency</string>
<string>1227.ImportedFromIB2</string>
<string>1230.IBPluginDependency</string>
<string>1230.ImportedFromIB2</string>
<string>134.IBPluginDependency</string> <string>134.IBPluginDependency</string>
<string>134.ImportedFromIB2</string> <string>134.ImportedFromIB2</string>
<string>136.IBPluginDependency</string> <string>136.IBPluginDependency</string>
@ -3444,7 +3492,7 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{294, 689}, {617, 0}}</string> <string>{{294, 462}, {617, 227}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3488,6 +3536,10 @@
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<boolean value="YES"/> <boolean value="YES"/>
<boolean value="YES"/> <boolean value="YES"/>
<string>{{324, 289}, {557, 400}}</string> <string>{{324, 289}, {557, 400}}</string>
@ -3529,7 +3581,7 @@
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>{{328, 475}, {361, 293}}</string> <string>{{328, 455}, {383, 313}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3569,7 +3621,7 @@
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>{{94, 408}, {331, 243}}</string> <string>{{310, 310}, {353, 263}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3669,7 +3721,7 @@
</object> </object>
</object> </object>
<nil key="sourceID"/> <nil key="sourceID"/>
<int key="maxID">1226</int> <int key="maxID">1231</int>
</object> </object>
<object class="IBClassDescriber" key="IBDocument.Classes"> <object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions"> <object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -4065,6 +4117,7 @@
<string>deleteMarked:</string> <string>deleteMarked:</string>
<string>exportToXHTML:</string> <string>exportToXHTML:</string>
<string>filter:</string> <string>filter:</string>
<string>hardlinkMarked:</string>
<string>ignoreSelected:</string> <string>ignoreSelected:</string>
<string>invokeCustomCommand:</string> <string>invokeCustomCommand:</string>
<string>loadResults:</string> <string>loadResults:</string>
@ -4121,6 +4174,7 @@
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="actionInfosByName"> <object class="NSMutableDictionary" key="actionInfosByName">
@ -4134,6 +4188,7 @@
<string>deleteMarked:</string> <string>deleteMarked:</string>
<string>exportToXHTML:</string> <string>exportToXHTML:</string>
<string>filter:</string> <string>filter:</string>
<string>hardlinkMarked:</string>
<string>ignoreSelected:</string> <string>ignoreSelected:</string>
<string>invokeCustomCommand:</string> <string>invokeCustomCommand:</string>
<string>loadResults:</string> <string>loadResults:</string>
@ -4188,6 +4243,10 @@
<string key="name">filter:</string> <string key="name">filter:</string>
<string key="candidateClassName">id</string> <string key="candidateClassName">id</string>
</object> </object>
<object class="IBActionInfo">
<string key="name">hardlinkMarked:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo"> <object class="IBActionInfo">
<string key="name">ignoreSelected:</string> <string key="name">ignoreSelected:</string>
<string key="candidateClassName">id</string> <string key="candidateClassName">id</string>

View File

@ -6,8 +6,6 @@
# 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
import os.path as op import os.path as op
import logging import logging
@ -63,18 +61,22 @@ class DupeGuru(RegistrableApplication, Broadcaster):
else: else:
self.action_count += count self.action_count += count
def _do_delete(self, j): def _do_delete(self, j, replace_with_hardlinks):
def op(dupe): def op(dupe):
j.add_progress() j.add_progress()
return self._do_delete_dupe(dupe) return self._do_delete_dupe(dupe, replace_with_hardlinks)
j.start_job(self.results.mark_count) j.start_job(self.results.mark_count)
self.results.perform_on_marked(op, True) self.results.perform_on_marked(op, True)
def _do_delete_dupe(self, dupe): def _do_delete_dupe(self, dupe, replace_with_hardlinks):
if not io.exists(dupe.path): if not io.exists(dupe.path):
return return
send2trash(str(dupe.path)) # Raises OSError when there's a problem send2trash(str(dupe.path)) # Raises OSError when there's a problem
if replace_with_hardlinks:
group = self.results.get_group_of_duplicate(dupe)
ref = group.ref
os.link(str(ref.path), str(dupe.path))
self.clean_empty_dirs(dupe.path[:-1]) self.clean_empty_dirs(dupe.path[:-1])
def _do_load(self, j): def _do_load(self, j):
@ -135,8 +137,8 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.selected_dupes = dupes self.selected_dupes = dupes
self.notify('dupes_selected') self.notify('dupes_selected')
def _start_job(self, jobid, func): def _start_job(self, jobid, func, *args):
# func(j) # func(j, *args)
raise NotImplementedError() raise NotImplementedError()
def add_directory(self, d): def add_directory(self, d):
@ -208,9 +210,9 @@ class DupeGuru(RegistrableApplication, Broadcaster):
jobid = JOB_COPY if copy else JOB_MOVE jobid = JOB_COPY if copy else JOB_MOVE
self._start_job(jobid, do) self._start_job(jobid, do)
def delete_marked(self): def delete_marked(self, replace_with_hardlinks=False):
self._demo_check() self._demo_check()
self._start_job(JOB_DELETE, self._do_delete) self._start_job(JOB_DELETE, self._do_delete, replace_with_hardlinks)
def export_to_xhtml(self, column_ids): def export_to_xhtml(self, column_ids):
column_ids = [colid for colid in column_ids if colid.isdigit()] column_ids = [colid for colid in column_ids if colid.isdigit()]

View File

@ -55,10 +55,11 @@ class DupeGuru(app.DupeGuru):
def _reveal_path(path): def _reveal_path(path):
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(str(path), '') NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(str(path), '')
def _start_job(self, jobid, func): def _start_job(self, jobid, func, *args):
try: try:
j = self.progress.create_job() j = self.progress.create_job()
self.progress.run_threaded(func, args=(j, )) args = tuple([j] + list(args))
self.progress.run_threaded(func, args=args)
except job.JobInProgressError: except job.JobInProgressError:
NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self) NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self)
else: else:

View File

@ -80,6 +80,9 @@ class PyDupeGuruBase(PyRegistrable):
def deleteMarked(self): def deleteMarked(self):
self.py.delete_marked() self.py.delete_marked()
def hardlinkMarked(self):
self.py.delete_marked(replace_with_hardlinks=True)
def applyFilter_(self, filter): def applyFilter_(self, filter):
self.py.apply_filter(filter) self.py.apply_filter(filter)

View File

@ -30,8 +30,8 @@ class DupeGuru(DupeGuruBase):
def __init__(self): def __init__(self):
DupeGuruBase.__init__(self, data, '/tmp', appid=4) DupeGuruBase.__init__(self, data, '/tmp', appid=4)
def _start_job(self, jobid, func): def _start_job(self, jobid, func, *args):
func(nulljob) func(nulljob, *args)
class CallLogger(object): class CallLogger(object):

View File

@ -139,10 +139,10 @@ class DupeGuruPE(app_cocoa.DupeGuru):
self.directories = Directories() self.directories = Directories()
self.scanner.cache_path = op.join(self.appdata, 'cached_pictures.db') self.scanner.cache_path = op.join(self.appdata, 'cached_pictures.db')
def _do_delete(self, j): def _do_delete(self, j, replace_with_hardlinks):
def op(dupe): def op(dupe):
j.add_progress() j.add_progress()
return self._do_delete_dupe(dupe) return self._do_delete_dupe(dupe, replace_with_hardlinks)
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)] marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
self.path2iphoto = {} self.path2iphoto = {}
@ -164,7 +164,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
self.results.perform_on_marked(op, True) self.results.perform_on_marked(op, True)
del self.path2iphoto del self.path2iphoto
def _do_delete_dupe(self, dupe): def _do_delete_dupe(self, dupe, replace_with_hardlinks):
if isinstance(dupe, IPhoto): if isinstance(dupe, IPhoto):
if str(dupe.path) in self.path2iphoto: if str(dupe.path) in self.path2iphoto:
photo = self.path2iphoto[str(dupe.path)] photo = self.path2iphoto[str(dupe.path)]
@ -177,7 +177,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
msg = "Could not find photo %s in iPhoto Library" % str(dupe.path) msg = "Could not find photo %s in iPhoto Library" % str(dupe.path)
raise EnvironmentError(msg) raise EnvironmentError(msg)
else: else:
app_cocoa.DupeGuru._do_delete_dupe(self, dupe) app_cocoa.DupeGuru._do_delete_dupe(self, dupe, replace_with_hardlinks)
def _get_file(self, str_path): def _get_file(self, str_path):
p = Path(str_path) p = Path(str_path)

View File

@ -131,11 +131,12 @@ class DupeGuru(DupeGuruBase, QObject):
def _reveal_path(path): def _reveal_path(path):
DupeGuru._open_path(path[:-1]) DupeGuru._open_path(path[:-1])
def _start_job(self, jobid, func): def _start_job(self, jobid, func, *args):
title = JOBID2TITLE[jobid] title = JOBID2TITLE[jobid]
try: try:
j = self._progress.create_job() j = self._progress.create_job()
self._progress.run(jobid, title, func, args=(j, )) args = tuple([j] + list(args))
self._progress.run(jobid, title, func, args=args)
except job.JobInProgressError: except job.JobInProgressError:
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
QMessageBox.information(self.main_window, 'Action in progress', msg) QMessageBox.information(self.main_window, 'Action in progress', msg)

View File

@ -44,6 +44,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actionInvokeCustomCommand.triggered.connect(self.app.invokeCustomCommand) self.actionInvokeCustomCommand.triggered.connect(self.app.invokeCustomCommand)
self.actionLoadResults.triggered.connect(self.loadResultsTriggered) self.actionLoadResults.triggered.connect(self.loadResultsTriggered)
self.actionSaveResults.triggered.connect(self.saveResultsTriggered) self.actionSaveResults.triggered.connect(self.saveResultsTriggered)
self.actionHardlinkMarked.triggered.connect(self.hardlinkTriggered)
def _setupUi(self): def _setupUi(self):
self.setupUi(self) self.setupUi(self)
@ -73,6 +74,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
actionMenu = QMenu('Actions', self.toolBar) actionMenu = QMenu('Actions', self.toolBar)
actionMenu.setIcon(QIcon(QPixmap(":/actions"))) actionMenu.setIcon(QIcon(QPixmap(":/actions")))
actionMenu.addAction(self.actionDeleteMarked) actionMenu.addAction(self.actionDeleteMarked)
actionMenu.addAction(self.actionHardlinkMarked)
actionMenu.addAction(self.actionMoveMarked) actionMenu.addAction(self.actionMoveMarked)
actionMenu.addAction(self.actionCopyMarked) actionMenu.addAction(self.actionCopyMarked)
actionMenu.addAction(self.actionRemoveMarked) actionMenu.addAction(self.actionRemoveMarked)
@ -99,9 +101,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if self.app.prefs.mainWindowRect is not None and not self.app.prefs.mainWindowIsMaximized: if self.app.prefs.mainWindowRect is not None and not self.app.prefs.mainWindowIsMaximized:
self.setGeometry(self.app.prefs.mainWindowRect) self.setGeometry(self.app.prefs.mainWindowRect)
# Linux setup # Platform-specific setup
if sys.platform == 'linux2': if sys.platform == 'linux2':
self.actionCheckForUpdate.setVisible(False) # This only works on Windows self.actionCheckForUpdate.setVisible(False) # This only works on Windows
if sys.platform not in {'darwin', 'linux2'}:
self.actionHardlinkMarked.setVisible(False)
#--- Private #--- Private
def _confirm(self, title, msg, default_button=QMessageBox.Yes): def _confirm(self, title, msg, default_button=QMessageBox.Yes):
@ -194,6 +198,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
url = QUrl.fromLocalFile(exported_path) url = QUrl.fromLocalFile(exported_path)
QDesktopServices.openUrl(url) QDesktopServices.openUrl(url)
def hardlinkTriggered(self):
count = self.app.results.mark_count
if not count:
return
title = "Delete and hardlink duplicates"
msg = "You are about to send {0} files to the trash and hardlink them afterwards. Continue?".format(count)
if self._confirm(title, msg):
self.app.delete_marked(replace_with_hardlinks=True)
def loadResultsTriggered(self): def loadResultsTriggered(self):
title = "Select a results file to load" title = "Select a results file to load"
files = "dupeGuru Results (*.dupeguru)" files = "dupeGuru Results (*.dupeguru)"

View File

@ -64,6 +64,7 @@
<string>Actions</string> <string>Actions</string>
</property> </property>
<addaction name="actionDeleteMarked"/> <addaction name="actionDeleteMarked"/>
<addaction name="actionHardlinkMarked"/>
<addaction name="actionMoveMarked"/> <addaction name="actionMoveMarked"/>
<addaction name="actionCopyMarked"/> <addaction name="actionCopyMarked"/>
<addaction name="actionRemoveMarked"/> <addaction name="actionRemoveMarked"/>
@ -265,6 +266,14 @@
<string>Ctrl+D</string> <string>Ctrl+D</string>
</property> </property>
</action> </action>
<action name="actionHardlinkMarked">
<property name="text">
<string>Delete Marked and Replace with Hardlinks</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+D</string>
</property>
</action>
<action name="actionMoveMarked"> <action name="actionMoveMarked">
<property name="text"> <property name="text">
<string>Move Marked to...</string> <string>Move Marked to...</string>