[#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)deleteMarked;
- (void)hardlinkMarked;
- (void)removeMarked;
//Data

View File

@ -38,6 +38,7 @@ http://www.hardcoded.net/licenses/hs_license
- (NSDictionary *)getColumnsWidth;
- (void)initResultColumns;
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted;
/* Actions */
- (IBAction)clearIgnoreList:(id)sender;
@ -45,6 +46,7 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)changePowerMarker:(id)sender;
- (IBAction)copyMarked:(id)sender;
- (IBAction)deleteMarked:(id)sender;
- (IBAction)hardlinkMarked:(id)sender;
- (IBAction)exportToXHTML:(id)sender;
- (IBAction)filter:(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 */
- (IBAction)clearIgnoreList:(id)sender
{
@ -168,14 +191,12 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)deleteMarked:(id)sender
{
NSInteger mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
[py deleteMarked];
[self sendMarkedToTrash:NO];
}
- (IBAction)hardlinkMarked:(id)sender
{
[self sendMarkedToTrash:YES];
}
- (IBAction)exportToXHTML:(id)sender

View File

@ -12,8 +12,8 @@
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="2"/>
<integer value="29"/>
<integer value="1147"/>
<integer value="598"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -87,7 +87,6 @@
<int key="NSvFlags">256</int>
<string key="NSFrame">{{7, 14}, {67, 25}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="431579725">
<int key="NSCellFlags">67239424</int>
@ -183,7 +182,6 @@
<int key="NSvFlags">258</int>
<string key="NSFrame">{{0, 14}, {81, 22}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSearchFieldCell" key="NSCell" id="484816507">
<int key="NSCellFlags">343014976</int>
@ -329,7 +327,6 @@
<int key="NSvFlags">256</int>
<string key="NSFrame">{{1, 14}, {40, 25}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="436420677">
<int key="NSCellFlags">-2076049856</int>
@ -385,6 +382,16 @@
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="436420677"/>
</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">
<reference key="NSMenu" ref="106411576"/>
<string key="NSTitle">Move Marked to...</string>
@ -505,6 +512,7 @@
</object>
</object>
</object>
<int key="NSSelectedIndex">2</int>
<bool key="NSPullDown">YES</bool>
<int key="NSPreferredEdge">3</int>
<bool key="NSUsesItemFromMenu">YES</bool>
@ -535,7 +543,6 @@
<int key="NSvFlags">256</int>
<string key="NSFrame">{{4, 14}, {67, 25}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="211272396">
<int key="NSCellFlags">67239424</int>
@ -1226,6 +1233,15 @@
<reference key="NSOnImage" ref="852972005"/>
<reference key="NSMixedImage" ref="218295580"/>
</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">
<reference key="NSMenu" ref="600111647"/>
<string key="NSTitle">Move Marked to...</string>
@ -2274,6 +2290,22 @@
</object>
<int key="connectionID">1226</int>
</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 class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@ -2556,6 +2588,7 @@
<reference ref="564101661"/>
<reference ref="747820446"/>
<reference ref="517397504"/>
<reference ref="514384201"/>
</object>
<reference key="parent" ref="528113253"/>
</object>
@ -2925,6 +2958,7 @@
<reference ref="707934795"/>
<reference ref="13829058"/>
<reference ref="698110866"/>
<reference ref="103810273"/>
</object>
<reference key="parent" ref="436420677"/>
</object>
@ -3172,6 +3206,16 @@
<reference key="object" ref="267036250"/>
<reference key="parent" ref="201009225"/>
</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 class="NSMutableDictionary" key="flattenedProperties">
@ -3232,6 +3276,10 @@
<string>1222.IBPluginDependency</string>
<string>1223.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.ImportedFromIB2</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>{{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>
@ -3488,6 +3536,10 @@
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<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"/>
<string>{{324, 289}, {557, 400}}</string>
@ -3529,7 +3581,7 @@
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>{{328, 475}, {361, 293}}</string>
<string>{{328, 455}, {383, 313}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3569,7 +3621,7 @@
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>{{94, 408}, {331, 243}}</string>
<string>{{310, 310}, {353, 263}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3669,7 +3721,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">1226</int>
<int key="maxID">1231</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -4065,6 +4117,7 @@
<string>deleteMarked:</string>
<string>exportToXHTML:</string>
<string>filter:</string>
<string>hardlinkMarked:</string>
<string>ignoreSelected:</string>
<string>invokeCustomCommand:</string>
<string>loadResults:</string>
@ -4121,6 +4174,7 @@
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="NSMutableDictionary" key="actionInfosByName">
@ -4134,6 +4188,7 @@
<string>deleteMarked:</string>
<string>exportToXHTML:</string>
<string>filter:</string>
<string>hardlinkMarked:</string>
<string>ignoreSelected:</string>
<string>invokeCustomCommand:</string>
<string>loadResults:</string>
@ -4188,6 +4243,10 @@
<string key="name">filter:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo">
<string key="name">hardlinkMarked:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo">
<string key="name">ignoreSelected:</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
# http://www.hardcoded.net/licenses/hs_license
import os
import os.path as op
import logging
@ -63,18 +61,22 @@ class DupeGuru(RegistrableApplication, Broadcaster):
else:
self.action_count += count
def _do_delete(self, j):
def _do_delete(self, j, replace_with_hardlinks):
def op(dupe):
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)
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):
return
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])
def _do_load(self, j):
@ -135,8 +137,8 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.selected_dupes = dupes
self.notify('dupes_selected')
def _start_job(self, jobid, func):
# func(j)
def _start_job(self, jobid, func, *args):
# func(j, *args)
raise NotImplementedError()
def add_directory(self, d):
@ -208,9 +210,9 @@ class DupeGuru(RegistrableApplication, Broadcaster):
jobid = JOB_COPY if copy else JOB_MOVE
self._start_job(jobid, do)
def delete_marked(self):
def delete_marked(self, replace_with_hardlinks=False):
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):
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):
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(str(path), '')
def _start_job(self, jobid, func):
def _start_job(self, jobid, func, *args):
try:
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:
NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self)
else:

View File

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

View File

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

View File

@ -139,10 +139,10 @@ class DupeGuruPE(app_cocoa.DupeGuru):
self.directories = Directories()
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):
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)]
self.path2iphoto = {}
@ -164,7 +164,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
self.results.perform_on_marked(op, True)
del self.path2iphoto
def _do_delete_dupe(self, dupe):
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
if isinstance(dupe, IPhoto):
if str(dupe.path) in self.path2iphoto:
photo = self.path2iphoto[str(dupe.path)]
@ -177,7 +177,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
msg = "Could not find photo %s in iPhoto Library" % str(dupe.path)
raise EnvironmentError(msg)
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):
p = Path(str_path)

View File

@ -131,11 +131,12 @@ class DupeGuru(DupeGuruBase, QObject):
def _reveal_path(path):
DupeGuru._open_path(path[:-1])
def _start_job(self, jobid, func):
def _start_job(self, jobid, func, *args):
title = JOBID2TITLE[jobid]
try:
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:
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)

View File

@ -44,6 +44,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actionInvokeCustomCommand.triggered.connect(self.app.invokeCustomCommand)
self.actionLoadResults.triggered.connect(self.loadResultsTriggered)
self.actionSaveResults.triggered.connect(self.saveResultsTriggered)
self.actionHardlinkMarked.triggered.connect(self.hardlinkTriggered)
def _setupUi(self):
self.setupUi(self)
@ -73,6 +74,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
actionMenu = QMenu('Actions', self.toolBar)
actionMenu.setIcon(QIcon(QPixmap(":/actions")))
actionMenu.addAction(self.actionDeleteMarked)
actionMenu.addAction(self.actionHardlinkMarked)
actionMenu.addAction(self.actionMoveMarked)
actionMenu.addAction(self.actionCopyMarked)
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:
self.setGeometry(self.app.prefs.mainWindowRect)
# Linux setup
# Platform-specific setup
if sys.platform == 'linux2':
self.actionCheckForUpdate.setVisible(False) # This only works on Windows
if sys.platform not in {'darwin', 'linux2'}:
self.actionHardlinkMarked.setVisible(False)
#--- Private
def _confirm(self, title, msg, default_button=QMessageBox.Yes):
@ -194,6 +198,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
url = QUrl.fromLocalFile(exported_path)
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):
title = "Select a results file to load"
files = "dupeGuru Results (*.dupeguru)"

View File

@ -64,6 +64,7 @@
<string>Actions</string>
</property>
<addaction name="actionDeleteMarked"/>
<addaction name="actionHardlinkMarked"/>
<addaction name="actionMoveMarked"/>
<addaction name="actionCopyMarked"/>
<addaction name="actionRemoveMarked"/>
@ -265,6 +266,14 @@
<string>Ctrl+D</string>
</property>
</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">
<property name="text">
<string>Move Marked to...</string>