mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 16:11:39 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f21804c769 | ||
|
|
4bc05a8d46 | ||
|
|
eebe2b0e80 | ||
|
|
250a496a78 | ||
|
|
29163ed053 | ||
|
|
cc05661f9e | ||
|
|
89409c22d1 | ||
|
|
e2f240ebc9 | ||
|
|
8d56f4c33b | ||
|
|
36eccb7122 | ||
|
|
c8827769b4 | ||
|
|
12e6c400b9 | ||
|
|
4c273a7910 | ||
|
|
58da335b17 | ||
|
|
5b2d506462 | ||
|
|
531430d44a | ||
|
|
7450eec7eb | ||
|
|
3a5802435f | ||
|
|
1b6b058097 | ||
|
|
a5797a2350 | ||
|
|
e81a5147c5 | ||
|
|
565c990687 | ||
|
|
0ccdfe0e26 | ||
|
|
f8a558e3a7 | ||
|
|
c5fa195cc6 | ||
|
|
3a821edd45 | ||
|
|
854d194f88 | ||
|
|
fb79daad6a | ||
|
|
b2ae0e8759 |
2
.hgtags
2
.hgtags
@@ -22,3 +22,5 @@ cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||
f71d405e62badcfdc1b037facaac043cece40ee5 se2.10.1
|
||||
3742e83edd9eadf44e1a501859f5e2462b1ef6fd me5.8.1
|
||||
724ff565dd785fb739774588c6ee652cfc0612d5 pe1.9.1
|
||||
634b66415c6529f46ae4f837318027cc9d70c3b5 before-py3k
|
||||
|
||||
18
README
18
README
@@ -25,12 +25,11 @@ Before being able to build dupeGuru, a few dependencies have to be installed:
|
||||
General dependencies
|
||||
-----
|
||||
|
||||
- Python 2.6 (http://www.python.org)
|
||||
- Send2Trash (http://hg.hardcoded.net/send2trash)
|
||||
- hsutil (http://hg.hardcoded.net/hsutil)
|
||||
- hsaudiotag (for ME) (http://hg.hardcoded.net/hsaudiotag)
|
||||
- lxml, to read and write XML files. (http://codespeak.net/lxml/)
|
||||
- Mako, to generate help files. (http://www.makotemplates.org/)
|
||||
- Python 3.1 (http://www.python.org)
|
||||
- Send2Trash3k (http://hg.hardcoded.net/send2trash3k)
|
||||
- hsutil3k (http://hg.hardcoded.net/hsutil3k)
|
||||
- hsaudiotag3k (for ME) (http://hg.hardcoded.net/hsaudiotag3k)
|
||||
- Markdown, to generate help files. (http://pypi.python.org/pypi/Markdown)
|
||||
- PyYaml, for help files and the build system. (http://pyyaml.org/)
|
||||
- py.test, to run unit tests. (http://codespeak.net/py/dist/test/)
|
||||
|
||||
@@ -39,15 +38,14 @@ OS X prerequisites
|
||||
|
||||
- XCode 3.1 (http://developer.apple.com/TOOLS/xcode/)
|
||||
- Sparkle (http://sparkle.andymatuschak.org/)
|
||||
- PyObjC 2.2. (http://pyobjc.sourceforge.net/)
|
||||
- py2app (http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html)
|
||||
- PyObjC 2.3. (http://pyobjc.sourceforge.net/)
|
||||
- py2app 0.5.4 (http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html)
|
||||
|
||||
Windows prerequisites
|
||||
---
|
||||
|
||||
- Visual Studio 2008 (Express is enough) is needed to build C extensions. (http://www.microsoft.com/Express/)
|
||||
- PyQt 4.6 (http://www.riverbankcomputing.co.uk/news)
|
||||
- Python Imaging Library for dupeGuru PE. (http://www.pythonware.com/products/pil/)
|
||||
- PyQt 4.7 (http://www.riverbankcomputing.co.uk/news)
|
||||
- cx_Freeze, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://cx-freeze.sourceforge.net/)
|
||||
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
|
||||
|
||||
|
||||
62
build.py
62
build.py
@@ -13,6 +13,7 @@ import os.path as op
|
||||
import shutil
|
||||
|
||||
from setuptools import setup
|
||||
from distutils.extension import Extension
|
||||
import yaml
|
||||
|
||||
from hscommon import helpgen
|
||||
@@ -20,10 +21,10 @@ from hscommon.build import add_to_pythonpath, print_and_do, build_all_qt_ui, cop
|
||||
|
||||
def build_cocoa(edition, dev, help_destpath):
|
||||
if not dev:
|
||||
print "Building help index"
|
||||
print("Building help index")
|
||||
os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_destpath))
|
||||
|
||||
print "Building dg_cocoa.plugin"
|
||||
print("Building dg_cocoa.plugin")
|
||||
if op.exists('build'):
|
||||
shutil.rmtree('build')
|
||||
os.mkdir('build')
|
||||
@@ -54,7 +55,7 @@ def build_cocoa(edition, dev, help_destpath):
|
||||
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
|
||||
open(pthpath, 'w').write(op.abspath('.'))
|
||||
os.chdir(cocoa_project_path)
|
||||
print "Building the XCode project"
|
||||
print("Building the XCode project")
|
||||
args = []
|
||||
if dev:
|
||||
args.append('-configuration dev')
|
||||
@@ -65,25 +66,58 @@ def build_cocoa(edition, dev, help_destpath):
|
||||
os.chdir('..')
|
||||
|
||||
def build_qt(edition, dev):
|
||||
print("Building Qt stuff")
|
||||
build_all_qt_ui(op.join('qtlib', 'ui'))
|
||||
build_all_qt_ui(op.join('qt', 'base'))
|
||||
build_all_qt_ui(op.join('qt', edition))
|
||||
print_and_do("pyrcc4 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
||||
if edition == 'pe':
|
||||
os.chdir(op.join('qt', edition))
|
||||
os.system('python gen.py')
|
||||
os.chdir(op.join('..', '..'))
|
||||
print_and_do("pyrcc4 -py3 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
||||
|
||||
def build_pe_modules(ui):
|
||||
def move(src, dst):
|
||||
if not op.exists(src):
|
||||
return
|
||||
if op.exists(dst):
|
||||
os.remove(dst)
|
||||
print('Moving %s --> %s' % (src, dst))
|
||||
os.rename(src, dst)
|
||||
|
||||
print("Building PE Modules")
|
||||
exts = [
|
||||
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||
]
|
||||
if ui == 'qt':
|
||||
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
|
||||
elif ui == 'cocoa':
|
||||
exts.append(Extension(
|
||||
"_block_osx", [op.join('core_pe', 'modules', 'block_osx.m'), op.join('core_pe', 'modules', 'common.c')],
|
||||
extra_link_args=[
|
||||
"-framework", "CoreFoundation",
|
||||
"-framework", "Foundation",
|
||||
"-framework", "ApplicationServices",]
|
||||
))
|
||||
setup(
|
||||
script_args = ['build_ext', '--inplace'],
|
||||
ext_modules = exts,
|
||||
)
|
||||
move('_block.so', op.join('core_pe', '_block.so'))
|
||||
move('_block.pyd', op.join('core_pe', '_block.pyd'))
|
||||
move('_block_osx.so', op.join('core_pe', '_block_osx.so'))
|
||||
move('_cache.so', op.join('core_pe', '_cache.so'))
|
||||
move('_cache.pyd', op.join('core_pe', '_cache.pyd'))
|
||||
move('_block_qt.so', op.join('qt', 'pe', '_block.so'))
|
||||
move('_block_qt.pyd', op.join('qt', 'pe', '_block.pyd'))
|
||||
|
||||
def main():
|
||||
conf = yaml.load(open('conf.yaml'))
|
||||
edition = conf['edition']
|
||||
ui = conf['ui']
|
||||
dev = conf['dev']
|
||||
print "Building dupeGuru {0} with UI {1}".format(edition.upper(), ui)
|
||||
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||
if dev:
|
||||
print "Building in Dev mode"
|
||||
print("Building in Dev mode")
|
||||
add_to_pythonpath('.')
|
||||
print "Generating Help"
|
||||
print("Generating Help")
|
||||
windows = sys.platform == 'win32'
|
||||
profile = 'win_en' if windows else 'osx_en'
|
||||
help_dir = 'help_{0}'.format(edition)
|
||||
@@ -91,11 +125,9 @@ def main():
|
||||
help_basepath = op.abspath(help_dir)
|
||||
help_destpath = op.abspath(op.join(help_dir, dest_dir))
|
||||
helpgen.gen(help_basepath, help_destpath, profile=profile)
|
||||
print "Building dupeGuru"
|
||||
print("Building dupeGuru")
|
||||
if edition == 'pe':
|
||||
os.chdir('core_pe')
|
||||
os.system('python gen.py')
|
||||
os.chdir('..')
|
||||
build_pe_modules(ui)
|
||||
if ui == 'cocoa':
|
||||
build_cocoa(edition, dev, help_destpath)
|
||||
elif ui == 'qt':
|
||||
|
||||
@@ -14,7 +14,9 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
- (NSNumber *)addDirectory:(NSString *)name;
|
||||
- (void)removeDirectory:(NSNumber *)index;
|
||||
- (void)loadResults;
|
||||
- (void)loadResultsFrom:(NSString *)filename;
|
||||
- (void)saveResults;
|
||||
- (void)saveResultsAs:(NSString *)filename;
|
||||
- (void)loadIgnoreList;
|
||||
- (void)saveIgnoreList;
|
||||
- (void)clearIgnoreList;
|
||||
|
||||
@@ -49,6 +49,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
- (IBAction)filter:(id)sender;
|
||||
- (IBAction)ignoreSelected:(id)sender;
|
||||
- (IBAction)invokeCustomCommand:(id)sender;
|
||||
- (IBAction)loadResults:(id)sender;
|
||||
- (IBAction)markAll:(id)sender;
|
||||
- (IBAction)markInvert:(id)sender;
|
||||
- (IBAction)markNone:(id)sender;
|
||||
@@ -61,6 +62,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
- (IBAction)renameSelected:(id)sender;
|
||||
- (IBAction)resetColumnsToDefault:(id)sender;
|
||||
- (IBAction)revealSelected:(id)sender;
|
||||
- (IBAction)saveResults:(id)sender;
|
||||
- (IBAction)showPreferencesPanel:(id)sender;
|
||||
- (IBAction)startDuplicateScan:(id)sender;
|
||||
- (IBAction)switchSelected:(id)sender;
|
||||
|
||||
@@ -212,6 +212,21 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)loadResults:(id)sender
|
||||
{
|
||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||
[op setCanChooseFiles:YES];
|
||||
[op setCanChooseDirectories:NO];
|
||||
[op setCanCreateDirectories:NO];
|
||||
[op setAllowsMultipleSelection:NO];
|
||||
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||
[op setTitle:@"Select a results file to load"];
|
||||
if ([op runModal] == NSOKButton) {
|
||||
NSString *filename = [[op filenames] objectAtIndex:0];
|
||||
[py loadResultsFrom:filename];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)markAll:(id)sender
|
||||
{
|
||||
[py markAll];
|
||||
@@ -303,6 +318,17 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[preferencesPanel showWindow:sender];
|
||||
}
|
||||
|
||||
- (IBAction)saveResults:(id)sender
|
||||
{
|
||||
NSSavePanel *sp = [NSSavePanel savePanel];
|
||||
[sp setCanCreateDirectories:YES];
|
||||
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||
[sp setTitle:@"Select a file to save your results to"];
|
||||
if ([sp runModal] == NSOKButton) {
|
||||
[py saveResultsAs:[sp filename]];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)startDuplicateScan:(id)sender
|
||||
{
|
||||
// Virtual
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">10C540</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
||||
<string key="IBDocument.SystemVersion">10F569</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">740</string>
|
||||
<string key="NS.object.0">788</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -41,7 +41,7 @@
|
||||
<object class="NSWindowTemplate" id="476890502">
|
||||
<int key="NSWindowStyleMask">155</int>
|
||||
<int key="NSWindowBacking">2</int>
|
||||
<string key="NSWindowRect">{{33, 261}, {451, 161}}</string>
|
||||
<string key="NSWindowRect">{{33, 261}, {451, 146}}</string>
|
||||
<int key="NSWTFlags">-260571136</int>
|
||||
<string key="NSWindowTitle">Details of Selected File</string>
|
||||
<object class="NSMutableString" key="NSWindowClass">
|
||||
@@ -51,7 +51,7 @@
|
||||
<characters key="NS.bytes">View</characters>
|
||||
</object>
|
||||
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<string key="NSWindowContentMinSize">{451, 161}</string>
|
||||
<string key="NSWindowContentMinSize">{451, 146}</string>
|
||||
<object class="NSView" key="NSWindowView" id="1027711962">
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
@@ -70,7 +70,7 @@
|
||||
<object class="NSTableView" id="251969872">
|
||||
<reference key="NSNextResponder" ref="488480549"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<string key="NSFrameSize">{449, 143}</string>
|
||||
<string key="NSFrameSize">{449, 128}</string>
|
||||
<reference key="NSSuperview" ref="488480549"/>
|
||||
<int key="NSTag">2</int>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
@@ -224,7 +224,7 @@
|
||||
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrame">{{1, 17}, {449, 143}}</string>
|
||||
<string key="NSFrame">{{1, 17}, {449, 128}}</string>
|
||||
<reference key="NSSuperview" ref="362108788"/>
|
||||
<reference key="NSNextKeyView" ref="251969872"/>
|
||||
<reference key="NSDocView" ref="251969872"/>
|
||||
@@ -266,7 +266,7 @@
|
||||
</object>
|
||||
<reference ref="717380566"/>
|
||||
</object>
|
||||
<string key="NSFrameSize">{451, 161}</string>
|
||||
<string key="NSFrameSize">{451, 146}</string>
|
||||
<reference key="NSSuperview" ref="1027711962"/>
|
||||
<reference key="NSNextKeyView" ref="488480549"/>
|
||||
<int key="NSsFlags">530</int>
|
||||
@@ -278,12 +278,13 @@
|
||||
<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrameSize">{451, 161}</string>
|
||||
<string key="NSFrameSize">{451, 146}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
</object>
|
||||
<string key="NSScreenRect">{{0, 0}, {1024, 746}}</string>
|
||||
<string key="NSMinSize">{451, 177}</string>
|
||||
<string key="NSMinSize">{451, 162}</string>
|
||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<string key="NSFrameAutosaveName">DetailsPanel</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
@@ -497,12 +498,12 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{109, 656}, {451, 161}}</string>
|
||||
<string>{{109, 671}, {451, 146}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>{{109, 656}, {451, 161}}</string>
|
||||
<string>{{109, 671}, {451, 146}}</string>
|
||||
<boolean value="YES"/>
|
||||
<boolean value="YES"/>
|
||||
<string>{451, 161}</string>
|
||||
<string>{451, 146}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -536,11 +537,18 @@
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">DetailsPanel</string>
|
||||
<string key="superclassName">NSWindowController</string>
|
||||
<string key="superclassName">HSWindowController</string>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<string key="NS.object.0">NSTableView</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||
<string key="name">detailsTable</string>
|
||||
<string key="candidateClassName">NSTableView</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../base/DetailsPanel.h</string>
|
||||
@@ -548,7 +556,7 @@
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">DetailsPanel</string>
|
||||
<string key="superclassName">NSWindowController</string>
|
||||
<string key="superclassName">HSWindowController</string>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBUserSource</string>
|
||||
<string key="minorKey"/>
|
||||
@@ -562,6 +570,32 @@
|
||||
<string key="minorKey"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">HSWindowController</string>
|
||||
<string key="superclassName">NSWindowController</string>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../controllers/HSWindowController.h</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">NSObject</string>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../views/HSOutlineView.h</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">NSObject</string>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="111130406">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../views/NSTableViewAdditions.h</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">NSTableView</string>
|
||||
<reference key="sourceIdentifier" ref="111130406"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -1029,6 +1063,13 @@
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">showWindow:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBFrameworkSource</string>
|
||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||
@@ -1037,6 +1078,7 @@
|
||||
</object>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||
<integer value="1050" key="NS.object.0"/>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">10C540</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
||||
<string key="IBDocument.SystemVersion">10F569</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">740</string>
|
||||
<string key="NS.object.0">788</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -428,6 +428,7 @@
|
||||
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
||||
<string key="NSMinSize">{369, 291}</string>
|
||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<string key="NSFrameAutosaveName">DirectoryPanel</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
@@ -869,6 +870,35 @@
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>askForDirectory:</string>
|
||||
<string>popupAddDirectoryMenu:</string>
|
||||
<string>removeSelectedDirectory:</string>
|
||||
<string>toggleVisible:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">askForDirectory:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">popupAddDirectoryMenu:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">removeSelectedDirectory:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">toggleVisible:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
@@ -884,6 +914,30 @@
|
||||
<string>NSButton</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>addButtonPopUp</string>
|
||||
<string>outlineView</string>
|
||||
<string>removeButton</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">addButtonPopUp</string>
|
||||
<string key="candidateClassName">NSPopUpButton</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">outlineView</string>
|
||||
<string key="candidateClassName">HSOutlineView</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">removeButton</string>
|
||||
<string key="candidateClassName">NSButton</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../base/DirectoryPanel.h</string>
|
||||
@@ -1437,6 +1491,13 @@
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">showWindow:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBFrameworkSource</string>
|
||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||
@@ -1445,6 +1506,7 @@
|
||||
</object>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||
<integer value="1050" key="NS.object.0"/>
|
||||
@@ -1460,5 +1522,18 @@
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<string key="IBDocument.LastKnownRelativeProjectPath">../../se/dupeguru.xcodeproj</string>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>NSMenuCheckmark</string>
|
||||
<string>NSMenuMixedState</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>{9, 8}</string>
|
||||
<string>{7, 2}</string>
|
||||
</object>
|
||||
</object>
|
||||
</data>
|
||||
</archive>
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">10D573</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
||||
<string key="IBDocument.SystemVersion">10F569</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||
<string key="IBDocument.HIToolboxVersion">460.00</string>
|
||||
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">740</string>
|
||||
<string key="NS.object.0">788</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="598"/>
|
||||
<integer value="219"/>
|
||||
<integer value="1204"/>
|
||||
</object>
|
||||
<object class="NSArray" key="IBDocument.PluginDependencies">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -83,9 +82,11 @@
|
||||
<string key="NSToolbarItemPaletteLabel">Power Marker</string>
|
||||
<nil key="NSToolbarItemToolTip"/>
|
||||
<object class="NSSegmentedControl" key="NSToolbarItemView" id="35398541">
|
||||
<nil key="NSNextResponder"/>
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<string key="NSFrame">{{7, 14}, {67, 24}}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
<reference key="NSWindow"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSSegmentedCell" key="NSCell" id="431579725">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
@@ -176,9 +177,11 @@
|
||||
<string key="NSToolbarItemPaletteLabel">Filter</string>
|
||||
<nil key="NSToolbarItemToolTip"/>
|
||||
<object class="NSSearchField" key="NSToolbarItemView" id="1013657232">
|
||||
<nil key="NSNextResponder"/>
|
||||
<reference key="NSNextResponder"/>
|
||||
<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>
|
||||
@@ -320,9 +323,11 @@
|
||||
<string key="NSToolbarItemPaletteLabel">Action</string>
|
||||
<nil key="NSToolbarItemToolTip"/>
|
||||
<object class="NSPopUpButton" key="NSToolbarItemView" id="165812138">
|
||||
<nil key="NSNextResponder"/>
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<string key="NSFrame">{{0, 14}, {58, 26}}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
<reference key="NSWindow"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSPopUpButtonCell" key="NSCell" id="436420677">
|
||||
<int key="NSCellFlags">-2076049856</int>
|
||||
@@ -525,9 +530,11 @@
|
||||
<string key="NSToolbarItemPaletteLabel">Delta Values</string>
|
||||
<nil key="NSToolbarItemToolTip"/>
|
||||
<object class="NSSegmentedControl" key="NSToolbarItemView" id="311230297">
|
||||
<nil key="NSNextResponder"/>
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<string key="NSFrame">{{4, 14}, {67, 24}}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
<reference key="NSWindow"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSSegmentedCell" key="NSCell" id="211272396">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
@@ -674,7 +681,7 @@
|
||||
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<string key="NSWindowContentMinSize">{340, 340}</string>
|
||||
<object class="NSView" key="NSWindowView" id="455829030">
|
||||
<reference key="NSNextResponder"/>
|
||||
<nil key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<object class="NSMutableArray" key="NSSubviews">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -823,7 +830,6 @@
|
||||
</object>
|
||||
<string key="NSFrame">{{1, 17}, {515, 317}}</string>
|
||||
<reference key="NSSuperview" ref="417210994"/>
|
||||
<reference key="NSNextKeyView" ref="40047569"/>
|
||||
<reference key="NSDocView" ref="40047569"/>
|
||||
<reference key="NSBGColor" ref="91259834"/>
|
||||
<int key="NScvFlags">4</int>
|
||||
@@ -856,7 +862,6 @@
|
||||
</object>
|
||||
<string key="NSFrame">{{1, 0}, {515, 17}}</string>
|
||||
<reference key="NSSuperview" ref="417210994"/>
|
||||
<reference key="NSNextKeyView" ref="837301452"/>
|
||||
<reference key="NSDocView" ref="837301452"/>
|
||||
<reference key="NSBGColor" ref="91259834"/>
|
||||
<int key="NScvFlags">4</int>
|
||||
@@ -865,7 +870,6 @@
|
||||
</object>
|
||||
<string key="NSFrame">{{20, 45}, {517, 335}}</string>
|
||||
<reference key="NSSuperview" ref="455829030"/>
|
||||
<reference key="NSNextKeyView" ref="948758365"/>
|
||||
<int key="NSsFlags">562</int>
|
||||
<reference key="NSVScroller" ref="167459243"/>
|
||||
<reference key="NSHScroller" ref="916628114"/>
|
||||
@@ -897,7 +901,6 @@
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrameSize">{557, 400}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
</object>
|
||||
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
||||
<string key="NSMinSize">{340, 418}</string>
|
||||
@@ -1029,6 +1032,48 @@
|
||||
<string key="NSName">_NSAppleMenu</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="252491888">
|
||||
<reference key="NSMenu" ref="133452984"/>
|
||||
<string key="NSTitle">File</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="852972005"/>
|
||||
<reference key="NSMixedImage" ref="218295580"/>
|
||||
<string key="NSAction">submenuAction:</string>
|
||||
<object class="NSMenu" key="NSSubmenu" id="948321368">
|
||||
<string key="NSTitle">File</string>
|
||||
<object class="NSMutableArray" key="NSMenuItems">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSMenuItem" id="777321316">
|
||||
<reference key="NSMenu" ref="948321368"/>
|
||||
<string key="NSTitle">Load Results...</string>
|
||||
<string key="NSKeyEquiv">o</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="975401896">
|
||||
<reference key="NSMenu" ref="948321368"/>
|
||||
<string key="NSTitle">Save Results...</string>
|
||||
<string key="NSKeyEquiv">s</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="630362403">
|
||||
<reference key="NSMenu" ref="948321368"/>
|
||||
<string key="NSTitle">Export Results to XHTML</string>
|
||||
<string key="NSKeyEquiv">E</string>
|
||||
<int key="NSKeyEquivModMask">1048576</int>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="852972005"/>
|
||||
<reference key="NSMixedImage" ref="218295580"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="551331186">
|
||||
<reference key="NSMenu" ref="133452984"/>
|
||||
<string key="NSTitle">Edit</string>
|
||||
@@ -1137,7 +1182,7 @@
|
||||
<object class="NSMenuItem" id="1035429435">
|
||||
<reference key="NSMenu" ref="600111647"/>
|
||||
<string key="NSTitle">Start Duplicate Scan</string>
|
||||
<string key="NSKeyEquiv">s</string>
|
||||
<string key="NSKeyEquiv">d</string>
|
||||
<int key="NSKeyEquivModMask">1048576</int>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="852972005"/>
|
||||
@@ -1152,15 +1197,6 @@
|
||||
<reference key="NSOnImage" ref="852972005"/>
|
||||
<reference key="NSMixedImage" ref="218295580"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="630362403">
|
||||
<reference key="NSMenu" ref="600111647"/>
|
||||
<string key="NSTitle">Export Results to XHTML</string>
|
||||
<string key="NSKeyEquiv">E</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="189815600">
|
||||
<reference key="NSMenu" ref="600111647"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
@@ -2221,6 +2257,22 @@
|
||||
</object>
|
||||
<int key="connectionID">1178</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">saveResults:</string>
|
||||
<reference key="source" ref="339936126"/>
|
||||
<reference key="destination" ref="975401896"/>
|
||||
</object>
|
||||
<int key="connectionID">1207</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">loadResults:</string>
|
||||
<reference key="source" ref="339936126"/>
|
||||
<reference key="destination" ref="777321316"/>
|
||||
</object>
|
||||
<int key="connectionID">1208</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<object class="NSArray" key="orderedObjects">
|
||||
@@ -2336,6 +2388,7 @@
|
||||
<reference ref="385797557"/>
|
||||
<reference ref="958594216"/>
|
||||
<reference ref="551331186"/>
|
||||
<reference ref="252491888"/>
|
||||
</object>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">MainMenu</string>
|
||||
@@ -2545,7 +2598,6 @@
|
||||
<reference ref="578499792"/>
|
||||
<reference ref="189815600"/>
|
||||
<reference ref="564101661"/>
|
||||
<reference ref="630362403"/>
|
||||
<reference ref="747820446"/>
|
||||
<reference ref="517397504"/>
|
||||
</object>
|
||||
@@ -2621,11 +2673,6 @@
|
||||
<reference key="object" ref="564101661"/>
|
||||
<reference key="parent" ref="600111647"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">947</int>
|
||||
<reference key="object" ref="630362403"/>
|
||||
<reference key="parent" ref="600111647"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">618</int>
|
||||
<reference key="object" ref="385797557"/>
|
||||
@@ -3089,6 +3136,41 @@
|
||||
<reference key="object" ref="517397504"/>
|
||||
<reference key="parent" ref="600111647"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">1203</int>
|
||||
<reference key="object" ref="252491888"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="948321368"/>
|
||||
</object>
|
||||
<reference key="parent" ref="133452984"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">1204</int>
|
||||
<reference key="object" ref="948321368"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="777321316"/>
|
||||
<reference ref="975401896"/>
|
||||
<reference ref="630362403"/>
|
||||
</object>
|
||||
<reference key="parent" ref="252491888"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">1205</int>
|
||||
<reference key="object" ref="777321316"/>
|
||||
<reference key="parent" ref="948321368"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">1206</int>
|
||||
<reference key="object" ref="975401896"/>
|
||||
<reference key="parent" ref="948321368"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">947</int>
|
||||
<reference key="object" ref="630362403"/>
|
||||
<reference key="parent" ref="948321368"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="flattenedProperties">
|
||||
@@ -3113,6 +3195,7 @@
|
||||
<string>1028.ImportedFromIB2</string>
|
||||
<string>103.IBPluginDependency</string>
|
||||
<string>103.ImportedFromIB2</string>
|
||||
<string>106.IBEditorWindowLastContentRect</string>
|
||||
<string>106.IBPluginDependency</string>
|
||||
<string>106.ImportedFromIB2</string>
|
||||
<string>111.IBPluginDependency</string>
|
||||
@@ -3141,6 +3224,11 @@
|
||||
<string>1172.IBPluginDependency</string>
|
||||
<string>1173.IBPluginDependency</string>
|
||||
<string>1177.IBPluginDependency</string>
|
||||
<string>1203.IBPluginDependency</string>
|
||||
<string>1204.IBEditorWindowLastContentRect</string>
|
||||
<string>1204.IBPluginDependency</string>
|
||||
<string>1205.IBPluginDependency</string>
|
||||
<string>1206.IBPluginDependency</string>
|
||||
<string>134.IBPluginDependency</string>
|
||||
<string>134.ImportedFromIB2</string>
|
||||
<string>136.IBPluginDependency</string>
|
||||
@@ -3177,6 +3265,7 @@
|
||||
<string>222.ImportedFromIB2</string>
|
||||
<string>23.IBPluginDependency</string>
|
||||
<string>23.ImportedFromIB2</string>
|
||||
<string>24.IBEditorWindowLastContentRect</string>
|
||||
<string>24.IBPluginDependency</string>
|
||||
<string>24.ImportedFromIB2</string>
|
||||
<string>29.IBEditorWindowLastContentRect</string>
|
||||
@@ -3314,6 +3403,7 @@
|
||||
<string>955.ImportedFromIB2</string>
|
||||
<string>959.IBPluginDependency</string>
|
||||
<string>959.ImportedFromIB2</string>
|
||||
<string>960.IBEditorWindowLastContentRect</string>
|
||||
<string>960.IBPluginDependency</string>
|
||||
<string>960.ImportedFromIB2</string>
|
||||
<string>961.IBPluginDependency</string>
|
||||
@@ -3322,6 +3412,7 @@
|
||||
<string>962.ImportedFromIB2</string>
|
||||
<string>965.IBPluginDependency</string>
|
||||
<string>965.ImportedFromIB2</string>
|
||||
<string>966.IBEditorWindowLastContentRect</string>
|
||||
<string>966.IBPluginDependency</string>
|
||||
<string>966.ImportedFromIB2</string>
|
||||
<string>985.IBPluginDependency</string>
|
||||
@@ -3351,6 +3442,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{602, 725}, {196, 43}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3368,7 +3460,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{79, 766}, {617, 0}}</string>
|
||||
<string>{{409, 745}, {617, 0}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3380,6 +3472,11 @@
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>{{242, 705}, {258, 63}}</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>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
@@ -3415,9 +3512,10 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{531, 625}, {193, 143}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{140, 768}, {481, 20}}</string>
|
||||
<string>{{140, 768}, {523, 20}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3448,7 +3546,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{286, 455}, {361, 313}}</string>
|
||||
<string>{{328, 475}, {361, 293}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3468,7 +3566,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{355, 762}, {64, 6}}</string>
|
||||
<string>{{397, 762}, {64, 6}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{182, 609}, {331, 133}}</string>
|
||||
@@ -3552,6 +3650,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{475, 725}, {167, 43}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3560,6 +3659,7 @@
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>{{284, 615}, {188, 153}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES"/>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
@@ -3586,7 +3686,7 @@
|
||||
</object>
|
||||
</object>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">1178</int>
|
||||
<int key="maxID">1208</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
@@ -3607,6 +3707,25 @@
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>openWebsite:</string>
|
||||
<string>toggleDirectories:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">openWebsite:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">toggleDirectories:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">AppDelegate.h</string>
|
||||
@@ -3619,6 +3738,13 @@
|
||||
<string key="NS.key.0">unlockApp:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">unlockApp:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">unlockApp:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
@@ -3634,6 +3760,30 @@
|
||||
<string>NSMenuItem</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>py</string>
|
||||
<string>recentDirectories</string>
|
||||
<string>unlockMenuItem</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">py</string>
|
||||
<string key="candidateClassName">PyDupeGuru</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">recentDirectories</string>
|
||||
<string key="candidateClassName">RecentDirectories</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">unlockMenuItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBUserSource</string>
|
||||
<string key="minorKey"/>
|
||||
@@ -3646,6 +3796,13 @@
|
||||
<string key="NS.key.0">unlockApp:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">unlockApp:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">unlockApp:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
@@ -3663,6 +3820,35 @@
|
||||
<string>NSMenuItem</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>py</string>
|
||||
<string>recentDirectories</string>
|
||||
<string>result</string>
|
||||
<string>unlockMenuItem</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">py</string>
|
||||
<string key="candidateClassName">PyDupeGuruBase</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">recentDirectories</string>
|
||||
<string key="candidateClassName">RecentDirectories</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">result</string>
|
||||
<string key="candidateClassName">ResultWindowBase</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">unlockMenuItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../base/AppDelegate.h</string>
|
||||
@@ -3771,6 +3957,25 @@
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>clearMenu:</string>
|
||||
<string>menuClick:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">clearMenu:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">menuClick:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
@@ -3784,6 +3989,25 @@
|
||||
<string>NSMenu</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>delegate</string>
|
||||
<string>menu</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">delegate</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">menu</string>
|
||||
<string key="candidateClassName">NSMenu</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../RecentDirectories.h</string>
|
||||
@@ -3813,6 +4037,25 @@
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>resetColumnsToDefault:</string>
|
||||
<string>startDuplicateScan:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">resetColumnsToDefault:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">startDuplicateScan:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">ResultWindow.h</string>
|
||||
@@ -3834,6 +4077,7 @@
|
||||
<string>filter:</string>
|
||||
<string>ignoreSelected:</string>
|
||||
<string>invokeCustomCommand:</string>
|
||||
<string>loadResults:</string>
|
||||
<string>markAll:</string>
|
||||
<string>markInvert:</string>
|
||||
<string>markNone:</string>
|
||||
@@ -3846,6 +4090,7 @@
|
||||
<string>renameSelected:</string>
|
||||
<string>resetColumnsToDefault:</string>
|
||||
<string>revealSelected:</string>
|
||||
<string>saveResults:</string>
|
||||
<string>showPreferencesPanel:</string>
|
||||
<string>startDuplicateScan:</string>
|
||||
<string>switchSelected:</string>
|
||||
@@ -3884,6 +4129,167 @@
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>changeDelta:</string>
|
||||
<string>changePowerMarker:</string>
|
||||
<string>clearIgnoreList:</string>
|
||||
<string>copyMarked:</string>
|
||||
<string>deleteMarked:</string>
|
||||
<string>exportToXHTML:</string>
|
||||
<string>filter:</string>
|
||||
<string>ignoreSelected:</string>
|
||||
<string>invokeCustomCommand:</string>
|
||||
<string>loadResults:</string>
|
||||
<string>markAll:</string>
|
||||
<string>markInvert:</string>
|
||||
<string>markNone:</string>
|
||||
<string>markSelected:</string>
|
||||
<string>moveMarked:</string>
|
||||
<string>openClicked:</string>
|
||||
<string>openSelected:</string>
|
||||
<string>removeMarked:</string>
|
||||
<string>removeSelected:</string>
|
||||
<string>renameSelected:</string>
|
||||
<string>resetColumnsToDefault:</string>
|
||||
<string>revealSelected:</string>
|
||||
<string>saveResults:</string>
|
||||
<string>showPreferencesPanel:</string>
|
||||
<string>startDuplicateScan:</string>
|
||||
<string>switchSelected:</string>
|
||||
<string>toggleColumn:</string>
|
||||
<string>toggleDelta:</string>
|
||||
<string>toggleDetailsPanel:</string>
|
||||
<string>togglePowerMarker:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">changeDelta:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">changePowerMarker:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">clearIgnoreList:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">copyMarked:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">deleteMarked:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">exportToXHTML:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">filter:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">ignoreSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">invokeCustomCommand:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">loadResults:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">markAll:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">markInvert:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">markNone:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">markSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">moveMarked:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">openClicked:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">openSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">removeMarked:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">removeSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">renameSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">resetColumnsToDefault:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">revealSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">saveResults:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">showPreferencesPanel:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">startDuplicateScan:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">switchSelected:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">toggleColumn:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">toggleDelta:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">toggleDetailsPanel:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo">
|
||||
<string key="name">togglePowerMarker:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
@@ -3911,6 +4317,55 @@
|
||||
<string>NSTextField</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>app</string>
|
||||
<string>columnsMenu</string>
|
||||
<string>deltaSwitch</string>
|
||||
<string>filterField</string>
|
||||
<string>matches</string>
|
||||
<string>pmSwitch</string>
|
||||
<string>py</string>
|
||||
<string>stats</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">app</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">columnsMenu</string>
|
||||
<string key="candidateClassName">NSMenu</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">deltaSwitch</string>
|
||||
<string key="candidateClassName">NSSegmentedControl</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">filterField</string>
|
||||
<string key="candidateClassName">NSSearchField</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">matches</string>
|
||||
<string key="candidateClassName">HSOutlineView</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">pmSwitch</string>
|
||||
<string key="candidateClassName">NSSegmentedControl</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">py</string>
|
||||
<string key="candidateClassName">PyDupeGuruBase</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">stats</string>
|
||||
<string key="candidateClassName">NSTextField</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../base/ResultWindow.h</string>
|
||||
@@ -4502,6 +4957,13 @@
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">showWindow:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBFrameworkSource</string>
|
||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||
@@ -4514,15 +4976,30 @@
|
||||
<string key="NS.key.0">checkForUpdates:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">checkForUpdates:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">checkForUpdates:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<string key="NS.key.0">delegate</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<string key="NS.key.0">delegate</string>
|
||||
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||
<string key="name">delegate</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<reference key="sourceIdentifier" ref="311396825"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||
<integer value="1050" key="NS.object.0"/>
|
||||
@@ -4538,5 +5015,28 @@
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<string key="IBDocument.LastKnownRelativeProjectPath">../../se/dupeguru.xcodeproj</string>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>NSActionTemplate</string>
|
||||
<string>NSApplicationIcon</string>
|
||||
<string>NSMenuCheckmark</string>
|
||||
<string>NSMenuMixedState</string>
|
||||
<string>details32</string>
|
||||
<string>folder32</string>
|
||||
<string>preferences32</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>{15, 15}</string>
|
||||
<string>{128, 128}</string>
|
||||
<string>{9, 8}</string>
|
||||
<string>{7, 2}</string>
|
||||
<string>{48, 48}</string>
|
||||
<string>{32, 32}</string>
|
||||
<string>{32, 32}</string>
|
||||
</object>
|
||||
</object>
|
||||
</data>
|
||||
</archive>
|
||||
|
||||
@@ -20,7 +20,7 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
{
|
||||
[super awakeFromNib];
|
||||
[[self window] setTitle:@"dupeGuru Music Edition"];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,6)];
|
||||
[deltaColumns removeIndex:6];
|
||||
[outline setDeltaColumns:deltaColumns];
|
||||
}
|
||||
@@ -38,13 +38,13 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[columnsOrder addObject:@"2"];
|
||||
[columnsOrder addObject:@"3"];
|
||||
[columnsOrder addObject:@"4"];
|
||||
[columnsOrder addObject:@"16"];
|
||||
[columnsOrder addObject:@"15"];
|
||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||
[columnsWidth setObject:i2n(214) forKey:@"0"];
|
||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||
[columnsWidth setObject:i2n(50) forKey:@"3"];
|
||||
[columnsWidth setObject:i2n(50) forKey:@"4"];
|
||||
[columnsWidth setObject:i2n(57) forKey:@"16"];
|
||||
[columnsWidth setObject:i2n(57) forKey:@"15"];
|
||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||
}
|
||||
|
||||
@@ -69,8 +69,6 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
||||
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
||||
NSInteger r = n2i([py doScan]);
|
||||
if (r == 1)
|
||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
||||
if (r == 3)
|
||||
{
|
||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||
@@ -96,18 +94,17 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[_resultColumns addObject:brCol];
|
||||
[_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:7 title:@"Creation" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Title" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Artist" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Album" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Genre" width:80 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:15 title:@"Comment" width:120 refCol:refCol]];
|
||||
[_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:18 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Title" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Artist" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Album" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Genre" width:80 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Year" width:40 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Track Number" width:40 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Comment" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Match %" width:57 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:16 title:@"Words Used" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||
}
|
||||
|
||||
/* Notifications */
|
||||
|
||||
@@ -8,13 +8,12 @@ from hscommon.cocoa import signature
|
||||
|
||||
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||
from core_me.app_cocoa import DupeGuruME
|
||||
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
||||
from core.scanner import ScanType
|
||||
|
||||
# Fix py2app imports which chokes on relative imports and other stuff
|
||||
from core_me import app_cocoa, data, fs, scanner
|
||||
from hsaudiotag import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
||||
from lxml import etree, _elementpath
|
||||
import xml.etree.ElementPath
|
||||
import gzip
|
||||
|
||||
class PyDupeGuru(PyDupeGuruBase):
|
||||
@@ -41,12 +40,12 @@ class PyDupeGuru(PyDupeGuruBase):
|
||||
def setScanType_(self, scan_type):
|
||||
try:
|
||||
self.py.scanner.scan_type = [
|
||||
SCAN_TYPE_FILENAME,
|
||||
SCAN_TYPE_FIELDS,
|
||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG,
|
||||
SCAN_TYPE_CONTENT,
|
||||
SCAN_TYPE_CONTENT_AUDIO
|
||||
ScanType.Filename,
|
||||
ScanType.Fields,
|
||||
ScanType.FieldsNoOrder,
|
||||
ScanType.Tag,
|
||||
ScanType.Contents,
|
||||
ScanType.ContentsAudio,
|
||||
][scan_type]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
||||
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */; };
|
||||
CE4B59C91119919700C06C9E /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C61119919700C06C9E /* progress.xib */; };
|
||||
CE4B59CA1119919700C06C9E /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C71119919700C06C9E /* registration.xib */; };
|
||||
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
|
||||
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; };
|
||||
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; };
|
||||
@@ -61,6 +60,7 @@
|
||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
|
||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
|
||||
CECC563B12144A9000ABF262 /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CECC563912144A9000ABF262 /* registration.xib */; };
|
||||
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDF07A2112493B200EE5BC0 /* StatsLabel.m */; };
|
||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
||||
@@ -133,7 +133,6 @@
|
||||
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
||||
CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ErrorReportWindow.xib; sourceTree = "<group>"; };
|
||||
CE4B59C61119919700C06C9E /* progress.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = progress.xib; sourceTree = "<group>"; };
|
||||
CE4B59C71119919700C06C9E /* registration.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = registration.xib; sourceTree = "<group>"; };
|
||||
CE515DE00FC6C12E00EC695D /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = ../../cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
|
||||
CE515DE10FC6C12E00EC695D /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
||||
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -165,6 +164,7 @@
|
||||
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
||||
CE900AD1109B238600754048 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
||||
CE900AD6109B2A9B00754048 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
||||
CECC563A12144A9000ABF262 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = ../../cocoalib/en.lproj/registration.xib; sourceTree = SOURCE_ROOT; };
|
||||
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||
CEDF07A0112493B200EE5BC0 /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -342,9 +342,9 @@
|
||||
CE4B59C41119919700C06C9E /* xib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CECC563912144A9000ABF262 /* registration.xib */,
|
||||
CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */,
|
||||
CE4B59C61119919700C06C9E /* progress.xib */,
|
||||
CE4B59C71119919700C06C9E /* registration.xib */,
|
||||
);
|
||||
name = xib;
|
||||
path = ../../cocoalib/xib;
|
||||
@@ -451,6 +451,13 @@
|
||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||
compatibilityVersion = "Xcode 3.0";
|
||||
hasScannedForEncodings = 1;
|
||||
knownRegions = (
|
||||
English,
|
||||
Japanese,
|
||||
French,
|
||||
German,
|
||||
en,
|
||||
);
|
||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -478,8 +485,8 @@
|
||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */,
|
||||
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */,
|
||||
CE4B59C91119919700C06C9E /* progress.xib in Resources */,
|
||||
CE4B59CA1119919700C06C9E /* registration.xib in Resources */,
|
||||
CE0A0C061175A24800DCA3C6 /* ProblemDialog.xib in Resources */,
|
||||
CECC563B12144A9000ABF262 /* registration.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -523,6 +530,18 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
CECC563912144A9000ABF262 /* registration.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CECC563A12144A9000ABF262 /* en */,
|
||||
);
|
||||
name = registration.xib;
|
||||
path = ../../cocoalib/xib;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C01FCF4C08A954540054247B /* release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
||||
@@ -20,9 +20,8 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
{
|
||||
[super awakeFromNib];
|
||||
[[self window] setTitle:@"dupeGuru Picture Edition"];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)];
|
||||
[deltaColumns removeIndex:3];
|
||||
[deltaColumns removeIndex:4];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
|
||||
[deltaColumns addIndex:5];
|
||||
[outline setDeltaColumns:deltaColumns];
|
||||
}
|
||||
|
||||
@@ -41,13 +40,13 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[columnsOrder addObject:@"1"];
|
||||
[columnsOrder addObject:@"2"];
|
||||
[columnsOrder addObject:@"4"];
|
||||
[columnsOrder addObject:@"7"];
|
||||
[columnsOrder addObject:@"6"];
|
||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||
[columnsWidth setObject:i2n(121) forKey:@"0"];
|
||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
||||
[columnsWidth setObject:i2n(58) forKey:@"7"];
|
||||
[columnsWidth setObject:i2n(58) forKey:@"6"];
|
||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||
}
|
||||
|
||||
@@ -66,8 +65,6 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
int r = n2i([py doScan]);
|
||||
if (r != 0)
|
||||
[[ProgressController mainProgressController] hide];
|
||||
if (r == 1)
|
||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
||||
if (r == 3)
|
||||
{
|
||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||
@@ -92,9 +89,8 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[_resultColumns addObject:sizeCol];
|
||||
[_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:5 title:@"Creation" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_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:5 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Match %" width:58 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||
}
|
||||
@end
|
||||
@@ -8,9 +8,13 @@ from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||
from core_pe import app_cocoa as app_pe_cocoa
|
||||
|
||||
# Fix py2app imports which chokes on relative imports and other stuff
|
||||
from core_pe import block, cache, matchbase, data, _block_osx
|
||||
from lxml import etree, _elementpath
|
||||
import hsutil.conflict
|
||||
import core.engine, core.fs, core.app
|
||||
import core_pe.block, core_pe.cache, core_pe.matchbase, core_pe.data, core_pe._block_osx
|
||||
import xml.etree.ElementPath
|
||||
import gzip
|
||||
import aem.kae
|
||||
import appscript.defaultterminology
|
||||
|
||||
class PyDupeGuru(PyDupeGuruBase):
|
||||
def init(self):
|
||||
@@ -23,10 +27,10 @@ class PyDupeGuru(PyDupeGuruBase):
|
||||
|
||||
#---Information
|
||||
def getSelectedDupePath(self):
|
||||
return unicode(self.py.selected_dupe_path())
|
||||
return str(self.py.selected_dupe_path())
|
||||
|
||||
def getSelectedDupeRefPath(self):
|
||||
return unicode(self.py.selected_dupe_ref_path())
|
||||
return str(self.py.selected_dupe_ref_path())
|
||||
|
||||
#---Properties
|
||||
def setMatchScaled_(self,match_scaled):
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
CE77C8A810946CE20078B0DB /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE77C8A710946CE20078B0DB /* DetailsPanel.xib */; };
|
||||
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */; };
|
||||
CE7AC9191119911200D02F6C /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9161119911200D02F6C /* progress.xib */; };
|
||||
CE7AC91A1119911200D02F6C /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9171119911200D02F6C /* registration.xib */; };
|
||||
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1C0FC192D60086DCA6 /* Dialogs.m */; };
|
||||
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */; };
|
||||
CE80DB310FC192D60086DCA6 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB220FC192D60086DCA6 /* ProgressController.m */; };
|
||||
@@ -41,6 +40,7 @@
|
||||
CE80DB8B0FC1951C0086DCA6 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */; };
|
||||
CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB890FC1951C0086DCA6 /* ResultWindow.m */; };
|
||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||
CE895D7B12144A7800E74705 /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE895D7912144A7800E74705 /* registration.xib */; };
|
||||
CE95865E112C516400F95FD2 /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE95865B112C516400F95FD2 /* ResultOutline.m */; };
|
||||
CE95865F112C516400F95FD2 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE95865D112C516400F95FD2 /* StatsLabel.m */; };
|
||||
CE9EA7561122C96C008CD2BC /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7441122C96C008CD2BC /* HSGUIController.m */; };
|
||||
@@ -110,7 +110,6 @@
|
||||
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailsPanel.xib; sourceTree = "<group>"; };
|
||||
CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ErrorReportWindow.xib; sourceTree = "<group>"; };
|
||||
CE7AC9161119911200D02F6C /* progress.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = progress.xib; sourceTree = "<group>"; };
|
||||
CE7AC9171119911200D02F6C /* registration.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = registration.xib; sourceTree = "<group>"; };
|
||||
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; };
|
||||
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -139,6 +138,7 @@
|
||||
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
||||
CE80DB890FC1951C0086DCA6 /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
||||
CE895D7A12144A7800E74705 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = ../../cocoalib/en.lproj/registration.xib; sourceTree = SOURCE_ROOT; };
|
||||
CE958658112C516400F95FD2 /* PyResultTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTree.h; path = ../base/PyResultTree.h; sourceTree = SOURCE_ROOT; };
|
||||
CE958659112C516400F95FD2 /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||
CE95865A112C516400F95FD2 /* ResultOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultOutline.h; path = ../base/ResultOutline.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -294,9 +294,9 @@
|
||||
CE7AC9141119911200D02F6C /* xib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE895D7912144A7800E74705 /* registration.xib */,
|
||||
CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */,
|
||||
CE7AC9161119911200D02F6C /* progress.xib */,
|
||||
CE7AC9171119911200D02F6C /* registration.xib */,
|
||||
);
|
||||
name = xib;
|
||||
path = ../../cocoalib/xib;
|
||||
@@ -458,6 +458,13 @@
|
||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||
compatibilityVersion = "Xcode 3.0";
|
||||
hasScannedForEncodings = 1;
|
||||
knownRegions = (
|
||||
English,
|
||||
Japanese,
|
||||
French,
|
||||
German,
|
||||
en,
|
||||
);
|
||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -486,8 +493,8 @@
|
||||
CE031754109B345200517EE6 /* MainMenu.xib in Resources */,
|
||||
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */,
|
||||
CE7AC9191119911200D02F6C /* progress.xib in Resources */,
|
||||
CE7AC91A1119911200D02F6C /* registration.xib in Resources */,
|
||||
CE0C2AC81177021600BC749F /* ProblemDialog.xib in Resources */,
|
||||
CE895D7B12144A7800E74705 /* registration.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -534,6 +541,18 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
CE895D7912144A7800E74705 /* registration.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CE895D7A12144A7800E74705 /* en */,
|
||||
);
|
||||
name = registration.xib;
|
||||
path = ../../cocoalib/xib;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C01FCF4C08A954540054247B /* release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">10C540</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
||||
<string key="IBDocument.SystemVersion">10F569</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">740</string>
|
||||
<string key="NS.object.0">788</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -434,6 +434,7 @@
|
||||
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
||||
<string key="NSMinSize">{451, 177}</string>
|
||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<string key="NSFrameAutosaveName">DetailsPanel</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
@@ -866,6 +867,13 @@
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<string key="NS.object.0">NSTableView</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||
<string key="name">detailsTable</string>
|
||||
<string key="candidateClassName">NSTableView</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">../base/DetailsPanel.h</string>
|
||||
@@ -891,6 +899,35 @@
|
||||
<string>NSProgressIndicator</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>dupeImage</string>
|
||||
<string>dupeProgressIndicator</string>
|
||||
<string>refImage</string>
|
||||
<string>refProgressIndicator</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">dupeImage</string>
|
||||
<string key="candidateClassName">NSImageView</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">dupeProgressIndicator</string>
|
||||
<string key="candidateClassName">NSProgressIndicator</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">refImage</string>
|
||||
<string key="candidateClassName">NSImageView</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo">
|
||||
<string key="name">refProgressIndicator</string>
|
||||
<string key="candidateClassName">NSProgressIndicator</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">DetailsPanel.h</string>
|
||||
@@ -903,6 +940,13 @@
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<string key="NS.object.0">NSTableView</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<string key="NS.key.0">detailsTable</string>
|
||||
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||
<string key="name">detailsTable</string>
|
||||
<string key="candidateClassName">NSTableView</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBUserSource</string>
|
||||
<string key="minorKey"/>
|
||||
@@ -1423,6 +1467,13 @@
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<string key="NS.object.0">id</string>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||
<string key="NS.key.0">showWindow:</string>
|
||||
<object class="IBActionInfo" key="NS.object.0">
|
||||
<string key="name">showWindow:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBFrameworkSource</string>
|
||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||
@@ -1431,6 +1482,7 @@
|
||||
</object>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||
<integer value="1050" key="NS.object.0"/>
|
||||
@@ -1446,5 +1498,9 @@
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<nil key="IBDocument.LastKnownRelativeProjectPath"/>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
|
||||
<string key="NS.key.0">NSApplicationIcon</string>
|
||||
<string key="NS.object.0">{128, 128}</string>
|
||||
</object>
|
||||
</data>
|
||||
</archive>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>hsft</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.10.1</string>
|
||||
<string>2.11.0</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -18,8 +18,8 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,4)];
|
||||
[deltaColumns removeIndex:3];
|
||||
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
|
||||
[deltaColumns addIndex:4];
|
||||
[outline setDeltaColumns:deltaColumns];
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[columnsOrder addObject:@"0"];
|
||||
[columnsOrder addObject:@"1"];
|
||||
[columnsOrder addObject:@"2"];
|
||||
[columnsOrder addObject:@"6"];
|
||||
[columnsOrder addObject:@"5"];
|
||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||
[columnsWidth setObject:i2n(195) forKey:@"0"];
|
||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||
[columnsWidth setObject:i2n(60) forKey:@"6"];
|
||||
[columnsWidth setObject:i2n(60) forKey:@"5"];
|
||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
int r = n2i([py doScan]);
|
||||
if (r != 0)
|
||||
[[ProgressController mainProgressController] hide];
|
||||
if (r == 1)
|
||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
||||
if (r == 3)
|
||||
{
|
||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||
@@ -80,10 +78,9 @@ http://www.hardcoded.net/licenses/hs_license
|
||||
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
||||
[_resultColumns addObject:sizeCol];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Creation" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Match %" width:60 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Words Used" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Modification" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Match %" width:60 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Words Used" width:120 refCol:refCol]];
|
||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
|
||||
from hscommon.cocoa import signature
|
||||
|
||||
from core import scanner
|
||||
from core.scanner import ScanType
|
||||
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||
from core_se.app_cocoa import DupeGuru
|
||||
|
||||
# Fix py2app imports with chokes on relative imports and other stuff
|
||||
from core_se import fs, data
|
||||
from lxml import etree, _elementpath
|
||||
import hsutil.conflict
|
||||
import core.engine, core.fs, core.app
|
||||
import core_se.fs, core_se.data
|
||||
import xml.etree.ElementPath
|
||||
import gzip
|
||||
|
||||
class PyDupeGuru(PyDupeGuruBase):
|
||||
@@ -28,8 +30,8 @@ class PyDupeGuru(PyDupeGuruBase):
|
||||
def setScanType_(self,scan_type):
|
||||
try:
|
||||
self.py.scanner.scan_type = [
|
||||
scanner.SCAN_TYPE_FILENAME,
|
||||
scanner.SCAN_TYPE_CONTENT
|
||||
ScanType.Filename,
|
||||
ScanType.Contents,
|
||||
][scan_type]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
CE073F6309CAE1A3005C1D2F /* dupeguru_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_help */; };
|
||||
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */; };
|
||||
CE19BC6411199231007CCEB0 /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6111199231007CCEB0 /* progress.xib */; };
|
||||
CE19BC6511199231007CCEB0 /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6211199231007CCEB0 /* registration.xib */; };
|
||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.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 */; };
|
||||
@@ -33,6 +32,7 @@
|
||||
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F212113BC22D0010360B /* ResultOutline.m */; };
|
||||
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; };
|
||||
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
|
||||
CEBC6C3912144A4B007B43AE /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEBC6C3712144A4B007B43AE /* registration.xib */; };
|
||||
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */; };
|
||||
CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */; };
|
||||
CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7EA120FE675C80004E467 /* DetailsPanel.m */; };
|
||||
@@ -78,7 +78,6 @@
|
||||
CE073F5409CAE1A3005C1D2F /* dupeguru_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_help; path = ../../help_se/dupeguru_help; sourceTree = "<group>"; };
|
||||
CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ErrorReportWindow.xib; sourceTree = "<group>"; };
|
||||
CE19BC6111199231007CCEB0 /* progress.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = progress.xib; sourceTree = "<group>"; };
|
||||
CE19BC6211199231007CCEB0 /* registration.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = registration.xib; sourceTree = "<group>"; };
|
||||
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
||||
CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
||||
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||
@@ -118,6 +117,7 @@
|
||||
CE91F213113BC22D0010360B /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||
CE91F214113BC22D0010360B /* StatsLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StatsLabel.m; path = ../base/StatsLabel.m; sourceTree = SOURCE_ROOT; };
|
||||
CEAC6810109B0B7E00B43C85 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = xib/Preferences.xib; sourceTree = "<group>"; };
|
||||
CEBC6C3812144A4B007B43AE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = ../../cocoalib/en.lproj/registration.xib; sourceTree = SOURCE_ROOT; };
|
||||
CEBE4D72111F0EE1009AAC6D /* HSWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSWindowController.h; sourceTree = "<group>"; };
|
||||
CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSWindowController.m; sourceTree = "<group>"; };
|
||||
CEDD92D60FDD01640031C7B7 /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -249,9 +249,9 @@
|
||||
CE19BC5F11199231007CCEB0 /* xib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEBC6C3712144A4B007B43AE /* registration.xib */,
|
||||
CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */,
|
||||
CE19BC6111199231007CCEB0 /* progress.xib */,
|
||||
CE19BC6211199231007CCEB0 /* registration.xib */,
|
||||
);
|
||||
name = xib;
|
||||
path = ../../cocoalib/xib;
|
||||
@@ -422,6 +422,13 @@
|
||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||
compatibilityVersion = "Xcode 3.0";
|
||||
hasScannedForEncodings = 1;
|
||||
knownRegions = (
|
||||
English,
|
||||
Japanese,
|
||||
French,
|
||||
German,
|
||||
en,
|
||||
);
|
||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -449,8 +456,8 @@
|
||||
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */,
|
||||
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */,
|
||||
CE19BC6411199231007CCEB0 /* progress.xib in Resources */,
|
||||
CE19BC6511199231007CCEB0 /* registration.xib in Resources */,
|
||||
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */,
|
||||
CEBC6C3912144A4B007B43AE /* registration.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -493,6 +500,18 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
CEBC6C3712144A4B007B43AE /* registration.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
CEBC6C3812144A4B007B43AE /* en */,
|
||||
);
|
||||
name = registration.xib;
|
||||
path = ../../cocoalib/xib;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C01FCF4C08A954540054247B /* release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
||||
@@ -18,7 +18,7 @@ def main(edition, ui, dev):
|
||||
if ui not in ('cocoa', 'qt'):
|
||||
ui = 'cocoa' if sys.platform == 'darwin' else 'qt'
|
||||
build_type = 'Dev' if dev else 'Release'
|
||||
print "Configuring dupeGuru {0} for UI {1} ({2})".format(edition.upper(), ui, build_type)
|
||||
print("Configuring dupeGuru {0} for UI {1} ({2})".format(edition.upper(), ui, build_type))
|
||||
conf = {
|
||||
'edition': edition,
|
||||
'ui': ui,
|
||||
|
||||
57
core/app.py
57
core/app.py
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import os
|
||||
import os.path as op
|
||||
@@ -33,9 +33,6 @@ JOB_DELETE = 'job_delete'
|
||||
class NoScannableFileError(Exception):
|
||||
pass
|
||||
|
||||
class AllFilesAreRefError(Exception):
|
||||
pass
|
||||
|
||||
class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
DEMO_LIMIT_DESC = "In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied."
|
||||
|
||||
@@ -76,23 +73,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
def _do_delete_dupe(self, dupe):
|
||||
if not io.exists(dupe.path):
|
||||
return
|
||||
send2trash(unicode(dupe.path)) # Raises OSError when there's a problem
|
||||
send2trash(str(dupe.path)) # Raises OSError when there's a problem
|
||||
self.clean_empty_dirs(dupe.path[:-1])
|
||||
|
||||
def _do_load(self, j):
|
||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||
self.notify('directories_changed')
|
||||
j = j.start_subjob([1, 9])
|
||||
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
|
||||
files = flatten(g[:] for g in self.results.groups)
|
||||
try:
|
||||
for file in j.iter_with_progress(files, 'Reading metadata %d/%d'):
|
||||
file._read_all_info(attrnames=self.data.METADATA_TO_READ)
|
||||
except (OSError, IOError):
|
||||
# If this error is raised, it means that a file was deleted while we were reading
|
||||
# metadata. Proper handling of this rare occurrence is complex because there's no easy
|
||||
# way to remove an arbitrary file from the Results. Thus, we simply clear them.
|
||||
self.results.groups = []
|
||||
|
||||
def _get_display_info(self, dupe, group, delta=False):
|
||||
if (dupe is None) or (group is None):
|
||||
@@ -100,12 +87,19 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
try:
|
||||
return self.data.GetDisplayInfo(dupe, group, delta)
|
||||
except Exception as e:
|
||||
logging.warning("Exception on GetDisplayInfo for %s: %s", unicode(dupe.path), unicode(e))
|
||||
logging.warning("Exception on GetDisplayInfo for %s: %s", str(dupe.path), str(e))
|
||||
return ['---'] * len(self.data.COLUMNS)
|
||||
|
||||
def _get_file(self, str_path):
|
||||
path = Path(str_path)
|
||||
return fs.get_file(path, self.directories.fileclasses)
|
||||
f = fs.get_file(path, self.directories.fileclasses)
|
||||
if f is None:
|
||||
return None
|
||||
try:
|
||||
f._read_all_info(attrnames=self.data.METADATA_TO_READ)
|
||||
return f
|
||||
except EnvironmentError:
|
||||
return None
|
||||
|
||||
def _job_completed(self, jobid):
|
||||
# Must be called by subclasses when they detect that an async job is completed.
|
||||
@@ -149,7 +143,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
g = self.results.get_group_of_duplicate(dupe)
|
||||
for other in g:
|
||||
if other is not dupe:
|
||||
self.scanner.ignore_list.Ignore(unicode(other.path), unicode(dupe.path))
|
||||
self.scanner.ignore_list.Ignore(str(other.path), str(dupe.path))
|
||||
self.remove_duplicates(dupes)
|
||||
|
||||
def apply_filter(self, filter):
|
||||
@@ -208,7 +202,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
|
||||
def export_to_xhtml(self, column_ids):
|
||||
column_ids = [colid for colid in column_ids if colid.isdigit()]
|
||||
column_ids = map(int, column_ids)
|
||||
column_ids = list(map(int, column_ids))
|
||||
column_ids.sort()
|
||||
colnames = [col['display'] for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
|
||||
rows = []
|
||||
@@ -232,8 +226,8 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
dupe = self.selected_dupes[0]
|
||||
group = self.results.get_group_of_duplicate(dupe)
|
||||
ref = group.ref
|
||||
cmd = cmd.replace('%d', unicode(dupe.path))
|
||||
cmd = cmd.replace('%r', unicode(ref.path))
|
||||
cmd = cmd.replace('%d', str(dupe.path))
|
||||
cmd = cmd.replace('%r', str(ref.path))
|
||||
match = re.match(r'"([^"]+)"(.*)', cmd)
|
||||
if match is not None:
|
||||
# This code here is because subprocess. Popen doesn't seem to accept, under Windows,
|
||||
@@ -249,6 +243,11 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
self._start_job(JOB_LOAD, self._do_load)
|
||||
self.load_ignore_list()
|
||||
|
||||
def load_from(self, filename):
|
||||
def do(j):
|
||||
self.results.load_from_xml(filename, self._get_file, j)
|
||||
self._start_job(JOB_LOAD, do)
|
||||
|
||||
def load_ignore_list(self):
|
||||
p = op.join(self.appdata, 'ignore_list.xml')
|
||||
self.scanner.ignore_list.load_from_xml(p)
|
||||
@@ -313,7 +312,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
d.rename(newname)
|
||||
return True
|
||||
except (IndexError, fs.FSError) as e:
|
||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
||||
logging.warning("dupeGuru Warning: %s" % str(e))
|
||||
return False
|
||||
|
||||
def reveal_selected(self):
|
||||
@@ -324,7 +323,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
if not op.exists(self.appdata):
|
||||
os.makedirs(self.appdata)
|
||||
self.directories.save_to_file(op.join(self.appdata, 'last_directories.xml'))
|
||||
self.results.save_to_xml(op.join(self.appdata, 'last_results.xml'))
|
||||
if self.results.is_modified:
|
||||
self.results.save_to_xml(op.join(self.appdata, 'last_results.xml'))
|
||||
|
||||
def save_as(self, filename):
|
||||
self.results.save_to_xml(filename)
|
||||
# It's not because we saved it here that we don't want to save it in appdata when we quit
|
||||
self.results.is_modified = True
|
||||
|
||||
def save_ignore_list(self):
|
||||
if not op.exists(self.appdata):
|
||||
@@ -339,12 +344,8 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
logging.info('Scanning %d files' % len(files))
|
||||
self.results.groups = self.scanner.GetDupeGroups(files, j)
|
||||
|
||||
files = self.directories.get_files()
|
||||
first_file = first(files)
|
||||
if first_file is None:
|
||||
if not self.directories.has_any_file():
|
||||
raise NoScannableFileError()
|
||||
if first_file.is_ref and all(f.is_ref for f in files):
|
||||
raise AllFilesAreRefError()
|
||||
self.results.groups = []
|
||||
self._start_job(JOB_SCAN, do)
|
||||
|
||||
|
||||
@@ -49,11 +49,11 @@ class DupeGuru(app.DupeGuru):
|
||||
#--- Override
|
||||
@staticmethod
|
||||
def _open_path(path):
|
||||
NSWorkspace.sharedWorkspace().openFile_(unicode(path))
|
||||
NSWorkspace.sharedWorkspace().openFile_(str(path))
|
||||
|
||||
@staticmethod
|
||||
def _reveal_path(path):
|
||||
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(unicode(path), '')
|
||||
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(str(path), '')
|
||||
|
||||
def _start_job(self, jobid, func):
|
||||
try:
|
||||
@@ -76,6 +76,4 @@ class DupeGuru(app.DupeGuru):
|
||||
return 0
|
||||
except app.NoScannableFileError:
|
||||
return 3
|
||||
except app.AllFilesAreRefError:
|
||||
return 1
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ class PyDupeGuruBase(PyRegistrable):
|
||||
def loadResults(self):
|
||||
self.py.load()
|
||||
|
||||
def loadResultsFrom_(self, filename):
|
||||
self.py.load_from(filename)
|
||||
|
||||
def markAll(self):
|
||||
self.py.mark_all()
|
||||
|
||||
@@ -67,6 +70,9 @@ class PyDupeGuruBase(PyRegistrable):
|
||||
def saveResults(self):
|
||||
self.py.save()
|
||||
|
||||
def saveResultsAs_(self, filename):
|
||||
self.py.save_as(filename)
|
||||
|
||||
#---Actions
|
||||
def addSelectedToIgnoreList(self):
|
||||
self.py.add_selected_to_ignore_list()
|
||||
|
||||
@@ -11,7 +11,7 @@ from hsutil.str import format_time, FT_DECIMAL, format_size
|
||||
import time
|
||||
|
||||
def format_path(p):
|
||||
return unicode(p[:-1])
|
||||
return str(p[:-1])
|
||||
|
||||
def format_timestamp(t, delta):
|
||||
if delta:
|
||||
@@ -38,4 +38,4 @@ def format_dupe_count(c):
|
||||
return str(c) if c else '---'
|
||||
|
||||
def cmp_value(value):
|
||||
return value.lower() if isinstance(value, basestring) else value
|
||||
return value.lower() if isinstance(value, str) else value
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from lxml import etree
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from hsutil import io
|
||||
from hsutil.files import FileOrPath
|
||||
@@ -124,12 +124,19 @@ class Directories(object):
|
||||
else:
|
||||
return STATE_NORMAL
|
||||
|
||||
def has_any_file(self):
|
||||
try:
|
||||
next(self.get_files())
|
||||
return True
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
def load_from_file(self, infile):
|
||||
try:
|
||||
root = etree.parse(infile).getroot()
|
||||
except:
|
||||
root = ET.parse(infile).getroot()
|
||||
except Exception:
|
||||
return
|
||||
for rdn in root.iterchildren('root_directory'):
|
||||
for rdn in root.getiterator('root_directory'):
|
||||
attrib = rdn.attrib
|
||||
if 'path' not in attrib:
|
||||
continue
|
||||
@@ -138,7 +145,7 @@ class Directories(object):
|
||||
self.add_path(Path(path))
|
||||
except (AlreadyThereError, InvalidPathError):
|
||||
pass
|
||||
for sn in root.iterchildren('state'):
|
||||
for sn in root.getiterator('state'):
|
||||
attrib = sn.attrib
|
||||
if not ('path' in attrib and 'value' in attrib):
|
||||
continue
|
||||
@@ -148,15 +155,15 @@ class Directories(object):
|
||||
|
||||
def save_to_file(self, outfile):
|
||||
with FileOrPath(outfile, 'wb') as fp:
|
||||
root = etree.Element('directories')
|
||||
root = ET.Element('directories')
|
||||
for root_path in self:
|
||||
root_path_node = etree.SubElement(root, 'root_directory')
|
||||
root_path_node.set('path', unicode(root_path))
|
||||
for path, state in self.states.iteritems():
|
||||
state_node = etree.SubElement(root, 'state')
|
||||
state_node.set('path', unicode(path))
|
||||
state_node.set('value', unicode(state))
|
||||
tree = etree.ElementTree(root)
|
||||
root_path_node = ET.SubElement(root, 'root_directory')
|
||||
root_path_node.set('path', str(root_path))
|
||||
for path, state in self.states.items():
|
||||
state_node = ET.SubElement(root, 'state')
|
||||
state_node.set('path', str(path))
|
||||
state_node.set('value', str(state))
|
||||
tree = ET.ElementTree(root)
|
||||
tree.write(fp, encoding='utf-8')
|
||||
|
||||
def set_state(self, path, state):
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import difflib
|
||||
import itertools
|
||||
import logging
|
||||
@@ -25,15 +25,15 @@ NO_FIELD_ORDER) = range(3)
|
||||
JOB_REFRESH_RATE = 100
|
||||
|
||||
def getwords(s):
|
||||
if isinstance(s, unicode):
|
||||
if isinstance(s, str):
|
||||
s = normalize('NFD', s)
|
||||
s = multi_replace(s, "-_&+():;\\[]{}.,<>/?~!@#$*", ' ').lower()
|
||||
s = ''.join(c for c in s if c in string.ascii_letters + string.digits + string.whitespace)
|
||||
return filter(None, s.split(' ')) # filter() is to remove empty elements
|
||||
return [_f for _f in s.split(' ') if _f] # remove empty elements
|
||||
|
||||
def getfields(s):
|
||||
fields = [getwords(field) for field in s.split(' - ')]
|
||||
return filter(None, fields)
|
||||
return [_f for _f in fields if _f]
|
||||
|
||||
def unpack_fields(fields):
|
||||
result = []
|
||||
@@ -118,7 +118,7 @@ def build_word_dict(objects, j=job.nulljob):
|
||||
def merge_similar_words(word_dict):
|
||||
"""Take all keys in word_dict that are similar, and merge them together.
|
||||
"""
|
||||
keys = word_dict.keys()
|
||||
keys = list(word_dict.keys())
|
||||
keys.sort(key=len)# we want the shortest word to stay
|
||||
while keys:
|
||||
key = keys.pop(0)
|
||||
@@ -138,7 +138,7 @@ def reduce_common_words(word_dict, threshold):
|
||||
Because if we remove them, we will miss some duplicates!
|
||||
"""
|
||||
uncommon_words = set(word for word, objects in word_dict.items() if len(objects) < threshold)
|
||||
for word, objects in word_dict.items():
|
||||
for word, objects in list(word_dict.items()):
|
||||
if len(objects) < threshold:
|
||||
continue
|
||||
reduced = set()
|
||||
|
||||
@@ -13,7 +13,7 @@ from tempfile import mkdtemp
|
||||
# Yes, this is a very low-tech solution, but at least it doesn't have all these annoying dependency
|
||||
# and resource problems.
|
||||
|
||||
MAIN_TEMPLATE = u"""
|
||||
MAIN_TEMPLATE = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
@@ -104,33 +104,33 @@ $rows
|
||||
</html>
|
||||
"""
|
||||
|
||||
COLHEADERS_TEMPLATE = u"<th>{name}</th>"
|
||||
COLHEADERS_TEMPLATE = "<th>{name}</th>"
|
||||
|
||||
ROW_TEMPLATE = u"""
|
||||
ROW_TEMPLATE = """
|
||||
<tr>
|
||||
<td class="{indented}">{filename}</td>{cells}
|
||||
</tr>
|
||||
"""
|
||||
|
||||
CELL_TEMPLATE = u"""<td>{value}</td>"""
|
||||
CELL_TEMPLATE = """<td>{value}</td>"""
|
||||
|
||||
def export_to_xhtml(colnames, rows):
|
||||
# a row is a list of values with the first value being a flag indicating if the row should be indented
|
||||
if rows:
|
||||
assert len(rows[0]) == len(colnames) + 1 # + 1 is for the "indented" flag
|
||||
colheaders = u''.join(COLHEADERS_TEMPLATE.format(name=name) for name in colnames)
|
||||
colheaders = ''.join(COLHEADERS_TEMPLATE.format(name=name) for name in colnames)
|
||||
rendered_rows = []
|
||||
for row in rows:
|
||||
# [2:] is to remove the indented flag + filename
|
||||
indented = u'indented' if row[0] else u''
|
||||
indented = 'indented' if row[0] else ''
|
||||
filename = row[1]
|
||||
cells = u''.join(CELL_TEMPLATE.format(value=value) for value in row[2:])
|
||||
cells = ''.join(CELL_TEMPLATE.format(value=value) for value in row[2:])
|
||||
rendered_rows.append(ROW_TEMPLATE.format(indented=indented, filename=filename, cells=cells))
|
||||
rendered_rows = u''.join(rendered_rows)
|
||||
rendered_rows = ''.join(rendered_rows)
|
||||
# The main template can't use format because the css code uses {}
|
||||
content = MAIN_TEMPLATE.replace('$colheaders', colheaders).replace('$rows', rendered_rows)
|
||||
folder = mkdtemp()
|
||||
destpath = op.join(folder, u'export.htm')
|
||||
destpath = op.join(folder, 'export.htm')
|
||||
fp = open(destpath, 'w')
|
||||
fp.write(content.encode('utf-8'))
|
||||
fp.close()
|
||||
|
||||
12
core/fs.py
12
core/fs.py
@@ -12,7 +12,7 @@
|
||||
# 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
|
||||
@@ -25,13 +25,13 @@ 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):
|
||||
if isinstance(fsobject, str):
|
||||
name = fsobject
|
||||
elif isinstance(fsobject, File):
|
||||
name = fsobject.name
|
||||
else:
|
||||
name = ''
|
||||
parentname = unicode(parent) if parent is not None else ''
|
||||
parentname = str(parent) if parent is not None else ''
|
||||
Exception.__init__(self, message.format(name=name, parent=parentname))
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ class OperationError(FSError):
|
||||
class File(object):
|
||||
INITIAL_INFO = {
|
||||
'size': 0,
|
||||
'ctime': 0,
|
||||
'mtime': 0,
|
||||
'md5': '',
|
||||
'md5partial': '',
|
||||
@@ -82,10 +81,9 @@ class File(object):
|
||||
raise AttributeError()
|
||||
|
||||
def _read_info(self, field):
|
||||
if field in ('size', 'ctime', 'mtime'):
|
||||
if field in ('size', '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:
|
||||
@@ -119,7 +117,7 @@ class File(object):
|
||||
If `attrnames` is not None, caches only attrnames.
|
||||
"""
|
||||
if attrnames is None:
|
||||
attrnames = self.INITIAL_INFO.keys()
|
||||
attrnames = list(self.INITIAL_INFO.keys())
|
||||
for attrname in attrnames:
|
||||
if attrname not in self.__dict__:
|
||||
self._read_info(attrname)
|
||||
|
||||
@@ -32,7 +32,7 @@ class DetailsPanel(GUIObject):
|
||||
ref = group.ref if group is not None and group.ref is not dupe else None
|
||||
l2 = self.app._get_display_info(ref, group, False)
|
||||
names = [c['display'] for c in self.app.data.COLUMNS]
|
||||
self._table = zip(names, l1, l2)
|
||||
self._table = list(zip(names, l1, l2))
|
||||
|
||||
#--- Public
|
||||
def row_count(self):
|
||||
|
||||
@@ -62,7 +62,7 @@ class DirectoryTree(GUIObject, Tree):
|
||||
def _refresh(self):
|
||||
self.clear()
|
||||
for path in self.app.directories:
|
||||
self.append(DirectoryNode(self.app, path, unicode(path)))
|
||||
self.append(DirectoryNode(self.app, path, str(path)))
|
||||
|
||||
def add_directory(self, path):
|
||||
self.app.add_directory(path)
|
||||
|
||||
@@ -39,5 +39,5 @@ class ProblemRow(Row):
|
||||
Row.__init__(self, table)
|
||||
self.dupe = dupe
|
||||
self.msg = msg
|
||||
self.path = unicode(dupe.path)
|
||||
self.path = str(dupe.path)
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class ResultTree(GUIObject, Tree):
|
||||
|
||||
def _select_nodes(self, nodes):
|
||||
Tree._select_nodes(self, nodes)
|
||||
self.app._select_dupes(map(attrgetter('_dupe'), nodes))
|
||||
self.app._select_dupes(list(map(attrgetter('_dupe'), nodes)))
|
||||
|
||||
#--- Private
|
||||
def _refresh(self):
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from lxml import etree
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from hsutil.files import FileOrPath
|
||||
|
||||
@@ -22,7 +22,7 @@ class IgnoreList(object):
|
||||
self._count = 0
|
||||
|
||||
def __iter__(self):
|
||||
for first,seconds in self._ignored.iteritems():
|
||||
for first,seconds in self._ignored.items():
|
||||
for second in seconds:
|
||||
yield (first,second)
|
||||
|
||||
@@ -77,14 +77,16 @@ class IgnoreList(object):
|
||||
infile can be a file object or a filename.
|
||||
"""
|
||||
try:
|
||||
root = etree.parse(infile).getroot()
|
||||
root = ET.parse(infile).getroot()
|
||||
except Exception:
|
||||
return
|
||||
for fn in root.iterchildren('file'):
|
||||
file_elems = (e for e in root if e.tag == 'file')
|
||||
for fn in file_elems:
|
||||
file_path = fn.get('path')
|
||||
if not file_path:
|
||||
continue
|
||||
for sfn in fn.iterchildren('file'):
|
||||
subfile_elems = (e for e in fn if e.tag == 'file')
|
||||
for sfn in subfile_elems:
|
||||
subfile_path = sfn.get('path')
|
||||
if subfile_path:
|
||||
self.Ignore(file_path, subfile_path)
|
||||
@@ -94,14 +96,14 @@ class IgnoreList(object):
|
||||
|
||||
outfile can be a file object or a filename.
|
||||
"""
|
||||
root = etree.Element('ignore_list')
|
||||
root = ET.Element('ignore_list')
|
||||
for filename, subfiles in self._ignored.items():
|
||||
file_node = etree.SubElement(root, 'file')
|
||||
file_node = ET.SubElement(root, 'file')
|
||||
file_node.set('path', filename)
|
||||
for subfilename in subfiles:
|
||||
subfile_node = etree.SubElement(file_node, 'file')
|
||||
subfile_node = ET.SubElement(file_node, 'file')
|
||||
subfile_node.set('path', subfilename)
|
||||
tree = etree.ElementTree(root)
|
||||
tree = ET.ElementTree(root)
|
||||
with FileOrPath(outfile, 'wb') as fp:
|
||||
tree.write(fp, encoding='utf-8')
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
from lxml import etree
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from . import engine
|
||||
from hscommon.job import nulljob
|
||||
@@ -33,6 +33,7 @@ class Results(Markable):
|
||||
self.__marked_size = 0
|
||||
self.data = data_module
|
||||
self.problems = [] # (dupe, error_msg)
|
||||
self.is_modified = False
|
||||
|
||||
def _did_mark(self, dupe):
|
||||
self.__marked_size += dupe.size
|
||||
@@ -115,6 +116,7 @@ class Results(Markable):
|
||||
self.__group_of_duplicate[dupe] = g
|
||||
if not hasattr(dupe, 'is_ref'):
|
||||
dupe.is_ref = False
|
||||
self.is_modified = True
|
||||
old_filters = nonone(self.__filters, [])
|
||||
self.apply_filter(None)
|
||||
for filter_str in old_filters:
|
||||
@@ -147,7 +149,7 @@ class Results(Markable):
|
||||
self.__filters.append(filter_str)
|
||||
if self.__filtered_dupes is None:
|
||||
self.__filtered_dupes = flatten(g[:] for g in self.groups)
|
||||
self.__filtered_dupes = set(dupe for dupe in self.__filtered_dupes if filter_re.search(unicode(dupe.path)))
|
||||
self.__filtered_dupes = set(dupe for dupe in self.__filtered_dupes if filter_re.search(str(dupe.path)))
|
||||
filtered_groups = set()
|
||||
for dupe in self.__filtered_dupes:
|
||||
filtered_groups.add(self.get_group_of_duplicate(dupe))
|
||||
@@ -176,16 +178,16 @@ class Results(Markable):
|
||||
|
||||
self.apply_filter(None)
|
||||
try:
|
||||
root = etree.parse(infile).getroot()
|
||||
root = ET.parse(infile).getroot()
|
||||
except Exception:
|
||||
return
|
||||
group_elems = list(root.iterchildren('group'))
|
||||
group_elems = list(root.getiterator('group'))
|
||||
groups = []
|
||||
marked = set()
|
||||
for group_elem in j.iter_with_progress(group_elems, every=100):
|
||||
group = engine.Group()
|
||||
dupes = []
|
||||
for file_elem in group_elem.iterchildren('file'):
|
||||
for file_elem in group_elem.getiterator('file'):
|
||||
path = file_elem.get('path')
|
||||
words = file_elem.get('words', '')
|
||||
if not path:
|
||||
@@ -198,7 +200,7 @@ class Results(Markable):
|
||||
dupes.append(file)
|
||||
if file_elem.get('marked') == 'y':
|
||||
marked.add(file)
|
||||
for match_elem in group_elem.iterchildren('match'):
|
||||
for match_elem in group_elem.getiterator('match'):
|
||||
try:
|
||||
attrs = match_elem.attrib
|
||||
first_file = dupes[int(attrs['first'])]
|
||||
@@ -216,6 +218,7 @@ class Results(Markable):
|
||||
self.groups = groups
|
||||
for dupe_file in marked:
|
||||
self.mark(dupe_file)
|
||||
self.is_modified = False
|
||||
|
||||
def make_ref(self, dupe):
|
||||
g = self.get_group_of_duplicate(dupe)
|
||||
@@ -229,6 +232,7 @@ class Results(Markable):
|
||||
self.__total_count -= 1
|
||||
self.__total_size -= dupe.size
|
||||
self.__dupes = None
|
||||
self.is_modified = True
|
||||
|
||||
def perform_on_marked(self, func, remove_from_results):
|
||||
# Performs `func` on all marked dupes. If an EnvironmentError is raised during the call,
|
||||
@@ -241,7 +245,7 @@ class Results(Markable):
|
||||
func(dupe)
|
||||
to_remove.append(dupe)
|
||||
except EnvironmentError as e:
|
||||
self.problems.append((dupe, unicode(e)))
|
||||
self.problems.append((dupe, str(e)))
|
||||
if remove_from_results:
|
||||
self.remove_duplicates(to_remove)
|
||||
self.mark_none()
|
||||
@@ -269,13 +273,14 @@ class Results(Markable):
|
||||
for group in affected_groups:
|
||||
group.discard_matches()
|
||||
self.__dupes = None
|
||||
self.is_modified = True
|
||||
|
||||
def save_to_xml(self, outfile):
|
||||
self.apply_filter(None)
|
||||
root = etree.Element('results')
|
||||
root = ET.Element('results')
|
||||
# writer = XMLGenerator(outfile, 'utf-8')
|
||||
for g in self.groups:
|
||||
group_elem = etree.SubElement(root, 'group')
|
||||
group_elem = ET.SubElement(root, 'group')
|
||||
dupe2index = {}
|
||||
for index, d in enumerate(g):
|
||||
dupe2index[d] = index
|
||||
@@ -283,22 +288,23 @@ class Results(Markable):
|
||||
words = engine.unpack_fields(d.words)
|
||||
except AttributeError:
|
||||
words = ()
|
||||
file_elem = etree.SubElement(group_elem, 'file')
|
||||
file_elem = ET.SubElement(group_elem, 'file')
|
||||
try:
|
||||
file_elem.set('path', unicode(d.path))
|
||||
file_elem.set('path', str(d.path))
|
||||
file_elem.set('words', ','.join(words))
|
||||
except ValueError: # If there's an invalid character, just skip the file
|
||||
file_elem.set('path', '')
|
||||
file_elem.set('is_ref', ('y' if d.is_ref else 'n'))
|
||||
file_elem.set('marked', ('y' if self.is_marked(d) else 'n'))
|
||||
for match in g.matches:
|
||||
match_elem = etree.SubElement(group_elem, 'match')
|
||||
match_elem.set('first', unicode(dupe2index[match.first]))
|
||||
match_elem.set('second', unicode(dupe2index[match.second]))
|
||||
match_elem.set('percentage', unicode(int(match.percentage)))
|
||||
tree = etree.ElementTree(root)
|
||||
match_elem = ET.SubElement(group_elem, 'match')
|
||||
match_elem.set('first', str(dupe2index[match.first]))
|
||||
match_elem.set('second', str(dupe2index[match.second]))
|
||||
match_elem.set('percentage', str(int(match.percentage)))
|
||||
tree = ET.ElementTree(root)
|
||||
with FileOrPath(outfile, 'wb') as fp:
|
||||
tree.write(fp, encoding='utf-8')
|
||||
self.is_modified = False
|
||||
|
||||
def sort_dupes(self, key, asc=True, delta=False):
|
||||
if not self.__dupes:
|
||||
|
||||
232
core/scanner.py
232
core/scanner.py
@@ -1,109 +1,123 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2006/03/03
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
from hscommon import job
|
||||
from hsutil import io
|
||||
from hsutil.misc import dedupe
|
||||
from hsutil.str import get_file_ext, rem_file_ext
|
||||
|
||||
from . import engine
|
||||
from .ignore import IgnoreList
|
||||
|
||||
(SCAN_TYPE_FILENAME,
|
||||
SCAN_TYPE_FIELDS,
|
||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG,
|
||||
UNUSED, # Must not be removed. Constants here are what scan_type in the prefs are.
|
||||
SCAN_TYPE_CONTENT,
|
||||
SCAN_TYPE_CONTENT_AUDIO) = range(7)
|
||||
|
||||
SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year']
|
||||
|
||||
class Scanner(object):
|
||||
def __init__(self):
|
||||
self.ignore_list = IgnoreList()
|
||||
self.discarded_file_count = 0
|
||||
|
||||
def _getmatches(self, files, j):
|
||||
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]
|
||||
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
|
||||
def _key_func(dupe):
|
||||
return (not dupe.is_ref, -dupe.size)
|
||||
|
||||
@staticmethod
|
||||
def _tie_breaker(ref, dupe):
|
||||
refname = rem_file_ext(ref.name).lower()
|
||||
dupename = rem_file_ext(dupe.name).lower()
|
||||
if 'copy' in refname and 'copy' not in dupename:
|
||||
return True
|
||||
if refname.startswith(dupename) and (refname[len(dupename):].strip().isdigit()):
|
||||
return True
|
||||
return len(dupe.path) > len(ref.path)
|
||||
|
||||
def GetDupeGroups(self, files, j=job.nulljob):
|
||||
j = j.start_subjob([8, 2])
|
||||
for f in [f for f in files if not hasattr(f, 'is_ref')]:
|
||||
f.is_ref = False
|
||||
logging.info('Getting matches')
|
||||
matches = self._getmatches(files, j)
|
||||
logging.info('Found %d matches' % len(matches))
|
||||
j.set_progress(100, 'Removing false matches')
|
||||
if not self.mix_file_kind:
|
||||
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:
|
||||
j = j.start_subjob(2)
|
||||
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
||||
matches = [m for m in iter_matches
|
||||
if not self.ignore_list.AreIgnored(unicode(m.first.path), unicode(m.second.path))]
|
||||
logging.info('Grouping matches')
|
||||
groups = engine.get_groups(matches, j)
|
||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
||||
self.discarded_file_count = len(matched_files) - sum(len(g) for g in groups)
|
||||
groups = [g for g in groups if any(not f.is_ref for f in g)]
|
||||
logging.info('Created %d groups' % len(groups))
|
||||
j.set_progress(100, 'Doing group prioritization')
|
||||
for g in groups:
|
||||
g.prioritize(self._key_func, self._tie_breaker)
|
||||
return groups
|
||||
|
||||
match_similar_words = False
|
||||
min_match_percentage = 80
|
||||
mix_file_kind = True
|
||||
scan_type = SCAN_TYPE_FILENAME
|
||||
scanned_tags = set(['artist', 'title'])
|
||||
size_threshold = 0
|
||||
word_weighting = False
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2006/03/03
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from hscommon import job
|
||||
from hsutil import io
|
||||
from hsutil.misc import dedupe
|
||||
from hsutil.str import get_file_ext, rem_file_ext
|
||||
|
||||
from . import engine
|
||||
from .ignore import IgnoreList
|
||||
|
||||
class ScanType:
|
||||
Filename = 0
|
||||
Fields = 1
|
||||
FieldsNoOrder = 2
|
||||
Tag = 3
|
||||
# number 4 is obsolete
|
||||
Contents = 5
|
||||
ContentsAudio = 6
|
||||
|
||||
SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year']
|
||||
|
||||
RE_DIGIT_ENDING = re.compile(r'\d+|\(\d+\)|\[\d+\]|{\d+}')
|
||||
|
||||
def is_same_with_digit(name, refname):
|
||||
# Returns True if name is the same as refname, but with digits (with brackets or not) at the end
|
||||
if not name.startswith(refname):
|
||||
return False
|
||||
end = name[len(refname):].strip()
|
||||
return RE_DIGIT_ENDING.match(end) is not None
|
||||
|
||||
class Scanner(object):
|
||||
def __init__(self):
|
||||
self.ignore_list = IgnoreList()
|
||||
self.discarded_file_count = 0
|
||||
|
||||
def _getmatches(self, files, j):
|
||||
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]
|
||||
if self.scan_type in (ScanType.Contents, ScanType.ContentsAudio):
|
||||
sizeattr = 'size' if self.scan_type == ScanType.Contents else 'audiosize'
|
||||
return engine.getmatches_by_contents(files, sizeattr, partial=self.scan_type==ScanType.ContentsAudio, 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 == ScanType.FieldsNoOrder:
|
||||
self.scan_type = ScanType.Fields
|
||||
kw['no_field_order'] = True
|
||||
func = {
|
||||
ScanType.Filename: lambda f: engine.getwords(rem_file_ext(f.name)),
|
||||
ScanType.Fields: lambda f: engine.getfields(rem_file_ext(f.name)),
|
||||
ScanType.Tag: lambda f: [engine.getwords(str(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
|
||||
def _key_func(dupe):
|
||||
return (not dupe.is_ref, -dupe.size)
|
||||
|
||||
@staticmethod
|
||||
def _tie_breaker(ref, dupe):
|
||||
refname = rem_file_ext(ref.name).lower()
|
||||
dupename = rem_file_ext(dupe.name).lower()
|
||||
if 'copy' in dupename:
|
||||
return False
|
||||
if 'copy' in refname:
|
||||
return True
|
||||
if is_same_with_digit(dupename, refname):
|
||||
return False
|
||||
if is_same_with_digit(refname, dupename):
|
||||
return True
|
||||
return len(dupe.path) > len(ref.path)
|
||||
|
||||
def GetDupeGroups(self, files, j=job.nulljob):
|
||||
j = j.start_subjob([8, 2])
|
||||
for f in [f for f in files if not hasattr(f, 'is_ref')]:
|
||||
f.is_ref = False
|
||||
logging.info('Getting matches')
|
||||
matches = self._getmatches(files, j)
|
||||
logging.info('Found %d matches' % len(matches))
|
||||
j.set_progress(100, 'Removing false matches')
|
||||
if not self.mix_file_kind:
|
||||
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:
|
||||
j = j.start_subjob(2)
|
||||
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
||||
matches = [m for m in iter_matches
|
||||
if not self.ignore_list.AreIgnored(str(m.first.path), str(m.second.path))]
|
||||
logging.info('Grouping matches')
|
||||
groups = engine.get_groups(matches, j)
|
||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
||||
self.discarded_file_count = len(matched_files) - sum(len(g) for g in groups)
|
||||
groups = [g for g in groups if any(not f.is_ref for f in g)]
|
||||
logging.info('Created %d groups' % len(groups))
|
||||
j.set_progress(100, 'Doing group prioritization')
|
||||
for g in groups:
|
||||
g.prioritize(self._key_func, self._tie_breaker)
|
||||
return groups
|
||||
|
||||
match_similar_words = False
|
||||
min_match_percentage = 80
|
||||
mix_file_kind = True
|
||||
scan_type = ScanType.Filename
|
||||
scanned_tags = set(['artist', 'title'])
|
||||
size_threshold = 0
|
||||
word_weighting = False
|
||||
|
||||
@@ -109,7 +109,7 @@ class TCDupeGuru(TestCase):
|
||||
|
||||
def test_Scan_with_objects_evaluating_to_false(self):
|
||||
class FakeFile(fs.File):
|
||||
def __nonzero__(self):
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class TCDupeGuru(TestCase):
|
||||
f1, f2 = [FakeFile('foo') for i in range(2)]
|
||||
f1.is_ref, f2.is_ref = (False, False)
|
||||
assert not (bool(f1) and bool(f2))
|
||||
app.directories.get_files = lambda: [f1, f2]
|
||||
app.directories.get_files = lambda: iter([f1, f2])
|
||||
app.directories._dirs.append('this is just so Scan() doesnt return 3')
|
||||
app.start_scanning() # no exception
|
||||
|
||||
@@ -200,11 +200,11 @@ class TCDupeGuruWithResults(TestCase):
|
||||
if expected is not None:
|
||||
expected = set(expected)
|
||||
not_called = expected - calls
|
||||
assert not not_called, u"These calls haven't been made: {0}".format(not_called)
|
||||
assert not not_called, "These calls haven't been made: {0}".format(not_called)
|
||||
if not_expected is not None:
|
||||
not_expected = set(not_expected)
|
||||
called = not_expected & calls
|
||||
assert not called, u"These calls shouldn't have been made: {0}".format(called)
|
||||
assert not called, "These calls shouldn't have been made: {0}".format(called)
|
||||
gui.clear_calls()
|
||||
|
||||
def clear_gui_calls(self):
|
||||
@@ -409,9 +409,9 @@ class TCDupeGuruWithResults(TestCase):
|
||||
|
||||
def test_only_unicode_is_added_to_ignore_list(self):
|
||||
def FakeIgnore(first,second):
|
||||
if not isinstance(first,unicode):
|
||||
if not isinstance(first,str):
|
||||
self.fail()
|
||||
if not isinstance(second,unicode):
|
||||
if not isinstance(second,str):
|
||||
self.fail()
|
||||
|
||||
app = self.app
|
||||
@@ -423,11 +423,11 @@ class TCDupeGuruWithResults(TestCase):
|
||||
class TCDupeGuru_renameSelected(TestCase):
|
||||
def setUp(self):
|
||||
p = self.tmppath()
|
||||
fp = open(unicode(p + 'foo bar 1'),mode='w')
|
||||
fp = open(str(p + 'foo bar 1'),mode='w')
|
||||
fp.close()
|
||||
fp = open(unicode(p + 'foo bar 2'),mode='w')
|
||||
fp = open(str(p + 'foo bar 2'),mode='w')
|
||||
fp.close()
|
||||
fp = open(unicode(p + 'foo bar 3'),mode='w')
|
||||
fp = open(str(p + 'foo bar 3'),mode='w')
|
||||
fp.close()
|
||||
files = fs.get_files(p)
|
||||
matches = engine.getmatches(files)
|
||||
|
||||
@@ -82,8 +82,8 @@ class TCDirectories(TestCase):
|
||||
|
||||
def test_AddPath_non_latin(self):
|
||||
p = Path(self.tmpdir())
|
||||
to_add = p + u'unicode\u201a'
|
||||
os.mkdir(unicode(to_add))
|
||||
to_add = p + 'unicode\u201a'
|
||||
os.mkdir(str(to_add))
|
||||
d = Directories()
|
||||
try:
|
||||
d.add_path(to_add)
|
||||
@@ -111,7 +111,7 @@ class TCDirectories(TestCase):
|
||||
self.assertEqual(STATE_REFERENCE,d.get_state(p))
|
||||
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
||||
self.assertEqual(1,len(d.states))
|
||||
self.assertEqual(p,d.states.keys()[0])
|
||||
self.assertEqual(p,list(d.states.keys())[0])
|
||||
self.assertEqual(STATE_REFERENCE,d.states[p])
|
||||
|
||||
def test_get_state_with_path_not_there(self):
|
||||
@@ -213,11 +213,11 @@ class TCDirectories(TestCase):
|
||||
|
||||
def test_unicode_save(self):
|
||||
d = Directories()
|
||||
p1 = self.tmppath() + u'hello\xe9'
|
||||
p1 = self.tmppath() + 'hello\xe9'
|
||||
io.mkdir(p1)
|
||||
io.mkdir(p1 + u'foo\xe9')
|
||||
io.mkdir(p1 + 'foo\xe9')
|
||||
d.add_path(p1)
|
||||
d.set_state(p1 + u'foo\xe9', STATE_EXCLUDED)
|
||||
d.set_state(p1 + 'foo\xe9', STATE_EXCLUDED)
|
||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
||||
try:
|
||||
d.save_to_file(tmpxml)
|
||||
|
||||
@@ -62,12 +62,12 @@ class TCgetwords(TestCase):
|
||||
|
||||
def test_splitter_chars(self):
|
||||
self.assertEqual(
|
||||
[chr(i) for i in xrange(ord('a'),ord('z')+1)],
|
||||
[chr(i) for i in range(ord('a'),ord('z')+1)],
|
||||
getwords("a-b_c&d+e(f)g;h\\i[j]k{l}m:n.o,p<q>r/s?t~u!v@w#x$y*z")
|
||||
)
|
||||
|
||||
def test_joiner_chars(self):
|
||||
self.assertEqual(["aec"], getwords(u"a'e\u0301c"))
|
||||
self.assertEqual(["aec"], getwords("a'e\u0301c"))
|
||||
|
||||
def test_empty(self):
|
||||
self.assertEqual([], getwords(''))
|
||||
@@ -76,7 +76,7 @@ class TCgetwords(TestCase):
|
||||
self.assertEqual(['foo', 'bar'], getwords('FOO BAR'))
|
||||
|
||||
def test_decompose_unicode(self):
|
||||
self.assertEqual(getwords(u'foo\xe9bar'), ['fooebar'])
|
||||
self.assertEqual(getwords('foo\xe9bar'), ['fooebar'])
|
||||
|
||||
|
||||
class TCgetfields(TestCase):
|
||||
@@ -768,7 +768,7 @@ class TCget_groups(TestCase):
|
||||
self.assert_(o3 in g)
|
||||
|
||||
def test_four_sized_group(self):
|
||||
l = [NamedObject("foobar") for i in xrange(4)]
|
||||
l = [NamedObject("foobar") for i in range(4)]
|
||||
m = getmatches(l)
|
||||
r = get_groups(m)
|
||||
self.assertEqual(1,len(r))
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import cStringIO
|
||||
from lxml import etree
|
||||
import io
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from hsutil.testutil import eq_
|
||||
|
||||
@@ -59,10 +59,10 @@ def test_save_to_xml():
|
||||
il.Ignore('foo','bar')
|
||||
il.Ignore('foo','bleh')
|
||||
il.Ignore('bleh','bar')
|
||||
f = cStringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
il.save_to_xml(f)
|
||||
f.seek(0)
|
||||
doc = etree.parse(f)
|
||||
doc = ET.parse(f)
|
||||
root = doc.getroot()
|
||||
eq_(root.tag, 'ignore_list')
|
||||
eq_(len(root), 2)
|
||||
@@ -76,19 +76,18 @@ def test_SaveThenLoad():
|
||||
il.Ignore('foo', 'bar')
|
||||
il.Ignore('foo', 'bleh')
|
||||
il.Ignore('bleh', 'bar')
|
||||
il.Ignore(u'\u00e9', 'bar')
|
||||
f = cStringIO.StringIO()
|
||||
il.Ignore('\u00e9', 'bar')
|
||||
f = io.BytesIO()
|
||||
il.save_to_xml(f)
|
||||
f.seek(0)
|
||||
f.seek(0)
|
||||
il = IgnoreList()
|
||||
il.load_from_xml(f)
|
||||
eq_(4,len(il))
|
||||
assert il.AreIgnored(u'\u00e9','bar')
|
||||
assert il.AreIgnored('\u00e9','bar')
|
||||
|
||||
def test_LoadXML_with_empty_file_tags():
|
||||
f = cStringIO.StringIO()
|
||||
f.write('<?xml version="1.0" encoding="utf-8"?><ignore_list><file><file/></file></ignore_list>')
|
||||
f = io.BytesIO()
|
||||
f.write(b'<?xml version="1.0" encoding="utf-8"?><ignore_list><file><file/></file></ignore_list>')
|
||||
f.seek(0)
|
||||
il = IgnoreList()
|
||||
il.load_from_xml(f)
|
||||
@@ -130,12 +129,12 @@ def test_filter():
|
||||
|
||||
def test_save_with_non_ascii_items():
|
||||
il = IgnoreList()
|
||||
il.Ignore(u'\xac', u'\xbf')
|
||||
f = cStringIO.StringIO()
|
||||
il.Ignore('\xac', '\xbf')
|
||||
f = io.BytesIO()
|
||||
try:
|
||||
il.save_to_xml(f)
|
||||
except Exception as e:
|
||||
raise AssertionError(unicode(e))
|
||||
raise AssertionError(str(e))
|
||||
|
||||
def test_len():
|
||||
il = IgnoreList()
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import StringIO
|
||||
import io
|
||||
import os.path as op
|
||||
|
||||
from lxml import etree
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from hsutil.path import Path
|
||||
from hsutil.testutil import eq_
|
||||
@@ -25,7 +25,7 @@ class NamedObject(engine_test.NamedObject):
|
||||
path = property(lambda x:Path('basepath') + x.name)
|
||||
is_ref = False
|
||||
|
||||
def __nonzero__(self):
|
||||
def __bool__(self):
|
||||
return False #Make sure that operations are made correctly when the bool value of files is false.
|
||||
|
||||
# Returns a group set that looks like that:
|
||||
@@ -54,21 +54,24 @@ class TCResultsEmpty(TestCase):
|
||||
self.test_stat_line() # make sure that the stats line isn't saying we applied a '[' filter
|
||||
|
||||
def test_stat_line(self):
|
||||
self.assertEqual("0 / 0 (0.00 B / 0.00 B) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 0 (0.00 B / 0.00 B) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_groups(self):
|
||||
self.assertEqual(0,len(self.results.groups))
|
||||
eq_(0,len(self.results.groups))
|
||||
|
||||
def test_get_group_of_duplicate(self):
|
||||
self.assert_(self.results.get_group_of_duplicate('foo') is None)
|
||||
assert self.results.get_group_of_duplicate('foo') is None
|
||||
|
||||
def test_save_to_xml(self):
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
doc = etree.parse(f)
|
||||
doc = ET.parse(f)
|
||||
root = doc.getroot()
|
||||
self.assertEqual('results', root.tag)
|
||||
eq_('results', root.tag)
|
||||
|
||||
def test_is_modified(self):
|
||||
assert not self.results.is_modified
|
||||
|
||||
|
||||
class TCResultsWithSomeGroups(TestCase):
|
||||
@@ -78,57 +81,57 @@ class TCResultsWithSomeGroups(TestCase):
|
||||
self.results.groups = self.groups
|
||||
|
||||
def test_stat_line(self):
|
||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_groups(self):
|
||||
self.assertEqual(2,len(self.results.groups))
|
||||
eq_(2,len(self.results.groups))
|
||||
|
||||
def test_get_group_of_duplicate(self):
|
||||
for o in self.objects:
|
||||
g = self.results.get_group_of_duplicate(o)
|
||||
self.assert_(isinstance(g, engine.Group))
|
||||
self.assert_(o in g)
|
||||
self.assert_(self.results.get_group_of_duplicate(self.groups[0]) is None)
|
||||
assert isinstance(g, engine.Group)
|
||||
assert o in g
|
||||
assert self.results.get_group_of_duplicate(self.groups[0]) is None
|
||||
|
||||
def test_remove_duplicates(self):
|
||||
g1,g2 = self.results.groups
|
||||
self.results.remove_duplicates([g1.dupes[0]])
|
||||
self.assertEqual(2,len(g1))
|
||||
self.assert_(g1 in self.results.groups)
|
||||
eq_(2,len(g1))
|
||||
assert g1 in self.results.groups
|
||||
self.results.remove_duplicates([g1.ref])
|
||||
self.assertEqual(2,len(g1))
|
||||
self.assert_(g1 in self.results.groups)
|
||||
eq_(2,len(g1))
|
||||
assert g1 in self.results.groups
|
||||
self.results.remove_duplicates([g1.dupes[0]])
|
||||
self.assertEqual(0,len(g1))
|
||||
self.assert_(g1 not in self.results.groups)
|
||||
eq_(0,len(g1))
|
||||
assert g1 not in self.results.groups
|
||||
self.results.remove_duplicates([g2.dupes[0]])
|
||||
self.assertEqual(0,len(g2))
|
||||
self.assert_(g2 not in self.results.groups)
|
||||
self.assertEqual(0,len(self.results.groups))
|
||||
eq_(0,len(g2))
|
||||
assert g2 not in self.results.groups
|
||||
eq_(0,len(self.results.groups))
|
||||
|
||||
def test_remove_duplicates_with_ref_files(self):
|
||||
g1,g2 = self.results.groups
|
||||
self.objects[0].is_ref = True
|
||||
self.objects[1].is_ref = True
|
||||
self.results.remove_duplicates([self.objects[2]])
|
||||
self.assertEqual(0,len(g1))
|
||||
self.assert_(g1 not in self.results.groups)
|
||||
eq_(0,len(g1))
|
||||
assert g1 not in self.results.groups
|
||||
|
||||
def test_make_ref(self):
|
||||
g = self.results.groups[0]
|
||||
d = g.dupes[0]
|
||||
self.results.make_ref(d)
|
||||
self.assert_(d is g.ref)
|
||||
assert d is g.ref
|
||||
|
||||
def test_sort_groups(self):
|
||||
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
||||
g1,g2 = self.groups
|
||||
self.results.sort_groups(2) #2 is the key for size
|
||||
self.assert_(self.results.groups[0] is g2)
|
||||
self.assert_(self.results.groups[1] is g1)
|
||||
assert self.results.groups[0] is g2
|
||||
assert self.results.groups[1] is g1
|
||||
self.results.sort_groups(2,False)
|
||||
self.assert_(self.results.groups[0] is g1)
|
||||
self.assert_(self.results.groups[1] is g2)
|
||||
assert self.results.groups[0] is g1
|
||||
assert self.results.groups[1] is g2
|
||||
|
||||
def test_set_groups_when_sorted(self):
|
||||
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
||||
@@ -137,24 +140,24 @@ class TCResultsWithSomeGroups(TestCase):
|
||||
g1,g2 = groups
|
||||
g1.switch_ref(objects[1])
|
||||
self.results.groups = groups
|
||||
self.assert_(self.results.groups[0] is g2)
|
||||
self.assert_(self.results.groups[1] is g1)
|
||||
assert self.results.groups[0] is g2
|
||||
assert self.results.groups[1] is g1
|
||||
|
||||
def test_get_dupe_list(self):
|
||||
self.assertEqual([self.objects[1],self.objects[2],self.objects[4]],self.results.dupes)
|
||||
eq_([self.objects[1],self.objects[2],self.objects[4]],self.results.dupes)
|
||||
|
||||
def test_dupe_list_is_cached(self):
|
||||
self.assert_(self.results.dupes is self.results.dupes)
|
||||
assert self.results.dupes is self.results.dupes
|
||||
|
||||
def test_dupe_list_cache_is_invalidated_when_needed(self):
|
||||
o1,o2,o3,o4,o5 = self.objects
|
||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
||||
eq_([o2,o3,o5],self.results.dupes)
|
||||
self.results.make_ref(o2)
|
||||
self.assertEqual([o1,o3,o5],self.results.dupes)
|
||||
eq_([o1,o3,o5],self.results.dupes)
|
||||
objects,matches,groups = GetTestGroups()
|
||||
o1,o2,o3,o4,o5 = objects
|
||||
self.results.groups = groups
|
||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
||||
eq_([o2,o3,o5],self.results.dupes)
|
||||
|
||||
def test_dupe_list_sort(self):
|
||||
o1,o2,o3,o4,o5 = self.objects
|
||||
@@ -164,9 +167,9 @@ class TCResultsWithSomeGroups(TestCase):
|
||||
o4.size = 2
|
||||
o5.size = 1
|
||||
self.results.sort_dupes(2)
|
||||
self.assertEqual([o5,o3,o2],self.results.dupes)
|
||||
eq_([o5,o3,o2],self.results.dupes)
|
||||
self.results.sort_dupes(2,False)
|
||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
||||
eq_([o2,o3,o5],self.results.dupes)
|
||||
|
||||
def test_dupe_list_remember_sort(self):
|
||||
o1,o2,o3,o4,o5 = self.objects
|
||||
@@ -177,7 +180,7 @@ class TCResultsWithSomeGroups(TestCase):
|
||||
o5.size = 1
|
||||
self.results.sort_dupes(2)
|
||||
self.results.make_ref(o2)
|
||||
self.assertEqual([o5,o3,o1],self.results.dupes)
|
||||
eq_([o5,o3,o1],self.results.dupes)
|
||||
|
||||
def test_dupe_list_sort_delta_values(self):
|
||||
o1,o2,o3,o4,o5 = self.objects
|
||||
@@ -187,19 +190,69 @@ class TCResultsWithSomeGroups(TestCase):
|
||||
o4.size = 20
|
||||
o5.size = 1 #-19
|
||||
self.results.sort_dupes(2,delta=True)
|
||||
self.assertEqual([o5,o2,o3],self.results.dupes)
|
||||
eq_([o5,o2,o3],self.results.dupes)
|
||||
|
||||
def test_sort_empty_list(self):
|
||||
#There was an infinite loop when sorting an empty list.
|
||||
r = Results(data)
|
||||
r.sort_dupes(0)
|
||||
self.assertEqual([],r.dupes)
|
||||
eq_([],r.dupes)
|
||||
|
||||
def test_dupe_list_update_on_remove_duplicates(self):
|
||||
o1,o2,o3,o4,o5 = self.objects
|
||||
self.assertEqual(3,len(self.results.dupes))
|
||||
eq_(3,len(self.results.dupes))
|
||||
self.results.remove_duplicates([o2])
|
||||
self.assertEqual(2,len(self.results.dupes))
|
||||
eq_(2,len(self.results.dupes))
|
||||
|
||||
def test_is_modified(self):
|
||||
# Changing the groups sets the modified flag
|
||||
assert self.results.is_modified
|
||||
|
||||
def test_is_modified_after_save_and_load(self):
|
||||
# Saving/Loading a file sets the modified flag back to False
|
||||
def get_file(path):
|
||||
return [f for f in self.objects if str(f.path) == path][0]
|
||||
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
assert not self.results.is_modified
|
||||
self.results.groups = self.groups # sets the flag back
|
||||
f.seek(0)
|
||||
self.results.load_from_xml(f, get_file)
|
||||
assert not self.results.is_modified
|
||||
|
||||
|
||||
class ResultsWithSavedResults(TestCase):
|
||||
def setUp(self):
|
||||
self.results = Results(data)
|
||||
self.objects,self.matches,self.groups = GetTestGroups()
|
||||
self.results.groups = self.groups
|
||||
self.f = io.BytesIO()
|
||||
self.results.save_to_xml(self.f)
|
||||
self.f.seek(0)
|
||||
|
||||
def test_is_modified(self):
|
||||
# Saving a file sets the modified flag back to False
|
||||
assert not self.results.is_modified
|
||||
|
||||
def test_is_modified_after_load(self):
|
||||
# Loading a file sets the modified flag back to False
|
||||
def get_file(path):
|
||||
return [f for f in self.objects if str(f.path) == path][0]
|
||||
|
||||
self.results.groups = self.groups # sets the flag back
|
||||
self.results.load_from_xml(self.f, get_file)
|
||||
assert not self.results.is_modified
|
||||
|
||||
def test_is_modified_after_remove(self):
|
||||
# Removing dupes sets the modified flag
|
||||
self.results.remove_duplicates([self.results.groups[0].dupes[0]])
|
||||
assert self.results.is_modified
|
||||
|
||||
def test_is_modified_after_make_ref(self):
|
||||
# Making a dupe ref sets the modified flag
|
||||
self.results.make_ref(self.results.groups[0].dupes[0])
|
||||
assert self.results.is_modified
|
||||
|
||||
|
||||
class TCResultsMarkings(TestCase):
|
||||
@@ -209,27 +262,27 @@ class TCResultsMarkings(TestCase):
|
||||
self.results.groups = self.groups
|
||||
|
||||
def test_stat_line(self):
|
||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.mark(self.objects[1])
|
||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.mark_invert()
|
||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.mark_invert()
|
||||
self.results.unmark(self.objects[1])
|
||||
self.results.mark(self.objects[2])
|
||||
self.results.mark(self.objects[4])
|
||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.mark(self.objects[0]) #this is a ref, it can't be counted
|
||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.groups = self.groups
|
||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_with_ref_duplicate(self):
|
||||
self.objects[1].is_ref = True
|
||||
self.results.groups = self.groups
|
||||
self.assert_(not self.results.mark(self.objects[1]))
|
||||
assert not self.results.mark(self.objects[1])
|
||||
self.results.mark(self.objects[2])
|
||||
self.assertEqual("1 / 2 (1.00 B / 2.00 B) duplicates marked.",self.results.stat_line)
|
||||
eq_("1 / 2 (1.00 B / 2.00 B) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_perform_on_marked(self):
|
||||
def log_object(o):
|
||||
@@ -239,17 +292,17 @@ class TCResultsMarkings(TestCase):
|
||||
log = []
|
||||
self.results.mark_all()
|
||||
self.results.perform_on_marked(log_object,False)
|
||||
self.assert_(self.objects[1] in log)
|
||||
self.assert_(self.objects[2] in log)
|
||||
self.assert_(self.objects[4] in log)
|
||||
self.assertEqual(3,len(log))
|
||||
assert self.objects[1] in log
|
||||
assert self.objects[2] in log
|
||||
assert self.objects[4] in log
|
||||
eq_(3,len(log))
|
||||
log = []
|
||||
self.results.mark_none()
|
||||
self.results.mark(self.objects[4])
|
||||
self.results.perform_on_marked(log_object,True)
|
||||
self.assertEqual(1,len(log))
|
||||
self.assert_(self.objects[4] in log)
|
||||
self.assertEqual(1,len(self.results.groups))
|
||||
eq_(1,len(log))
|
||||
assert self.objects[4] in log
|
||||
eq_(1,len(self.results.groups))
|
||||
|
||||
def test_perform_on_marked_with_problems(self):
|
||||
def log_object(o):
|
||||
@@ -282,61 +335,61 @@ class TCResultsMarkings(TestCase):
|
||||
self.objects[1].is_ref = True
|
||||
self.results.mark_all()
|
||||
self.results.perform_on_marked(log_object,True)
|
||||
self.assert_(self.objects[1] not in log)
|
||||
self.assert_(self.objects[2] in log)
|
||||
self.assert_(self.objects[4] in log)
|
||||
self.assertEqual(2,len(log))
|
||||
self.assertEqual(0,len(self.results.groups))
|
||||
assert self.objects[1] not in log
|
||||
assert self.objects[2] in log
|
||||
assert self.objects[4] in log
|
||||
eq_(2,len(log))
|
||||
eq_(0,len(self.results.groups))
|
||||
|
||||
def test_perform_on_marked_remove_objects_only_at_the_end(self):
|
||||
def check_groups(o):
|
||||
self.assertEqual(3,len(g1))
|
||||
self.assertEqual(2,len(g2))
|
||||
eq_(3,len(g1))
|
||||
eq_(2,len(g2))
|
||||
return True
|
||||
|
||||
g1,g2 = self.results.groups
|
||||
self.results.mark_all()
|
||||
self.results.perform_on_marked(check_groups,True)
|
||||
self.assertEqual(0,len(g1))
|
||||
self.assertEqual(0,len(g2))
|
||||
self.assertEqual(0,len(self.results.groups))
|
||||
eq_(0,len(g1))
|
||||
eq_(0,len(g2))
|
||||
eq_(0,len(self.results.groups))
|
||||
|
||||
def test_remove_duplicates(self):
|
||||
g1 = self.results.groups[0]
|
||||
g2 = self.results.groups[1]
|
||||
self.results.mark(g1.dupes[0])
|
||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.remove_duplicates([g1.dupes[1]])
|
||||
self.assertEqual("1 / 2 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("1 / 2 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.remove_duplicates([g1.dupes[0]])
|
||||
self.assertEqual("0 / 1 (0.00 B / 1.00 B) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 1 (0.00 B / 1.00 B) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_make_ref(self):
|
||||
g = self.results.groups[0]
|
||||
d = g.dupes[0]
|
||||
self.results.mark(d)
|
||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
eq_("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
||||
self.results.make_ref(d)
|
||||
self.assertEqual("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
||||
self.results.make_ref(d)
|
||||
self.assertEqual("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
||||
eq_("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
||||
|
||||
def test_SaveXML(self):
|
||||
self.results.mark(self.objects[1])
|
||||
self.results.mark_invert()
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
doc = etree.parse(f)
|
||||
doc = ET.parse(f)
|
||||
root = doc.getroot()
|
||||
g1, g2 = root.iterchildren('group')
|
||||
d1, d2, d3 = g1.iterchildren('file')
|
||||
self.assertEqual('n', d1.get('marked'))
|
||||
self.assertEqual('n', d2.get('marked'))
|
||||
self.assertEqual('y', d3.get('marked'))
|
||||
d1, d2 = g2.iterchildren('file')
|
||||
self.assertEqual('n', d1.get('marked'))
|
||||
self.assertEqual('y', d2.get('marked'))
|
||||
g1, g2 = root.getiterator('group')
|
||||
d1, d2, d3 = g1.getiterator('file')
|
||||
eq_('n', d1.get('marked'))
|
||||
eq_('n', d2.get('marked'))
|
||||
eq_('y', d3.get('marked'))
|
||||
d1, d2 = g2.getiterator('file')
|
||||
eq_('n', d1.get('marked'))
|
||||
eq_('y', d2.get('marked'))
|
||||
|
||||
def test_LoadXML(self):
|
||||
def get_file(path):
|
||||
@@ -345,16 +398,16 @@ class TCResultsMarkings(TestCase):
|
||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
||||
self.results.mark(self.objects[1])
|
||||
self.results.mark_invert()
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f,get_file)
|
||||
self.assert_(not r.is_marked(self.objects[0]))
|
||||
self.assert_(not r.is_marked(self.objects[1]))
|
||||
self.assert_(r.is_marked(self.objects[2]))
|
||||
self.assert_(not r.is_marked(self.objects[3]))
|
||||
self.assert_(r.is_marked(self.objects[4]))
|
||||
assert not r.is_marked(self.objects[0])
|
||||
assert not r.is_marked(self.objects[1])
|
||||
assert r.is_marked(self.objects[2])
|
||||
assert not r.is_marked(self.objects[3])
|
||||
assert r.is_marked(self.objects[4])
|
||||
|
||||
|
||||
class TCResultsXML(TestCase):
|
||||
@@ -369,38 +422,38 @@ class TCResultsXML(TestCase):
|
||||
def test_save_to_xml(self):
|
||||
self.objects[0].is_ref = True
|
||||
self.objects[0].words = [['foo','bar']]
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
doc = etree.parse(f)
|
||||
doc = ET.parse(f)
|
||||
root = doc.getroot()
|
||||
self.assertEqual('results', root.tag)
|
||||
self.assertEqual(2, len(root))
|
||||
self.assertEqual(2, len([c for c in root if c.tag == 'group']))
|
||||
eq_('results', root.tag)
|
||||
eq_(2, len(root))
|
||||
eq_(2, len([c for c in root if c.tag == 'group']))
|
||||
g1, g2 = root
|
||||
self.assertEqual(6,len(g1))
|
||||
self.assertEqual(3,len([c for c in g1 if c.tag == 'file']))
|
||||
self.assertEqual(3,len([c for c in g1 if c.tag == 'match']))
|
||||
eq_(6,len(g1))
|
||||
eq_(3,len([c for c in g1 if c.tag == 'file']))
|
||||
eq_(3,len([c for c in g1 if c.tag == 'match']))
|
||||
d1, d2, d3 = [c for c in g1 if c.tag == 'file']
|
||||
self.assertEqual(op.join('basepath','foo bar'),d1.get('path'))
|
||||
self.assertEqual(op.join('basepath','bar bleh'),d2.get('path'))
|
||||
self.assertEqual(op.join('basepath','foo bleh'),d3.get('path'))
|
||||
self.assertEqual('y',d1.get('is_ref'))
|
||||
self.assertEqual('n',d2.get('is_ref'))
|
||||
self.assertEqual('n',d3.get('is_ref'))
|
||||
self.assertEqual('foo,bar',d1.get('words'))
|
||||
self.assertEqual('bar,bleh',d2.get('words'))
|
||||
self.assertEqual('foo,bleh',d3.get('words'))
|
||||
self.assertEqual(3,len(g2))
|
||||
self.assertEqual(2,len([c for c in g2 if c.tag == 'file']))
|
||||
self.assertEqual(1,len([c for c in g2 if c.tag == 'match']))
|
||||
eq_(op.join('basepath','foo bar'),d1.get('path'))
|
||||
eq_(op.join('basepath','bar bleh'),d2.get('path'))
|
||||
eq_(op.join('basepath','foo bleh'),d3.get('path'))
|
||||
eq_('y',d1.get('is_ref'))
|
||||
eq_('n',d2.get('is_ref'))
|
||||
eq_('n',d3.get('is_ref'))
|
||||
eq_('foo,bar',d1.get('words'))
|
||||
eq_('bar,bleh',d2.get('words'))
|
||||
eq_('foo,bleh',d3.get('words'))
|
||||
eq_(3,len(g2))
|
||||
eq_(2,len([c for c in g2 if c.tag == 'file']))
|
||||
eq_(1,len([c for c in g2 if c.tag == 'match']))
|
||||
d1, d2 = [c for c in g2 if c.tag == 'file']
|
||||
self.assertEqual(op.join('basepath','ibabtu'),d1.get('path'))
|
||||
self.assertEqual(op.join('basepath','ibabtu'),d2.get('path'))
|
||||
self.assertEqual('n',d1.get('is_ref'))
|
||||
self.assertEqual('n',d2.get('is_ref'))
|
||||
self.assertEqual('ibabtu',d1.get('words'))
|
||||
self.assertEqual('ibabtu',d2.get('words'))
|
||||
eq_(op.join('basepath','ibabtu'),d1.get('path'))
|
||||
eq_(op.join('basepath','ibabtu'),d2.get('path'))
|
||||
eq_('n',d1.get('is_ref'))
|
||||
eq_('n',d2.get('is_ref'))
|
||||
eq_('ibabtu',d1.get('words'))
|
||||
eq_('ibabtu',d2.get('words'))
|
||||
|
||||
def test_LoadXML(self):
|
||||
def get_file(path):
|
||||
@@ -408,30 +461,30 @@ class TCResultsXML(TestCase):
|
||||
|
||||
self.objects[0].is_ref = True
|
||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f,get_file)
|
||||
self.assertEqual(2,len(r.groups))
|
||||
eq_(2,len(r.groups))
|
||||
g1,g2 = r.groups
|
||||
self.assertEqual(3,len(g1))
|
||||
self.assert_(g1[0].is_ref)
|
||||
self.assert_(not g1[1].is_ref)
|
||||
self.assert_(not g1[2].is_ref)
|
||||
self.assert_(g1[0] is self.objects[0])
|
||||
self.assert_(g1[1] is self.objects[1])
|
||||
self.assert_(g1[2] is self.objects[2])
|
||||
self.assertEqual(['foo','bar'],g1[0].words)
|
||||
self.assertEqual(['bar','bleh'],g1[1].words)
|
||||
self.assertEqual(['foo','bleh'],g1[2].words)
|
||||
self.assertEqual(2,len(g2))
|
||||
self.assert_(not g2[0].is_ref)
|
||||
self.assert_(not g2[1].is_ref)
|
||||
self.assert_(g2[0] is self.objects[3])
|
||||
self.assert_(g2[1] is self.objects[4])
|
||||
self.assertEqual(['ibabtu'],g2[0].words)
|
||||
self.assertEqual(['ibabtu'],g2[1].words)
|
||||
eq_(3,len(g1))
|
||||
assert g1[0].is_ref
|
||||
assert not g1[1].is_ref
|
||||
assert not g1[2].is_ref
|
||||
assert g1[0] is self.objects[0]
|
||||
assert g1[1] is self.objects[1]
|
||||
assert g1[2] is self.objects[2]
|
||||
eq_(['foo','bar'],g1[0].words)
|
||||
eq_(['bar','bleh'],g1[1].words)
|
||||
eq_(['foo','bleh'],g1[2].words)
|
||||
eq_(2,len(g2))
|
||||
assert not g2[0].is_ref
|
||||
assert not g2[1].is_ref
|
||||
assert g2[0] is self.objects[3]
|
||||
assert g2[1] is self.objects[4]
|
||||
eq_(['ibabtu'],g2[0].words)
|
||||
eq_(['ibabtu'],g2[1].words)
|
||||
|
||||
def test_LoadXML_with_filename(self):
|
||||
def get_file(path):
|
||||
@@ -442,7 +495,7 @@ class TCResultsXML(TestCase):
|
||||
self.results.save_to_xml(filename)
|
||||
r = Results(data)
|
||||
r.load_from_xml(filename,get_file)
|
||||
self.assertEqual(2,len(r.groups))
|
||||
eq_(2,len(r.groups))
|
||||
|
||||
def test_LoadXML_with_some_files_that_dont_exist_anymore(self):
|
||||
def get_file(path):
|
||||
@@ -451,84 +504,84 @@ class TCResultsXML(TestCase):
|
||||
return [f for f in self.objects if str(f.path) == path][0]
|
||||
|
||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f,get_file)
|
||||
self.assertEqual(1,len(r.groups))
|
||||
self.assertEqual(3,len(r.groups[0]))
|
||||
eq_(1,len(r.groups))
|
||||
eq_(3,len(r.groups[0]))
|
||||
|
||||
def test_LoadXML_missing_attributes_and_bogus_elements(self):
|
||||
def get_file(path):
|
||||
return [f for f in self.objects if str(f.path) == path][0]
|
||||
|
||||
root = etree.Element('foobar') #The root element shouldn't matter, really.
|
||||
group_node = etree.SubElement(root, 'group')
|
||||
dupe_node = etree.SubElement(group_node, 'file') #Perfectly correct file
|
||||
root = ET.Element('foobar') #The root element shouldn't matter, really.
|
||||
group_node = ET.SubElement(root, 'group')
|
||||
dupe_node = ET.SubElement(group_node, 'file') #Perfectly correct file
|
||||
dupe_node.set('path', op.join('basepath','foo bar'))
|
||||
dupe_node.set('is_ref', 'y')
|
||||
dupe_node.set('words', 'foo,bar')
|
||||
dupe_node = etree.SubElement(group_node, 'file') #is_ref missing, default to 'n'
|
||||
dupe_node = ET.SubElement(group_node, 'file') #is_ref missing, default to 'n'
|
||||
dupe_node.set('path',op.join('basepath','foo bleh'))
|
||||
dupe_node.set('words','foo,bleh')
|
||||
dupe_node = etree.SubElement(group_node, 'file') #words are missing, valid.
|
||||
dupe_node = ET.SubElement(group_node, 'file') #words are missing, valid.
|
||||
dupe_node.set('path',op.join('basepath','bar bleh'))
|
||||
dupe_node = etree.SubElement(group_node, 'file') #path is missing, invalid.
|
||||
dupe_node = ET.SubElement(group_node, 'file') #path is missing, invalid.
|
||||
dupe_node.set('words','foo,bleh')
|
||||
dupe_node = etree.SubElement(group_node, 'foobar') #Invalid element name
|
||||
dupe_node = ET.SubElement(group_node, 'foobar') #Invalid element name
|
||||
dupe_node.set('path',op.join('basepath','bar bleh'))
|
||||
dupe_node.set('is_ref','y')
|
||||
dupe_node.set('words','bar,bleh')
|
||||
match_node = etree.SubElement(group_node, 'match') # match pointing to a bad index
|
||||
match_node = ET.SubElement(group_node, 'match') # match pointing to a bad index
|
||||
match_node.set('first', '42')
|
||||
match_node.set('second', '45')
|
||||
match_node = etree.SubElement(group_node, 'match') # match with missing attrs
|
||||
match_node = etree.SubElement(group_node, 'match') # match with non-int values
|
||||
match_node = ET.SubElement(group_node, 'match') # match with missing attrs
|
||||
match_node = ET.SubElement(group_node, 'match') # match with non-int values
|
||||
match_node.set('first', 'foo')
|
||||
match_node.set('second', 'bar')
|
||||
match_node.set('percentage', 'baz')
|
||||
group_node = etree.SubElement(root, 'foobar') #invalid group
|
||||
group_node = etree.SubElement(root, 'group') #empty group
|
||||
f = StringIO.StringIO()
|
||||
tree = etree.ElementTree(root)
|
||||
group_node = ET.SubElement(root, 'foobar') #invalid group
|
||||
group_node = ET.SubElement(root, 'group') #empty group
|
||||
f = io.BytesIO()
|
||||
tree = ET.ElementTree(root)
|
||||
tree.write(f, encoding='utf-8')
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f, get_file)
|
||||
self.assertEqual(1,len(r.groups))
|
||||
self.assertEqual(3,len(r.groups[0]))
|
||||
eq_(1,len(r.groups))
|
||||
eq_(3,len(r.groups[0]))
|
||||
|
||||
def test_xml_non_ascii(self):
|
||||
def get_file(path):
|
||||
if path == op.join('basepath',u'\xe9foo bar'):
|
||||
if path == op.join('basepath','\xe9foo bar'):
|
||||
return objects[0]
|
||||
if path == op.join('basepath',u'bar bleh'):
|
||||
if path == op.join('basepath','bar bleh'):
|
||||
return objects[1]
|
||||
|
||||
objects = [NamedObject(u"\xe9foo bar",True),NamedObject("bar bleh",True)]
|
||||
objects = [NamedObject("\xe9foo bar",True),NamedObject("bar bleh",True)]
|
||||
matches = engine.getmatches(objects) #we should have 5 matches
|
||||
groups = engine.get_groups(matches) #We should have 2 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
|
||||
results = Results(data)
|
||||
results.groups = groups
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f,get_file)
|
||||
g = r.groups[0]
|
||||
self.assertEqual(u"\xe9foo bar",g[0].name)
|
||||
self.assertEqual(['efoo','bar'],g[0].words)
|
||||
eq_("\xe9foo bar",g[0].name)
|
||||
eq_(['efoo','bar'],g[0].words)
|
||||
|
||||
def test_load_invalid_xml(self):
|
||||
f = StringIO.StringIO()
|
||||
f.write('<this is invalid')
|
||||
f = io.BytesIO()
|
||||
f.write(b'<this is invalid')
|
||||
f.seek(0)
|
||||
r = Results(data)
|
||||
r.load_from_xml(f,None)
|
||||
self.assertEqual(0,len(r.groups))
|
||||
eq_(0,len(r.groups))
|
||||
|
||||
def test_load_non_existant_xml(self):
|
||||
r = Results(data)
|
||||
@@ -536,7 +589,7 @@ class TCResultsXML(TestCase):
|
||||
r.load_from_xml('does_not_exist.xml', None)
|
||||
except IOError:
|
||||
self.fail()
|
||||
self.assertEqual(0,len(r.groups))
|
||||
eq_(0,len(r.groups))
|
||||
|
||||
def test_remember_match_percentage(self):
|
||||
group = self.groups[0]
|
||||
@@ -546,7 +599,7 @@ class TCResultsXML(TestCase):
|
||||
fake_matches.add(engine.Match(d1, d3, 43))
|
||||
fake_matches.add(engine.Match(d2, d3, 46))
|
||||
group.matches = fake_matches
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
results = self.results
|
||||
results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
@@ -555,16 +608,16 @@ class TCResultsXML(TestCase):
|
||||
group = results.groups[0]
|
||||
d1, d2, d3 = group
|
||||
match = group.get_match_of(d2) #d1 - d2
|
||||
self.assertEqual(42, match[2])
|
||||
eq_(42, match[2])
|
||||
match = group.get_match_of(d3) #d1 - d3
|
||||
self.assertEqual(43, match[2])
|
||||
eq_(43, match[2])
|
||||
group.switch_ref(d2)
|
||||
match = group.get_match_of(d3) #d2 - d3
|
||||
self.assertEqual(46, match[2])
|
||||
eq_(46, match[2])
|
||||
|
||||
def test_save_and_load(self):
|
||||
# previously, when reloading matches, they wouldn't be reloaded as namedtuples
|
||||
f = StringIO.StringIO()
|
||||
f = io.BytesIO()
|
||||
self.results.save_to_xml(f)
|
||||
f.seek(0)
|
||||
self.results.load_from_xml(f, self.get_file)
|
||||
@@ -572,13 +625,13 @@ class TCResultsXML(TestCase):
|
||||
|
||||
def test_apply_filter_works_on_paths(self):
|
||||
# apply_filter() searches on the whole path, not just on the filename.
|
||||
self.results.apply_filter(u'basepath')
|
||||
self.results.apply_filter('basepath')
|
||||
eq_(len(self.results.groups), 2)
|
||||
|
||||
def test_save_xml_with_invalid_characters(self):
|
||||
# Don't crash when saving files that have invalid xml characters in their path
|
||||
self.objects[0].name = u'foo\x19'
|
||||
self.results.save_to_xml(StringIO.StringIO()) # don't crash
|
||||
self.objects[0].name = 'foo\x19'
|
||||
self.results.save_to_xml(io.BytesIO()) # don't crash
|
||||
|
||||
|
||||
class TCResultsFilter(TestCase):
|
||||
@@ -589,47 +642,47 @@ class TCResultsFilter(TestCase):
|
||||
self.results.apply_filter(r'foo')
|
||||
|
||||
def test_groups(self):
|
||||
self.assertEqual(1, len(self.results.groups))
|
||||
self.assert_(self.results.groups[0] is self.groups[0])
|
||||
eq_(1, len(self.results.groups))
|
||||
assert self.results.groups[0] is self.groups[0]
|
||||
|
||||
def test_dupes(self):
|
||||
# There are 2 objects matching. The first one is ref. Only the 3rd one is supposed to be in dupes.
|
||||
self.assertEqual(1, len(self.results.dupes))
|
||||
self.assert_(self.results.dupes[0] is self.objects[2])
|
||||
eq_(1, len(self.results.dupes))
|
||||
assert self.results.dupes[0] is self.objects[2]
|
||||
|
||||
def test_cancel_filter(self):
|
||||
self.results.apply_filter(None)
|
||||
self.assertEqual(3, len(self.results.dupes))
|
||||
self.assertEqual(2, len(self.results.groups))
|
||||
eq_(3, len(self.results.dupes))
|
||||
eq_(2, len(self.results.groups))
|
||||
|
||||
def test_dupes_reconstructed_filtered(self):
|
||||
# make_ref resets self.__dupes to None. When it's reconstructed, we want it filtered
|
||||
dupe = self.results.dupes[0] #3rd object
|
||||
self.results.make_ref(dupe)
|
||||
self.assertEqual(1, len(self.results.dupes))
|
||||
self.assert_(self.results.dupes[0] is self.objects[0])
|
||||
eq_(1, len(self.results.dupes))
|
||||
assert self.results.dupes[0] is self.objects[0]
|
||||
|
||||
def test_include_ref_dupes_in_filter(self):
|
||||
# When only the ref of a group match the filter, include it in the group
|
||||
self.results.apply_filter(None)
|
||||
self.results.apply_filter(r'foo bar')
|
||||
self.assertEqual(1, len(self.results.groups))
|
||||
self.assertEqual(0, len(self.results.dupes))
|
||||
eq_(1, len(self.results.groups))
|
||||
eq_(0, len(self.results.dupes))
|
||||
|
||||
def test_filters_build_on_one_another(self):
|
||||
self.results.apply_filter(r'bar')
|
||||
self.assertEqual(1, len(self.results.groups))
|
||||
self.assertEqual(0, len(self.results.dupes))
|
||||
eq_(1, len(self.results.groups))
|
||||
eq_(0, len(self.results.dupes))
|
||||
|
||||
def test_stat_line(self):
|
||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked. filter: foo'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
self.results.apply_filter(r'bar')
|
||||
expected = '0 / 0 (0.00 B / 0.00 B) duplicates marked. filter: foo --> bar'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
self.results.apply_filter(None)
|
||||
expected = '0 / 3 (0.00 B / 1.01 KB) duplicates marked.'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
|
||||
def test_mark_count_is_filtered_as_well(self):
|
||||
self.results.apply_filter(None)
|
||||
@@ -638,7 +691,7 @@ class TCResultsFilter(TestCase):
|
||||
self.results.mark(dupe)
|
||||
self.results.apply_filter(r'foo')
|
||||
expected = '1 / 1 (1.00 B / 1.00 B) duplicates marked. filter: foo'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
|
||||
def test_sort_groups(self):
|
||||
self.results.apply_filter(None)
|
||||
@@ -646,22 +699,22 @@ class TCResultsFilter(TestCase):
|
||||
g1,g2 = self.groups
|
||||
self.results.apply_filter('a') # Matches both group
|
||||
self.results.sort_groups(2) #2 is the key for size
|
||||
self.assert_(self.results.groups[0] is g2)
|
||||
self.assert_(self.results.groups[1] is g1)
|
||||
assert self.results.groups[0] is g2
|
||||
assert self.results.groups[1] is g1
|
||||
self.results.apply_filter(None)
|
||||
self.assert_(self.results.groups[0] is g2)
|
||||
self.assert_(self.results.groups[1] is g1)
|
||||
assert self.results.groups[0] is g2
|
||||
assert self.results.groups[1] is g1
|
||||
self.results.sort_groups(2, False)
|
||||
self.results.apply_filter('a')
|
||||
self.assert_(self.results.groups[1] is g2)
|
||||
self.assert_(self.results.groups[0] is g1)
|
||||
assert self.results.groups[1] is g2
|
||||
assert self.results.groups[0] is g1
|
||||
|
||||
def test_set_group(self):
|
||||
#We want the new group to be filtered
|
||||
self.objects, self.matches, self.groups = GetTestGroups()
|
||||
self.results.groups = self.groups
|
||||
self.assertEqual(1, len(self.results.groups))
|
||||
self.assert_(self.results.groups[0] is self.groups[0])
|
||||
eq_(1, len(self.results.groups))
|
||||
assert self.results.groups[0] is self.groups[0]
|
||||
|
||||
def test_load_cancels_filter(self):
|
||||
def get_file(path):
|
||||
@@ -673,23 +726,23 @@ class TCResultsFilter(TestCase):
|
||||
r = Results(data)
|
||||
r.apply_filter('foo')
|
||||
r.load_from_xml(filename,get_file)
|
||||
self.assertEqual(2,len(r.groups))
|
||||
eq_(2,len(r.groups))
|
||||
|
||||
def test_remove_dupe(self):
|
||||
self.results.remove_duplicates([self.results.dupes[0]])
|
||||
self.results.apply_filter(None)
|
||||
self.assertEqual(2,len(self.results.groups))
|
||||
self.assertEqual(2,len(self.results.dupes))
|
||||
eq_(2,len(self.results.groups))
|
||||
eq_(2,len(self.results.dupes))
|
||||
self.results.apply_filter('ibabtu')
|
||||
self.results.remove_duplicates([self.results.dupes[0]])
|
||||
self.results.apply_filter(None)
|
||||
self.assertEqual(1,len(self.results.groups))
|
||||
self.assertEqual(1,len(self.results.dupes))
|
||||
eq_(1,len(self.results.groups))
|
||||
eq_(1,len(self.results.dupes))
|
||||
|
||||
def test_filter_is_case_insensitive(self):
|
||||
self.results.apply_filter(None)
|
||||
self.results.apply_filter('FOO')
|
||||
self.assertEqual(1, len(self.results.dupes))
|
||||
eq_(1, len(self.results.dupes))
|
||||
|
||||
def test_make_ref_on_filtered_out_doesnt_mess_stats(self):
|
||||
# When filtered, a group containing filtered out dupes will display them as being reference.
|
||||
@@ -700,10 +753,10 @@ class TCResultsFilter(TestCase):
|
||||
self.results.make_ref(bar_bleh)
|
||||
# Now the stats should display *2* markable dupes (instead of 1)
|
||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked. filter: foo'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
self.results.apply_filter(None) # Now let's make sure our unfiltered results aren't fucked up
|
||||
expected = '0 / 3 (0.00 B / 3.00 B) duplicates marked.'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
|
||||
|
||||
class TCResultsRefFile(TestCase):
|
||||
@@ -716,15 +769,15 @@ class TCResultsRefFile(TestCase):
|
||||
|
||||
def test_stat_line(self):
|
||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
|
||||
def test_make_ref(self):
|
||||
d = self.results.groups[0].dupes[1] #non-ref
|
||||
r = self.results.groups[0].ref
|
||||
self.results.make_ref(d)
|
||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked.'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
self.results.make_ref(r)
|
||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
||||
self.assertEqual(expected, self.results.stat_line)
|
||||
eq_(expected, self.results.stat_line)
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ class NamedObject(object):
|
||||
self.path = Path('')
|
||||
self.words = getwords(name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<NamedObject %r>' % self.name
|
||||
|
||||
|
||||
no = NamedObject
|
||||
|
||||
@@ -43,7 +46,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
def test_default_settings(self):
|
||||
s = Scanner()
|
||||
eq_(s.min_match_percentage, 80)
|
||||
eq_(s.scan_type, SCAN_TYPE_FILENAME)
|
||||
eq_(s.scan_type, ScanType.Filename)
|
||||
eq_(s.mix_file_kind, True)
|
||||
eq_(s.word_weighting, False)
|
||||
eq_(s.match_similar_words, False)
|
||||
@@ -95,7 +98,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_content_scan(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT
|
||||
s.scan_type = ScanType.Contents
|
||||
f = [no('foo'), no('bar'), no('bleh')]
|
||||
f[0].md5 = f[0].md5partial = 'foobar'
|
||||
f[1].md5 = f[1].md5partial = 'foobar'
|
||||
@@ -112,13 +115,13 @@ class ScannerTestFakeFiles(TestCase):
|
||||
raise AssertionError()
|
||||
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT
|
||||
s.scan_type = ScanType.Contents
|
||||
f = [MyFile('foo', 1), MyFile('bar', 2)]
|
||||
eq_(len(s.GetDupeGroups(f)), 0)
|
||||
|
||||
def test_min_match_perc_doesnt_matter_for_content_scan(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT
|
||||
s.scan_type = ScanType.Contents
|
||||
f = [no('foo'), no('bar'), no('bleh')]
|
||||
f[0].md5 = f[0].md5partial = 'foobar'
|
||||
f[1].md5 = f[1].md5partial = 'foobar'
|
||||
@@ -134,7 +137,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_content_scan_doesnt_put_md5_in_words_at_the_end(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT
|
||||
s.scan_type = ScanType.Contents
|
||||
f = [no('foo'),no('bar')]
|
||||
f[0].md5 = f[0].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||
f[1].md5 = f[1].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||
@@ -188,21 +191,21 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_fields(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_FIELDS
|
||||
s.scan_type = ScanType.Fields
|
||||
f = [no('The White Stripes - Little Ghost'), no('The White Stripes - Little Acorn')]
|
||||
r = s.GetDupeGroups(f)
|
||||
eq_(len(r), 0)
|
||||
|
||||
def test_fields_no_order(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_FIELDS_NO_ORDER
|
||||
s.scan_type = ScanType.FieldsNoOrder
|
||||
f = [no('The White Stripes - Little Ghost'), no('Little Ghost - The White Stripes')]
|
||||
r = s.GetDupeGroups(f)
|
||||
eq_(len(r), 1)
|
||||
|
||||
def test_tag_scan(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
o1.artist = 'The White Stripes'
|
||||
@@ -214,7 +217,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_tag_with_album_scan(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
@@ -233,7 +236,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_that_dash_in_tags_dont_create_new_fields(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||
s.min_match_percentage = 50
|
||||
o1 = no('foo')
|
||||
@@ -249,7 +252,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_tag_scan_with_different_scanned(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['track', 'year'])
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
@@ -266,7 +269,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_tag_scan_only_scans_existing_tags(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['artist', 'foo'])
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
@@ -279,7 +282,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_tag_scan_converts_to_str(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['track'])
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
@@ -293,12 +296,12 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_tag_scan_non_ascii(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_TAG
|
||||
s.scan_type = ScanType.Tag
|
||||
s.scanned_tags = set(['title'])
|
||||
o1 = no('foo')
|
||||
o2 = no('bar')
|
||||
o1.title = u'foobar\u00e9'
|
||||
o2.title = u'foobar\u00e9'
|
||||
o1.title = 'foobar\u00e9'
|
||||
o2.title = 'foobar\u00e9'
|
||||
try:
|
||||
r = s.GetDupeGroups([o1, o2])
|
||||
except UnicodeEncodeError:
|
||||
@@ -307,7 +310,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
|
||||
def test_audio_content_scan(self):
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
||||
s.scan_type = ScanType.ContentsAudio
|
||||
f = [no('foo'), no('bar'), no('bleh')]
|
||||
f[0].md5 = 'foo'
|
||||
f[1].md5 = 'bar'
|
||||
@@ -329,7 +332,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
raise AssertionError()
|
||||
|
||||
s = Scanner()
|
||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
||||
s.scan_type = ScanType.ContentsAudio
|
||||
f = [MyFile('foo'), MyFile('bar')]
|
||||
f[0].audiosize = 1
|
||||
f[1].audiosize = 2
|
||||
@@ -362,11 +365,11 @@ class ScannerTestFakeFiles(TestCase):
|
||||
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))
|
||||
f1.path = Path('foo1\u00e9')
|
||||
f2.path = Path('foo2\u00e9')
|
||||
f3.path = Path('foo3\u00e9')
|
||||
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]
|
||||
@@ -379,7 +382,7 @@ class ScannerTestFakeFiles(TestCase):
|
||||
# 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):
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
|
||||
@@ -426,11 +429,20 @@ class ScannerTestFakeFiles(TestCase):
|
||||
# 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 = no('foo bar 42')
|
||||
o2 = no('foo bar [42]')
|
||||
o3 = no('foo bar (42)')
|
||||
o4 = no('foo bar {42}')
|
||||
o5 = no('foo bar')
|
||||
# all numbered names have deeper paths, so they'll end up ref if the digits aren't correctly
|
||||
# used as tie breakers
|
||||
o1.path = Path('deeper/path')
|
||||
o2.path = Path('foo')
|
||||
[group] = s.GetDupeGroups([o1, o2])
|
||||
assert group.ref is o2
|
||||
o2.path = Path('deeper/path')
|
||||
o3.path = Path('deeper/path')
|
||||
o4.path = Path('deeper/path')
|
||||
o5.path = Path('foo')
|
||||
[group] = s.GetDupeGroups([o1, o2, o3, o4, o5])
|
||||
assert group.ref is o5
|
||||
|
||||
def test_partial_group_match(self):
|
||||
# Count the number od discarded matches (when a file doesn't match all other dupes of the
|
||||
@@ -453,7 +465,7 @@ class ScannerTest(TestCase):
|
||||
# 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
|
||||
s.scan_type = ScanType.Contents
|
||||
p = self.tmppath()
|
||||
io.open(p + 'file1', 'w').write('foo')
|
||||
io.open(p + 'file2', 'w').write('foo')
|
||||
|
||||
@@ -41,7 +41,7 @@ class DupeGuruME(DupeGuruBase):
|
||||
try:
|
||||
track.delete(timeout=0)
|
||||
except CommandError as e:
|
||||
logging.warning('Error while trying to remove a track from iTunes: %s' % unicode(e))
|
||||
logging.warning('Error while trying to remove a track from iTunes: %s' % str(e))
|
||||
|
||||
self._start_job(JOB_REMOVE_DEAD_TRACKS, do)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ COLUMNS = [
|
||||
{'attr':'bitrate','display':'Bitrate'},
|
||||
{'attr':'samplerate','display':'Sample Rate'},
|
||||
{'attr':'extension','display':'Kind'},
|
||||
{'attr':'ctime','display':'Creation'},
|
||||
{'attr':'mtime','display':'Modification'},
|
||||
{'attr':'title','display':'Title'},
|
||||
{'attr':'artist','display':'Artist'},
|
||||
@@ -32,7 +31,7 @@ COLUMNS = [
|
||||
{'attr':'dupe_count','display':'Dupe Count'},
|
||||
]
|
||||
|
||||
METADATA_TO_READ = ['size', 'ctime', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
|
||||
METADATA_TO_READ = ['size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
|
||||
'album', 'genre', 'year', 'track', 'comment']
|
||||
|
||||
def GetDisplayInfo(dupe, group, delta):
|
||||
@@ -40,7 +39,6 @@ def GetDisplayInfo(dupe, group, delta):
|
||||
duration = dupe.duration
|
||||
bitrate = dupe.bitrate
|
||||
samplerate = dupe.samplerate
|
||||
ctime = dupe.ctime
|
||||
mtime = dupe.mtime
|
||||
m = group.get_match_of(dupe)
|
||||
if m:
|
||||
@@ -52,7 +50,6 @@ def GetDisplayInfo(dupe, group, delta):
|
||||
duration -= r.duration
|
||||
bitrate -= r.bitrate
|
||||
samplerate -= r.samplerate
|
||||
ctime -= r.ctime
|
||||
mtime -= r.mtime
|
||||
else:
|
||||
percentage = group.percentage
|
||||
@@ -65,7 +62,6 @@ def GetDisplayInfo(dupe, group, delta):
|
||||
str(bitrate),
|
||||
str(samplerate),
|
||||
dupe.extension,
|
||||
format_timestamp(ctime,delta and m),
|
||||
format_timestamp(mtime,delta and m),
|
||||
dupe.title,
|
||||
dupe.artist,
|
||||
|
||||
@@ -42,12 +42,12 @@ class Mp3File(MusicFile):
|
||||
HANDLED_EXTS = set(['mp3'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
fileinfo = mpeg.Mpeg(unicode(self.path))
|
||||
fileinfo = mpeg.Mpeg(str(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))
|
||||
fileinfo = mpeg.Mpeg(str(self.path))
|
||||
self.audiosize = fileinfo.audio_size
|
||||
self.bitrate = fileinfo.bitrate
|
||||
self.duration = fileinfo.duration
|
||||
@@ -70,12 +70,12 @@ class WmaFile(MusicFile):
|
||||
HANDLED_EXTS = set(['wma'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
dec = wma.WMADecoder(unicode(self.path))
|
||||
dec = wma.WMADecoder(str(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))
|
||||
dec = wma.WMADecoder(str(self.path))
|
||||
self.audiosize = dec.audio_size
|
||||
self.bitrate = dec.bitrate
|
||||
self.duration = dec.duration
|
||||
@@ -92,13 +92,13 @@ class Mp4File(MusicFile):
|
||||
HANDLED_EXTS = set(['m4a', 'm4p'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
dec = mp4.File(unicode(self.path))
|
||||
dec = mp4.File(str(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))
|
||||
dec = mp4.File(str(self.path))
|
||||
self.audiosize = dec.audio_size
|
||||
self.bitrate = dec.bitrate
|
||||
self.duration = dec.duration
|
||||
@@ -116,12 +116,12 @@ class OggFile(MusicFile):
|
||||
HANDLED_EXTS = set(['ogg'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
dec = ogg.Vorbis(unicode(self.path))
|
||||
dec = ogg.Vorbis(str(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))
|
||||
dec = ogg.Vorbis(str(self.path))
|
||||
self.audiosize = dec.audio_size
|
||||
self.bitrate = dec.bitrate
|
||||
self.duration = dec.duration
|
||||
@@ -138,12 +138,12 @@ class FlacFile(MusicFile):
|
||||
HANDLED_EXTS = set(['flac'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
dec = flac.FLAC(unicode(self.path))
|
||||
dec = flac.FLAC(str(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))
|
||||
dec = flac.FLAC(str(self.path))
|
||||
self.audiosize = dec.audio_size
|
||||
self.bitrate = dec.bitrate
|
||||
self.duration = dec.duration
|
||||
@@ -160,12 +160,12 @@ class AiffFile(MusicFile):
|
||||
HANDLED_EXTS = set(['aif', 'aiff', 'aifc'])
|
||||
def _read_info(self, field):
|
||||
if field == 'md5partial':
|
||||
dec = aiff.File(unicode(self.path))
|
||||
dec = aiff.File(str(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))
|
||||
dec = aiff.File(str(self.path))
|
||||
self.audiosize = dec.audio_size
|
||||
self.bitrate = dec.bitrate
|
||||
self.duration = dec.duration
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
|
||||
import os.path as op
|
||||
import plistlib
|
||||
import logging
|
||||
import re
|
||||
|
||||
from lxml import etree
|
||||
from appscript import app, k, CommandError
|
||||
|
||||
from hsutil import io
|
||||
from hsutil.str import get_file_ext
|
||||
from hsutil.str import get_file_ext, remove_invalid_xml
|
||||
from hsutil.path import Path
|
||||
from hscommon.cocoa import as_fetch
|
||||
from hscommon.cocoa.objcmin import NSUserDefaults, NSURL
|
||||
@@ -37,15 +38,15 @@ class Photo(fs.File):
|
||||
def _read_info(self, field):
|
||||
fs.File._read_info(self, field)
|
||||
if field == 'dimensions':
|
||||
self.dimensions = _block_osx.get_image_size(unicode(self.path))
|
||||
self.dimensions = _block_osx.get_image_size(str(self.path))
|
||||
|
||||
def get_blocks(self, block_count_per_side):
|
||||
try:
|
||||
blocks = _block_osx.getblocks(unicode(self.path), block_count_per_side)
|
||||
blocks = _block_osx.getblocks(str(self.path), block_count_per_side)
|
||||
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"' % (str(self.path), str(e)))
|
||||
if not blocks:
|
||||
raise IOError('The picture %s could not be read' % unicode(self.path))
|
||||
raise IOError('The picture %s could not be read' % str(self.path))
|
||||
return blocks
|
||||
|
||||
|
||||
@@ -67,11 +68,16 @@ def get_iphoto_database_path():
|
||||
def get_iphoto_pictures(plistpath):
|
||||
if not io.exists(plistpath):
|
||||
return []
|
||||
# We make the xml go through lxml so that it can fix broken xml which iPhoto sometimes produces.
|
||||
parser = etree.XMLParser(recover=True)
|
||||
root = etree.parse(io.open(plistpath), parser=parser).getroot()
|
||||
s = etree.tostring(root)
|
||||
plist = plistlib.readPlistFromString(s)
|
||||
s = io.open(plistpath, 'rt', encoding='utf-8').read()
|
||||
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
||||
s = remove_invalid_xml(s, replace_with='')
|
||||
# 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
|
||||
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
||||
if count:
|
||||
logging.warning("%d invalid XML entities replacement made", count)
|
||||
plist = plistlib.readPlistFromBytes(s.encode('utf-8'))
|
||||
result = []
|
||||
for photo_data in plist['Master Image List'].values():
|
||||
if photo_data['MediaType'] != 'Image':
|
||||
@@ -116,6 +122,15 @@ class Directories(directories.Directories):
|
||||
else:
|
||||
directories.Directories.add_path(self, path)
|
||||
|
||||
def has_any_file(self):
|
||||
# If we don't do that, it causes a hangup in the GUI when we click Start Scanning because
|
||||
# checking if there's any file to scan involves reading the whole library. If we have the
|
||||
# iPhoto library, we assume we have at least one file.
|
||||
if any(path == Path('iPhoto Library') for path in self._dirs):
|
||||
return True
|
||||
else:
|
||||
return directories.Directories.has_any_file(self)
|
||||
|
||||
|
||||
class DupeGuruPE(app_cocoa.DupeGuru):
|
||||
def __init__(self):
|
||||
@@ -140,7 +155,7 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
||||
photos = as_fetch(a.photo_library_album().photos, k.item)
|
||||
for photo in j.iter_with_progress(photos):
|
||||
try:
|
||||
self.path2iphoto[unicode(photo.image_path(timeout=0))] = photo
|
||||
self.path2iphoto[str(photo.image_path(timeout=0))] = photo
|
||||
except CommandError:
|
||||
pass
|
||||
except (CommandError, RuntimeError):
|
||||
@@ -151,23 +166,19 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
||||
|
||||
def _do_delete_dupe(self, dupe):
|
||||
if isinstance(dupe, IPhoto):
|
||||
if unicode(dupe.path) in self.path2iphoto:
|
||||
photo = self.path2iphoto[unicode(dupe.path)]
|
||||
if str(dupe.path) in self.path2iphoto:
|
||||
photo = self.path2iphoto[str(dupe.path)]
|
||||
try:
|
||||
a = app('iPhoto')
|
||||
a.remove(photo, timeout=0)
|
||||
except (CommandError, RuntimeError) as e:
|
||||
raise EnvironmentError(unicode(e))
|
||||
raise EnvironmentError(str(e))
|
||||
else:
|
||||
msg = u"Could not find photo %s in iPhoto Library" % unicode(dupe.path)
|
||||
msg = "Could not find photo %s in iPhoto Library" % str(dupe.path)
|
||||
raise EnvironmentError(msg)
|
||||
else:
|
||||
app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
|
||||
|
||||
def _do_load(self, j):
|
||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
|
||||
|
||||
def _get_file(self, str_path):
|
||||
p = Path(str_path)
|
||||
if (self.directories.iphoto_libpath is not None) and (p in self.directories.iphoto_libpath[:-1]):
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from _block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2
|
||||
from ._block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2
|
||||
|
||||
# Converted to C
|
||||
# def getblock(image):
|
||||
|
||||
@@ -10,7 +10,7 @@ import os
|
||||
import logging
|
||||
import sqlite3 as sqlite
|
||||
|
||||
from _cache import string_to_colors
|
||||
from ._cache import string_to_colors
|
||||
|
||||
def colors_to_string(colors):
|
||||
"""Transform the 3 sized tuples 'colors' into a hex string.
|
||||
@@ -82,7 +82,7 @@ class Cache(object):
|
||||
self.con.execute(sql, [value, key])
|
||||
except sqlite.OperationalError:
|
||||
logging.warning('Picture cache could not set %r for key %r', value, key)
|
||||
except sqlite.DatabaseError, e:
|
||||
except sqlite.DatabaseError as e:
|
||||
logging.warning('DatabaseError while setting %r for key %r: %s', value, key, str(e))
|
||||
|
||||
def _create_con(self, second_try=False):
|
||||
@@ -97,7 +97,7 @@ class Cache(object):
|
||||
self.con.execute("select * from pictures where 1=2")
|
||||
except sqlite.OperationalError: # new db
|
||||
create_tables()
|
||||
except sqlite.DatabaseError, e: # corrupted db
|
||||
except sqlite.DatabaseError as e: # corrupted db
|
||||
if second_try:
|
||||
raise # Something really strange is happening
|
||||
logging.warning('Could not create picture cache because of an error: %s', str(e))
|
||||
|
||||
@@ -18,19 +18,17 @@ COLUMNS = [
|
||||
{'attr':'size','display':'Size (KB)'},
|
||||
{'attr':'extension','display':'Kind'},
|
||||
{'attr':'dimensions','display':'Dimensions'},
|
||||
{'attr':'ctime','display':'Creation'},
|
||||
{'attr':'mtime','display':'Modification'},
|
||||
{'attr':'percentage','display':'Match %'},
|
||||
{'attr':'dupe_count','display':'Dupe Count'},
|
||||
]
|
||||
|
||||
METADATA_TO_READ = ['size', 'ctime', 'mtime', 'dimensions']
|
||||
METADATA_TO_READ = ['size', 'mtime', 'dimensions']
|
||||
|
||||
def GetDisplayInfo(dupe,group,delta=False):
|
||||
if (dupe is None) or (group is None):
|
||||
return ['---'] * len(COLUMNS)
|
||||
size = dupe.size
|
||||
ctime = dupe.ctime
|
||||
mtime = dupe.mtime
|
||||
m = group.get_match_of(dupe)
|
||||
if m:
|
||||
@@ -39,7 +37,6 @@ def GetDisplayInfo(dupe,group,delta=False):
|
||||
if delta:
|
||||
r = group.ref
|
||||
size -= r.size
|
||||
ctime -= r.ctime
|
||||
mtime -= r.mtime
|
||||
else:
|
||||
percentage = group.percentage
|
||||
@@ -51,7 +48,6 @@ def GetDisplayInfo(dupe,group,delta=False):
|
||||
format_size(size, 0, 1, False),
|
||||
dupe.extension,
|
||||
format_dimensions(dupe.dimensions),
|
||||
format_timestamp(ctime, delta and m),
|
||||
format_timestamp(mtime, delta and m),
|
||||
format_perc(percentage),
|
||||
format_dupe_count(dupe_count)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-05-26
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import os
|
||||
import os.path as op
|
||||
|
||||
def move(src, dst):
|
||||
if not op.exists(src):
|
||||
return
|
||||
if op.exists(dst):
|
||||
os.remove(dst)
|
||||
print 'Moving %s --> %s' % (src, dst)
|
||||
os.rename(src, dst)
|
||||
|
||||
os.chdir('modules')
|
||||
os.system('python setup.py build_ext --inplace')
|
||||
os.chdir('..')
|
||||
move(op.join('modules', '_block.so'), '_block.so')
|
||||
move(op.join('modules', '_block.pyd'), '_block.pyd')
|
||||
move(op.join('modules', '_block_osx.so'), '_block_osx.so')
|
||||
move(op.join('modules', '_cache.so'), '_cache.so')
|
||||
move(op.join('modules', '_cache.pyd'), '_cache.pyd')
|
||||
@@ -34,16 +34,16 @@ def prepare_pictures(pictures, cache_path, j=job.nulljob):
|
||||
try:
|
||||
for picture in j.iter_with_progress(pictures, 'Analyzed %d/%d pictures'):
|
||||
picture.dimensions
|
||||
picture.unicode_path = unicode(picture.path)
|
||||
picture.unicode_path = str(picture.path)
|
||||
try:
|
||||
if picture.unicode_path not in cache:
|
||||
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
|
||||
cache[picture.unicode_path] = blocks
|
||||
prepared.append(picture)
|
||||
except (IOError, ValueError) as e:
|
||||
logging.warning(unicode(e))
|
||||
logging.warning(str(e))
|
||||
except MemoryError:
|
||||
logging.warning(u'Ran out of memory while reading %s of size %d' % (picture.unicode_path, picture.size))
|
||||
logging.warning('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:
|
||||
|
||||
@@ -39,9 +39,9 @@ static PyObject* getblock(PyObject *image)
|
||||
pg = PySequence_ITEM(ppixel, 1);
|
||||
pb = PySequence_ITEM(ppixel, 2);
|
||||
Py_DECREF(ppixel);
|
||||
r = PyInt_AsSsize_t(pr);
|
||||
g = PyInt_AsSsize_t(pg);
|
||||
b = PyInt_AsSsize_t(pb);
|
||||
r = PyLong_AsLong(pr);
|
||||
g = PyLong_AsLong(pg);
|
||||
b = PyLong_AsLong(pb);
|
||||
Py_DECREF(pr);
|
||||
Py_DECREF(pg);
|
||||
Py_DECREF(pb);
|
||||
@@ -67,14 +67,14 @@ static PyObject* getblock(PyObject *image)
|
||||
*/
|
||||
static int diff(PyObject *first, PyObject *second)
|
||||
{
|
||||
Py_ssize_t r1, g1, b1, r2, b2, g2;
|
||||
int r1, g1, b1, r2, b2, g2;
|
||||
PyObject *pr, *pg, *pb;
|
||||
pr = PySequence_ITEM(first, 0);
|
||||
pg = PySequence_ITEM(first, 1);
|
||||
pb = PySequence_ITEM(first, 2);
|
||||
r1 = PyInt_AsSsize_t(pr);
|
||||
g1 = PyInt_AsSsize_t(pg);
|
||||
b1 = PyInt_AsSsize_t(pb);
|
||||
r1 = PyLong_AsLong(pr);
|
||||
g1 = PyLong_AsLong(pg);
|
||||
b1 = PyLong_AsLong(pb);
|
||||
Py_DECREF(pr);
|
||||
Py_DECREF(pg);
|
||||
Py_DECREF(pb);
|
||||
@@ -82,9 +82,9 @@ static int diff(PyObject *first, PyObject *second)
|
||||
pr = PySequence_ITEM(second, 0);
|
||||
pg = PySequence_ITEM(second, 1);
|
||||
pb = PySequence_ITEM(second, 2);
|
||||
r2 = PyInt_AsSsize_t(pr);
|
||||
g2 = PyInt_AsSsize_t(pg);
|
||||
b2 = PyInt_AsSsize_t(pb);
|
||||
r2 = PyLong_AsLong(pr);
|
||||
g2 = PyLong_AsLong(pg);
|
||||
b2 = PyLong_AsLong(pb);
|
||||
Py_DECREF(pr);
|
||||
Py_DECREF(pg);
|
||||
Py_DECREF(pb);
|
||||
@@ -115,8 +115,8 @@ static PyObject* block_getblocks2(PyObject *self, PyObject *args)
|
||||
pimage_size = PyObject_GetAttrString(image, "size");
|
||||
pwidth = PySequence_ITEM(pimage_size, 0);
|
||||
pheight = PySequence_ITEM(pimage_size, 1);
|
||||
width = PyInt_AsSsize_t(pwidth);
|
||||
height = PyInt_AsSsize_t(pheight);
|
||||
width = PyLong_AsLong(pwidth);
|
||||
height = PyLong_AsLong(pheight);
|
||||
Py_DECREF(pimage_size);
|
||||
Py_DECREF(pwidth);
|
||||
Py_DECREF(pheight);
|
||||
@@ -147,8 +147,8 @@ static PyObject* block_getblocks2(PyObject *self, PyObject *args)
|
||||
left = min(iw*block_width, width-block_width);
|
||||
right = left + block_width;
|
||||
pbox = inttuple(4, left, top, right, bottom);
|
||||
pmethodname = PyString_FromString("crop");
|
||||
pcrop = PyObject_CallMethodObjArgs(image, pmethodname, pbox);
|
||||
pmethodname = PyUnicode_FromString("crop");
|
||||
pcrop = PyObject_CallMethodObjArgs(image, pmethodname, pbox, NULL);
|
||||
Py_DECREF(pmethodname);
|
||||
Py_DECREF(pbox);
|
||||
if (pcrop == NULL) {
|
||||
@@ -207,7 +207,7 @@ static PyObject* block_avgdiff(PyObject *self, PyObject *args)
|
||||
Py_DECREF(item1);
|
||||
Py_DECREF(item2);
|
||||
if ((sum > limit*iteration_count) && (iteration_count >= min_iterations)) {
|
||||
return PyInt_FromSsize_t(limit + 1);
|
||||
return PyLong_FromLong(limit + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ static PyObject* block_avgdiff(PyObject *self, PyObject *args)
|
||||
if (!result && sum) {
|
||||
result = 1;
|
||||
}
|
||||
return PyInt_FromSsize_t(result);
|
||||
return PyLong_FromLong(result);
|
||||
}
|
||||
|
||||
static PyMethodDef BlockMethods[] = {
|
||||
@@ -224,16 +224,30 @@ static PyMethodDef BlockMethods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
init_block(void)
|
||||
static struct PyModuleDef BlockDef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_block",
|
||||
NULL,
|
||||
-1,
|
||||
BlockMethods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyObject *
|
||||
PyInit__block(void)
|
||||
{
|
||||
PyObject *m = Py_InitModule("_block", BlockMethods);
|
||||
PyObject *m = PyModule_Create(&BlockDef);
|
||||
if (m == NULL) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NoBlocksError = PyErr_NewException("_block.NoBlocksError", NULL, NULL);
|
||||
PyModule_AddObject(m, "NoBlocksError", NoBlocksError);
|
||||
DifferentBlockCountError = PyErr_NewException("_block.DifferentBlockCountError", NULL, NULL);
|
||||
PyModule_AddObject(m, "DifferentBlockCountError", DifferentBlockCountError);
|
||||
|
||||
return m;
|
||||
}
|
||||
@@ -29,8 +29,8 @@ pystring2cfstring(PyObject *pystring)
|
||||
Py_INCREF(encoded);
|
||||
}
|
||||
|
||||
s = (UInt8*)PyString_AS_STRING(encoded);
|
||||
size = PyString_GET_SIZE(encoded);
|
||||
s = (UInt8*)PyBytes_AS_STRING(encoded);
|
||||
size = PyUnicode_GET_SIZE(encoded);
|
||||
result = CFStringCreateWithBytes(NULL, s, size, kCFStringEncodingUTF8, FALSE);
|
||||
Py_DECREF(encoded);
|
||||
return result;
|
||||
@@ -43,7 +43,7 @@ static PyObject* block_osx_get_image_size(PyObject *self, PyObject *args)
|
||||
CFURLRef image_url;
|
||||
CGImageSourceRef source;
|
||||
CGImageRef image;
|
||||
size_t width, height;
|
||||
long width, height;
|
||||
PyObject *pwidth, *pheight;
|
||||
PyObject *result;
|
||||
|
||||
@@ -72,11 +72,11 @@ static PyObject* block_osx_get_image_size(PyObject *self, PyObject *args)
|
||||
CFRelease(source);
|
||||
}
|
||||
|
||||
pwidth = PyInt_FromSsize_t(width);
|
||||
pwidth = PyLong_FromLong(width);
|
||||
if (pwidth == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
pheight = PyInt_FromSsize_t(height);
|
||||
pheight = PyLong_FromLong(height);
|
||||
if (pheight == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -228,8 +228,24 @@ static PyMethodDef BlockOsxMethods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
init_block_osx(void)
|
||||
static struct PyModuleDef BlockOsxDef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_block_osx",
|
||||
NULL,
|
||||
-1,
|
||||
BlockOsxMethods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyObject *
|
||||
PyInit__block_osx(void)
|
||||
{
|
||||
Py_InitModule("_block_osx", BlockOsxMethods);
|
||||
PyObject *m = PyModule_Create(&BlockOsxDef);
|
||||
if (m == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
@@ -72,8 +72,24 @@ static PyMethodDef CacheMethods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
init_cache(void)
|
||||
static struct PyModuleDef CacheDef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_cache",
|
||||
NULL,
|
||||
-1,
|
||||
CacheMethods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyObject *
|
||||
PyInit__cache(void)
|
||||
{
|
||||
(void)Py_InitModule("_cache", CacheMethods);
|
||||
}
|
||||
PyObject *m = PyModule_Create(&CacheDef);
|
||||
if (m == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ PyObject* inttuple(int n, ...)
|
||||
result = PyTuple_New(n);
|
||||
|
||||
for (i=0; i<n; i++) {
|
||||
pnumber = PyInt_FromLong(va_arg(numbers, int));
|
||||
pnumber = PyLong_FromLong(va_arg(numbers, long));
|
||||
if (pnumber == NULL) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-23
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
|
||||
exts = []
|
||||
|
||||
exts.append(Extension("_block", ["block.c", "common.c"]))
|
||||
exts.append(Extension("_cache", ["cache.c", "common.c"]))
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
exts.append(Extension(
|
||||
"_block_osx", ["block_osx.m", "common.c"],
|
||||
extra_link_args=[
|
||||
"-framework", "CoreFoundation",
|
||||
"-framework", "Foundation",
|
||||
"-framework", "ApplicationServices",
|
||||
]))
|
||||
|
||||
setup(
|
||||
ext_modules = exts,
|
||||
)
|
||||
@@ -258,8 +258,8 @@ class TCavgdiff(unittest.TestCase):
|
||||
def test_return_at_least_1_at_the_slightest_difference(self):
|
||||
ref = (0,0,0)
|
||||
b1 = (1,0,0)
|
||||
blocks1 = [ref for i in xrange(250)]
|
||||
blocks2 = [ref for i in xrange(250)]
|
||||
blocks1 = [ref for i in range(250)]
|
||||
blocks2 = [ref for i in range(250)]
|
||||
blocks2[0] = b1
|
||||
self.assertEqual(1,my_avgdiff(blocks1,blocks2))
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from StringIO import StringIO
|
||||
import os.path as op
|
||||
import os
|
||||
import threading
|
||||
|
||||
from hsutil.testcase import TestCase
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
@@ -24,17 +24,17 @@ def is_bundle(str_path):
|
||||
sw = NSWorkspace.sharedWorkspace()
|
||||
uti, error = sw.typeOfFile_error_(str_path, None)
|
||||
if error is not None:
|
||||
logging.warning(u'There was an error trying to detect the UTI of %s', str_path)
|
||||
logging.warning('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')
|
||||
|
||||
class Bundle(BundleBase):
|
||||
@classmethod
|
||||
def can_handle(cls, path):
|
||||
return not io.islink(path) and io.isdir(path) and is_bundle(unicode(path))
|
||||
return not io.islink(path) and io.isdir(path) and is_bundle(str(path))
|
||||
|
||||
|
||||
class Directories(DirectoriesBase):
|
||||
ROOT_PATH_TO_EXCLUDE = map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev'])
|
||||
ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev']))
|
||||
HOME_PATH_TO_EXCLUDE = [Path('Library')]
|
||||
def __init__(self):
|
||||
DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File])
|
||||
|
||||
@@ -15,18 +15,16 @@ COLUMNS = [
|
||||
{'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']
|
||||
METADATA_TO_READ = ['size', 'mtime']
|
||||
|
||||
def GetDisplayInfo(dupe, group, delta):
|
||||
size = dupe.size
|
||||
ctime = dupe.ctime
|
||||
mtime = dupe.mtime
|
||||
m = group.get_match_of(dupe)
|
||||
if m:
|
||||
@@ -35,7 +33,6 @@ def GetDisplayInfo(dupe, group, delta):
|
||||
if delta:
|
||||
r = group.ref
|
||||
size -= r.size
|
||||
ctime -= r.ctime
|
||||
mtime -= r.mtime
|
||||
else:
|
||||
percentage = group.percentage
|
||||
@@ -45,7 +42,6 @@ def GetDisplayInfo(dupe, group, delta):
|
||||
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) if hasattr(dupe, 'words') else '',
|
||||
|
||||
@@ -20,12 +20,11 @@ class Bundle(fs.File):
|
||||
to see them as files.
|
||||
"""
|
||||
def _read_info(self, field):
|
||||
if field in ('size', 'ctime', 'mtime'):
|
||||
if field in ('size', 'mtime'):
|
||||
files = fs.get_all_files(self.path)
|
||||
size = sum((file.size for file in files), 0)
|
||||
self.size = size
|
||||
stats = io.stat(self.path)
|
||||
self.ctime = nonone(stats.st_ctime, 0)
|
||||
self.mtime = nonone(stats.st_mtime, 0)
|
||||
elif field in ('md5', 'md5partial'):
|
||||
# What's sensitive here is that we must make sure that subfiles'
|
||||
@@ -35,7 +34,7 @@ class Bundle(fs.File):
|
||||
files = fs.get_all_files(self.path)
|
||||
files.sort(key=lambda f:f.path)
|
||||
md5s = [getattr(f, field) for f in files]
|
||||
return ''.join(md5s)
|
||||
return b''.join(md5s)
|
||||
|
||||
md5 = hashlib.md5(get_dir_md5_concat())
|
||||
digest = md5.digest()
|
||||
|
||||
@@ -38,9 +38,8 @@ class TCBundle(TestCase):
|
||||
eq_(b.md5, md5.digest())
|
||||
|
||||
def test_has_file_attrs(self):
|
||||
#a Bundle must behave like a file, so it must have ctime and mtime attributes
|
||||
#a Bundle must behave like a file, so it must have mtime attributes
|
||||
b = fs.Bundle(self.tmppath())
|
||||
assert b.mtime > 0
|
||||
assert b.ctime > 0
|
||||
eq_(b.extension, '')
|
||||
|
||||
@@ -8,5 +8,5 @@ Homepage: http://www.hardcoded.net
|
||||
|
||||
Package: dupeguru-me
|
||||
Architecture: any
|
||||
Depends: python (>= 2.6), python-qt4 (>= 4.6), python-lxml (>= 2.1)
|
||||
Depends: python (>= 2.6), python-qt4 (>= 4.6)
|
||||
Description: dupeGuru Music Edition
|
||||
|
||||
@@ -8,5 +8,5 @@ Homepage: http://www.hardcoded.net
|
||||
|
||||
Package: dupeguru-pe
|
||||
Architecture: any
|
||||
Depends: python (>= 2.6), python-qt4 (>= 4.6), python-lxml (>= 2.1), python-imaging (>= 1.1.6)
|
||||
Depends: python (>= 2.6), python-qt4 (>= 4.6), python-imaging (>= 1.1.6)
|
||||
Description: dupeGuru Picture Edition
|
||||
|
||||
1
debian_se/compat
Normal file
1
debian_se/compat
Normal file
@@ -0,0 +1 @@
|
||||
7
|
||||
@@ -8,5 +8,5 @@ Homepage: http://www.hardcoded.net
|
||||
|
||||
Package: dupeguru-se
|
||||
Architecture: any
|
||||
Depends: python (>= 2.6), python-qt4 (>= 4.6), python-lxml (>= 2.1)
|
||||
Depends: python3 (>= 3.1)
|
||||
Description: dupeGuru
|
||||
|
||||
@@ -42,9 +42,9 @@ install: build
|
||||
dh_installdirs
|
||||
|
||||
chmod +x src/start.py
|
||||
cp -R src/ $(CURDIR)/debian/tmp/usr/local/share/dupeguru_se
|
||||
cp $(CURDIR)/debian/dupeguru_se.desktop $(CURDIR)/debian/tmp/usr/share/applications
|
||||
ln -s /usr/local/share/dupeguru_se/start.py $(CURDIR)/debian/tmp/usr/local/bin/dupeguru_se
|
||||
cp -R src/ $(CURDIR)/debian/dupeguru-se/usr/local/share/dupeguru_se
|
||||
cp $(CURDIR)/debian/dupeguru_se.desktop $(CURDIR)/debian/dupeguru-se/usr/share/applications
|
||||
ln -s /usr/local/share/dupeguru_se/start.py $(CURDIR)/debian/dupeguru-se/usr/local/bin/dupeguru_se
|
||||
|
||||
|
||||
# Build architecture-independent files here.
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
- date: 2010-08-18
|
||||
version: 2.11.0
|
||||
description: |
|
||||
* Added the ability to save results (and reload them) at arbitrary locations.
|
||||
* Improved the way reference files in dupe groups are chosen. (#15)
|
||||
* Remember size/position of all windows between launches. (#102)
|
||||
* Fixed a bug sometimes preventing dupeGuru from reloading previous results.
|
||||
* Fixed a bug sometimes causing the progress dialog to be stuck there. [Mac OS X] (#103)
|
||||
* Removed the Creation Date column, which wasn't displaying the correct value anyway. (#101)
|
||||
- date: 2010-07-15
|
||||
version: 2.10.1
|
||||
description: |
|
||||
|
||||
19
package.py
19
package.py
@@ -30,10 +30,11 @@ def package_windows(edition, dev):
|
||||
# On Windows, PyInstaller is used to build an exe (py2exe creates a very bad looking icon)
|
||||
# The release version is outdated. Use at least r672 on http://svn.pyinstaller.org/trunk
|
||||
if sys.platform != "win32":
|
||||
print "Qt packaging only works under Windows."
|
||||
print("Qt packaging only works under Windows.")
|
||||
return
|
||||
add_to_pythonpath('.')
|
||||
add_to_pythonpath('qt')
|
||||
add_to_pythonpath(op.join('qt', 'base'))
|
||||
add_to_pythonpath(op.join('qt', edition))
|
||||
os.chdir(op.join('qt', edition))
|
||||
from app import DupeGuru
|
||||
@@ -60,7 +61,7 @@ def package_windows(edition, dev):
|
||||
help_basedir = '..\\..\\help_{0}'.format(edition)
|
||||
help_dir = 'dupeguru_{0}_help'.format(edition) if edition != 'se' else 'dupeguru_help'
|
||||
help_path = op.join(help_basedir, help_dir)
|
||||
print "Copying {0} to dist\\help".format(help_path)
|
||||
print("Copying {0} to dist\\help".format(help_path))
|
||||
shutil.copytree(help_path, 'dist\\help')
|
||||
|
||||
# AdvancedInstaller.com has to be in your PATH
|
||||
@@ -73,6 +74,7 @@ def package_windows(edition, dev):
|
||||
|
||||
def package_debian(edition):
|
||||
add_to_pythonpath('qt')
|
||||
add_to_pythonpath(op.join('qt', 'base'))
|
||||
add_to_pythonpath(op.join('qt', edition))
|
||||
from app import DupeGuru
|
||||
|
||||
@@ -88,6 +90,15 @@ def package_debian(edition):
|
||||
if edition == 'me':
|
||||
packages.append('hsaudiotag')
|
||||
copy_packages(packages, srcpath)
|
||||
import sip, PyQt4
|
||||
shutil.copy(sip.__file__, srcpath)
|
||||
qtsrcpath = op.dirname(PyQt4.__file__)
|
||||
qtdestpath = op.join(srcpath, 'PyQt4')
|
||||
os.makedirs(qtdestpath)
|
||||
shutil.copy(op.join(qtsrcpath, '__init__.py'), qtdestpath)
|
||||
shutil.copy(op.join(qtsrcpath, 'Qt.so'), qtdestpath)
|
||||
shutil.copy(op.join(qtsrcpath, 'QtCore.so'), qtdestpath)
|
||||
shutil.copy(op.join(qtsrcpath, 'QtGui.so'), qtdestpath)
|
||||
shutil.copytree(ed('debian_{0}'), op.join(destpath, 'debian'))
|
||||
yaml_path = op.join(help_src, 'changelog.yaml')
|
||||
changelog_dest = op.join(destpath, 'debian', 'changelog')
|
||||
@@ -106,7 +117,7 @@ def main():
|
||||
edition = conf['edition']
|
||||
ui = conf['ui']
|
||||
dev = conf['dev']
|
||||
print "Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui)
|
||||
print("Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||
if ui == 'cocoa':
|
||||
package_cocoa(edition)
|
||||
elif ui == 'qt':
|
||||
@@ -115,7 +126,7 @@ def main():
|
||||
elif sys.platform == "linux2":
|
||||
package_debian(edition)
|
||||
else:
|
||||
print "Qt packaging only works under Windows or Linux."
|
||||
print("Qt packaging only works under Windows or Linux.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import os.path as op
|
||||
|
||||
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
||||
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL, pyqtSignal
|
||||
from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox
|
||||
|
||||
from hscommon import job
|
||||
@@ -54,7 +54,7 @@ class DupeGuru(DupeGuruBase, QObject):
|
||||
DELTA_COLUMNS = frozenset()
|
||||
|
||||
def __init__(self, data_module, appid):
|
||||
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
||||
appdata = str(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
||||
if not op.exists(appdata):
|
||||
os.makedirs(appdata)
|
||||
# For basicConfig() to work, we have to be sure that no logging has taken place before this call.
|
||||
@@ -86,7 +86,10 @@ class DupeGuru(DupeGuruBase, QObject):
|
||||
self._nagTimer = QTimer()
|
||||
self.connect(self._nagTimer, SIGNAL('timeout()'), self.mustShowNag)
|
||||
self._nagTimer.start(0)
|
||||
self.main_window.show()
|
||||
if self.prefs.mainWindowIsMaximized:
|
||||
self.main_window.showMaximized()
|
||||
else:
|
||||
self.main_window.show()
|
||||
self.load()
|
||||
|
||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
||||
@@ -120,7 +123,7 @@ class DupeGuru(DupeGuruBase, QObject):
|
||||
#--- Override
|
||||
@staticmethod
|
||||
def _open_path(path):
|
||||
url = QUrl.fromLocalFile(unicode(path))
|
||||
url = QUrl.fromLocalFile(str(path))
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
@staticmethod
|
||||
@@ -150,7 +153,7 @@ class DupeGuru(DupeGuruBase, QObject):
|
||||
opname = 'copy' if copy else 'move'
|
||||
title = "Select a directory to {0} marked files to".format(opname)
|
||||
flags = QFileDialog.ShowDirsOnly
|
||||
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
|
||||
destination = str(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
|
||||
if not destination:
|
||||
return
|
||||
recreate_path = self.prefs.destination_type
|
||||
@@ -205,8 +208,13 @@ class DupeGuru(DupeGuruBase, QObject):
|
||||
self.prefs.save()
|
||||
self._update_options()
|
||||
|
||||
#--- Signals
|
||||
willSavePrefs = pyqtSignal()
|
||||
|
||||
#--- Events
|
||||
def application_will_terminate(self):
|
||||
self.willSavePrefs.emit()
|
||||
self.prefs.save()
|
||||
self.save()
|
||||
self.save_ignore_list()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cxfreeze has some problems detecting all dependencies.
|
||||
# This modules explicitly import those problematic modules.
|
||||
|
||||
import lxml._elementpath
|
||||
import xml.etree.ElementPath
|
||||
import gzip
|
||||
|
||||
import os
|
||||
|
||||
@@ -20,15 +20,23 @@ class DetailsDialog(QDialog):
|
||||
self.app = app
|
||||
self.model = DetailsPanel(self, app)
|
||||
self._setupUi()
|
||||
if self.app.prefs.detailsWindowRect is not None:
|
||||
self.setGeometry(self.app.prefs.detailsWindowRect)
|
||||
self.tableModel = DetailsModel(self.model)
|
||||
# tableView is defined in subclasses
|
||||
self.tableView.setModel(self.tableModel)
|
||||
self.model.connect()
|
||||
|
||||
self.app.willSavePrefs.connect(self.appWillSavePrefs)
|
||||
|
||||
def _setupUi(self): # Virtual
|
||||
pass
|
||||
|
||||
# model --> view
|
||||
#--- Events
|
||||
def appWillSavePrefs(self):
|
||||
self.app.prefs.detailsWindowRect = self.geometry()
|
||||
|
||||
#--- model --> view
|
||||
def refresh(self):
|
||||
self.tableModel.reset()
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
|
||||
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
|
||||
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
|
||||
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
|
||||
self.app.willSavePrefs.connect(self.appWillSavePrefs)
|
||||
|
||||
def _setupUi(self):
|
||||
self.setupUi(self)
|
||||
@@ -40,6 +41,9 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
|
||||
header.setResizeMode(0, QHeaderView.Stretch)
|
||||
header.setResizeMode(1, QHeaderView.Fixed)
|
||||
header.resizeSection(1, 100)
|
||||
|
||||
if self.app.prefs.directoriesWindowRect is not None:
|
||||
self.setGeometry(self.app.prefs.directoriesWindowRect)
|
||||
|
||||
def _updateRemoveButton(self):
|
||||
indexes = self.treeView.selectedIndexes()
|
||||
@@ -51,15 +55,19 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
|
||||
node = index.internalPointer()
|
||||
# label = 'Remove' if node.parent is None else 'Exclude'
|
||||
|
||||
#--- Events
|
||||
def addButtonClicked(self):
|
||||
title = u"Select a directory to add to the scanning list"
|
||||
title = "Select a directory to add to the scanning list"
|
||||
flags = QFileDialog.ShowDirsOnly
|
||||
dirpath = unicode(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
|
||||
dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
|
||||
if not dirpath:
|
||||
return
|
||||
self.lastAddedFolder = dirpath
|
||||
self.app.add_directory(dirpath)
|
||||
|
||||
def appWillSavePrefs(self):
|
||||
self.app.prefs.directoriesWindowRect = self.geometry()
|
||||
|
||||
def doneButtonClicked(self):
|
||||
self.hide()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import urllib
|
||||
import urllib.parse
|
||||
|
||||
from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint, QUrl
|
||||
from PyQt4.QtGui import (QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush, QStyle,
|
||||
@@ -101,9 +101,9 @@ class DirectoriesModel(TreeModel):
|
||||
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]
|
||||
unquoted = urllib.parse.unquote(data)
|
||||
urls = str(unquoted, 'utf-8').split('\r\n')
|
||||
paths = [str(QUrl(url).toLocalFile()) for url in urls if url]
|
||||
for path in paths:
|
||||
self.model.add_directory(path)
|
||||
self.reset()
|
||||
|
||||
@@ -10,16 +10,16 @@ import sys
|
||||
|
||||
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
|
||||
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
|
||||
QMessageBox, QInputDialog, QLineEdit, QDesktopServices)
|
||||
QMessageBox, QInputDialog, QLineEdit, QDesktopServices, QFileDialog)
|
||||
|
||||
from hsutil.misc import nonone
|
||||
|
||||
from core.app import NoScannableFileError, AllFilesAreRefError
|
||||
from core.app import NoScannableFileError
|
||||
|
||||
import dg_rc
|
||||
from main_window_ui import Ui_MainWindow
|
||||
from results_model import ResultsModel
|
||||
from stats_label import StatsLabel
|
||||
from . import dg_rc
|
||||
from .main_window_ui import Ui_MainWindow
|
||||
from .results_model import ResultsModel
|
||||
from .stats_label import StatsLabel
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, app):
|
||||
@@ -34,10 +34,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
|
||||
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
|
||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
||||
self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
|
||||
self.connect(self.resultsView, SIGNAL('spacePressed()'), self.resultsSpacePressed)
|
||||
self.app.willSavePrefs.connect(self.appWillSavePrefs)
|
||||
|
||||
# Actions (the vast majority of them are connected in the UI file, but I'm trying to
|
||||
# phase away from those, and these connections are harder to maintain than through simple
|
||||
# code
|
||||
self.actionInvokeCustomCommand.triggered.connect(self.app.invokeCustomCommand)
|
||||
self.actionLoadResults.triggered.connect(self.loadResultsTriggered)
|
||||
self.actionSaveResults.triggered.connect(self.saveResultsTriggered)
|
||||
|
||||
def _setupUi(self):
|
||||
self.setupUi(self)
|
||||
@@ -90,6 +96,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.statusLabel = QLabel(self)
|
||||
self.statusbar.addPermanentWidget(self.statusLabel, 1)
|
||||
|
||||
if self.app.prefs.mainWindowRect is not None and not self.app.prefs.mainWindowIsMaximized:
|
||||
self.setGeometry(self.app.prefs.mainWindowRect)
|
||||
|
||||
# Linux setup
|
||||
if sys.platform == 'linux2':
|
||||
self.actionCheckForUpdate.setVisible(False) # This only works on Windows
|
||||
@@ -104,24 +113,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
h = self.resultsView.header()
|
||||
h.setResizeMode(QHeaderView.Interactive)
|
||||
prefs = self.app.prefs
|
||||
attrs = zip(prefs.columns_width, prefs.columns_visible)
|
||||
attrs = list(zip(prefs.columns_width, prefs.columns_visible))
|
||||
for index, (width, visible) in enumerate(attrs):
|
||||
h.resizeSection(index, width)
|
||||
h.setSectionHidden(index, not visible)
|
||||
h.setResizeMode(0, QHeaderView.Stretch)
|
||||
|
||||
def _save_columns(self):
|
||||
h = self.resultsView.header()
|
||||
widths = []
|
||||
visible = []
|
||||
for i in range(len(self.app.data.COLUMNS)):
|
||||
widths.append(h.sectionSize(i))
|
||||
visible.append(not h.isSectionHidden(i))
|
||||
prefs = self.app.prefs
|
||||
prefs.columns_width = widths
|
||||
prefs.columns_visible = visible
|
||||
prefs.save()
|
||||
|
||||
def _update_column_actions_status(self):
|
||||
h = self.resultsView.header()
|
||||
for action in self._column_actions:
|
||||
@@ -145,7 +142,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
answer, ok = QInputDialog.getText(self, title, msg, QLineEdit.Normal, text)
|
||||
if not ok:
|
||||
return
|
||||
answer = unicode(answer)
|
||||
answer = str(answer)
|
||||
self.app.apply_filter(answer)
|
||||
self._last_filter = answer
|
||||
|
||||
@@ -196,7 +193,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
exported_path = self.app.export_to_xhtml(column_ids)
|
||||
url = QUrl.fromLocalFile(exported_path)
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
|
||||
def loadResultsTriggered(self):
|
||||
title = "Select a results file to load"
|
||||
files = "dupeGuru Results (*.dupeguru)"
|
||||
destination = QFileDialog.getOpenFileName(self, title, '', files)
|
||||
if destination:
|
||||
self.app.load_from(destination)
|
||||
|
||||
def makeReferenceTriggered(self):
|
||||
self.app.make_selected_reference()
|
||||
|
||||
@@ -248,6 +252,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def revealTriggered(self):
|
||||
self.app.reveal_selected()
|
||||
|
||||
def saveResultsTriggered(self):
|
||||
title = "Select a file to save your results to"
|
||||
files = "dupeGuru Results (*.dupeguru)"
|
||||
destination = QFileDialog.getSaveFileName(self, title, '', files)
|
||||
if destination:
|
||||
self.app.save_as(destination)
|
||||
|
||||
def scanTriggered(self):
|
||||
title = "Start a new scan"
|
||||
if len(self.app.results.groups) > 0:
|
||||
@@ -260,16 +271,23 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
msg = "The selected directories contain no scannable file."
|
||||
QMessageBox.warning(self, title, msg)
|
||||
self.app.show_directories()
|
||||
except AllFilesAreRefError:
|
||||
msg = "You cannot make a duplicate scan with only reference directories."
|
||||
QMessageBox.warning(self, title, msg)
|
||||
|
||||
def showHelpTriggered(self):
|
||||
self.app.show_help()
|
||||
|
||||
#--- Events
|
||||
def application_will_terminate(self):
|
||||
self._save_columns()
|
||||
def appWillSavePrefs(self):
|
||||
prefs = self.app.prefs
|
||||
h = self.resultsView.header()
|
||||
widths = []
|
||||
visible = []
|
||||
for i in range(len(self.app.data.COLUMNS)):
|
||||
widths.append(h.sectionSize(i))
|
||||
visible.append(not h.isSectionHidden(i))
|
||||
prefs.columns_width = widths
|
||||
prefs.columns_visible = visible
|
||||
prefs.mainWindowIsMaximized = self.isMaximized()
|
||||
prefs.mainWindowRect = self.geometry()
|
||||
|
||||
def columnToggled(self, action):
|
||||
colid = action.column_index
|
||||
|
||||
@@ -126,6 +126,8 @@
|
||||
</property>
|
||||
<addaction name="actionScan"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionLoadResults"/>
|
||||
<addaction name="actionSaveResults"/>
|
||||
<addaction name="actionExport"/>
|
||||
<addaction name="actionClearIgnoreList"/>
|
||||
<addaction name="separator"/>
|
||||
@@ -183,7 +185,7 @@
|
||||
<string>Start scanning for duplicates</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
<string>Ctrl+T</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDirectories">
|
||||
@@ -427,6 +429,22 @@
|
||||
<string>Export To XHTML</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLoadResults">
|
||||
<property name="text">
|
||||
<string>Load Results...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+L</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSaveResults">
|
||||
<property name="text">
|
||||
<string>Save Results...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenDebugLog">
|
||||
<property name="text">
|
||||
<string>Open Debug Log</string>
|
||||
|
||||
@@ -11,10 +11,10 @@ import logging
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from platform_win import *
|
||||
from .platform_win import *
|
||||
elif sys.platform == 'darwin':
|
||||
from platform_osx import *
|
||||
from .platform_osx import *
|
||||
elif sys.platform == 'linux2':
|
||||
from platform_lnx import *
|
||||
from .platform_lnx import *
|
||||
else:
|
||||
pass # unsupported platform
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
INITIAL_FOLDER_IN_DIALOGS = u'/'
|
||||
INITIAL_FOLDER_IN_DIALOGS = '/'
|
||||
HELP_PATH = '/usr/local/share/dupeguru_{0}/help'
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
|
||||
# dummy unit to allow the app to run under OSX during development
|
||||
|
||||
INITIAL_FOLDER_IN_DIALOGS = u'/'
|
||||
INITIAL_FOLDER_IN_DIALOGS = '/'
|
||||
HELP_PATH = ''
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
INITIAL_FOLDER_IN_DIALOGS = u'C:\\'
|
||||
INITIAL_FOLDER_IN_DIALOGS = 'C:\\'
|
||||
HELP_PATH = 'help'
|
||||
|
||||
@@ -16,11 +16,12 @@ class Preferences(PreferencesBase):
|
||||
PreferencesBase.__init__(self)
|
||||
self.reset_columns()
|
||||
|
||||
def _load_specific(self, settings, get):
|
||||
def _load_specific(self, settings):
|
||||
# load prefs specific to the dg edition
|
||||
pass
|
||||
|
||||
def _load_values(self, settings, get):
|
||||
def _load_values(self, settings):
|
||||
get = self.get_value
|
||||
self.filter_hardness = get('FilterHardness', self.filter_hardness)
|
||||
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
|
||||
self.use_regexp = get('UseRegexp', self.use_regexp)
|
||||
@@ -33,9 +34,15 @@ class Preferences(PreferencesBase):
|
||||
if width > 0:
|
||||
self.columns_width[index] = width
|
||||
self.columns_visible = get('ColumnsVisible', self.columns_visible)
|
||||
|
||||
self.mainWindowIsMaximized = get('MainWindowIsMaximized', self.mainWindowIsMaximized)
|
||||
self.mainWindowRect = self.get_rect('MainWindowRect', self.mainWindowRect)
|
||||
self.detailsWindowRect = self.get_rect('DetailsWindowRect', self.detailsWindowRect)
|
||||
self.directoriesWindowRect = self.get_rect('DirectoriesWindowRect', self.directoriesWindowRect)
|
||||
|
||||
self.registration_code = get('RegistrationCode', self.registration_code)
|
||||
self.registration_email = get('RegistrationEmail', self.registration_email)
|
||||
self._load_specific(settings, get)
|
||||
self._load_specific(settings)
|
||||
|
||||
def _reset_specific(self):
|
||||
# reset prefs specific to the dg edition
|
||||
@@ -48,6 +55,12 @@ class Preferences(PreferencesBase):
|
||||
self.remove_empty_folders = False
|
||||
self.destination_type = 1
|
||||
self.custom_command = ''
|
||||
|
||||
self.mainWindowIsMaximized = False
|
||||
self.mainWindowRect = None
|
||||
self.detailsWindowRect = None
|
||||
self.directoriesWindowRect = None
|
||||
|
||||
self.registration_code = ''
|
||||
self.registration_email = ''
|
||||
self._reset_specific()
|
||||
@@ -56,11 +69,12 @@ class Preferences(PreferencesBase):
|
||||
self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS]
|
||||
self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS]
|
||||
|
||||
def _save_specific(self, settings, set_):
|
||||
def _save_specific(self, settings):
|
||||
# save prefs specific to the dg edition
|
||||
pass
|
||||
|
||||
def _save_values(self, settings, set_):
|
||||
def _save_values(self, settings):
|
||||
set_ = self.set_value
|
||||
set_('FilterHardness', self.filter_hardness)
|
||||
set_('MixFileKind', self.mix_file_kind)
|
||||
set_('UseRegexp', self.use_regexp)
|
||||
@@ -69,7 +83,13 @@ class Preferences(PreferencesBase):
|
||||
set_('CustomCommand', self.custom_command)
|
||||
set_('ColumnsWidth', self.columns_width)
|
||||
set_('ColumnsVisible', self.columns_visible)
|
||||
|
||||
set_('MainWindowIsMaximized', self.mainWindowIsMaximized)
|
||||
self.set_rect('MainWindowRect', self.mainWindowRect)
|
||||
self.set_rect('DetailsWindowRect', self.detailsWindowRect)
|
||||
self.set_rect('DirectoriesWindowRect', self.directoriesWindowRect)
|
||||
|
||||
set_('RegistrationCode', self.registration_code)
|
||||
set_('RegistrationEmail', self.registration_email)
|
||||
self._save_specific(settings, set_)
|
||||
self._save_specific(settings)
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class ResultsModel(TreeModel):
|
||||
return True
|
||||
if role == Qt.EditRole:
|
||||
if index.column() == 0:
|
||||
value = unicode(value.toString())
|
||||
value = str(value.toString())
|
||||
return self.model.rename_selected(value)
|
||||
return False
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class DupeGuru(DupeGuruBase):
|
||||
LOGO_NAME = 'logo_me'
|
||||
NAME = 'dupeGuru Music Edition'
|
||||
VERSION = '5.8.1'
|
||||
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
|
||||
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7])
|
||||
|
||||
def __init__(self):
|
||||
DupeGuruBase.__init__(self, data, appid=1)
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
||||
from core.scanner import ScanType
|
||||
|
||||
from base.preferences import Preferences as PreferencesBase
|
||||
|
||||
@@ -21,7 +20,6 @@ class Preferences(PreferencesBase):
|
||||
(50, True), # Bitrate
|
||||
(60, False), # Sample Rate
|
||||
(40, False), # Kind
|
||||
(120, False), # creation
|
||||
(120, False), # modification
|
||||
(120, False), # Title
|
||||
(120, False), # Artist
|
||||
@@ -35,7 +33,8 @@ class Preferences(PreferencesBase):
|
||||
(80, False), # dupe count
|
||||
]
|
||||
|
||||
def _load_specific(self, settings, get):
|
||||
def _load_specific(self, settings):
|
||||
get = self.get_value
|
||||
self.scan_type = get('ScanType', self.scan_type)
|
||||
self.word_weighting = get('WordWeighting', self.word_weighting)
|
||||
self.match_similar = get('MatchSimilar', self.match_similar)
|
||||
@@ -48,7 +47,7 @@ class Preferences(PreferencesBase):
|
||||
|
||||
def _reset_specific(self):
|
||||
self.filter_hardness = 80
|
||||
self.scan_type = SCAN_TYPE_TAG
|
||||
self.scan_type = ScanType.Tag
|
||||
self.word_weighting = True
|
||||
self.match_similar = False
|
||||
self.scan_tag_track = False
|
||||
@@ -58,7 +57,8 @@ class Preferences(PreferencesBase):
|
||||
self.scan_tag_genre = False
|
||||
self.scan_tag_year = False
|
||||
|
||||
def _save_specific(self, settings, set_):
|
||||
def _save_specific(self, settings):
|
||||
set_ = self.set_value
|
||||
set_('ScanType', self.scan_type)
|
||||
set_('WordWeighting', self.word_weighting)
|
||||
set_('MatchSimilar', self.match_similar)
|
||||
|
||||
@@ -9,19 +9,18 @@
|
||||
from PyQt4.QtCore import SIGNAL, Qt
|
||||
from PyQt4.QtGui import QDialog, QDialogButtonBox
|
||||
|
||||
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
||||
from core.scanner import ScanType
|
||||
|
||||
from preferences_dialog_ui import Ui_PreferencesDialog
|
||||
import preferences
|
||||
|
||||
SCAN_TYPE_ORDER = [
|
||||
SCAN_TYPE_FILENAME,
|
||||
SCAN_TYPE_FIELDS,
|
||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG,
|
||||
SCAN_TYPE_CONTENT,
|
||||
SCAN_TYPE_CONTENT_AUDIO,
|
||||
ScanType.Filename,
|
||||
ScanType.Fields,
|
||||
ScanType.FieldsNoOrder,
|
||||
ScanType.Tag,
|
||||
ScanType.Contents,
|
||||
ScanType.ContentsAudio,
|
||||
]
|
||||
|
||||
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
@@ -76,7 +75,7 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
prefs.use_regexp = ischecked(self.useRegexpBox)
|
||||
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
|
||||
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
|
||||
prefs.custom_command = unicode(self.customCommandEdit.text())
|
||||
prefs.custom_command = str(self.customCommandEdit.text())
|
||||
|
||||
def resetToDefaults(self):
|
||||
self.load(preferences.Preferences())
|
||||
@@ -89,9 +88,9 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
|
||||
def scanTypeChanged(self, index):
|
||||
scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
|
||||
word_based = scan_type in [SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
||||
SCAN_TYPE_TAG]
|
||||
tag_based = scan_type == SCAN_TYPE_TAG
|
||||
word_based = scan_type in (ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder,
|
||||
ScanType.Tag)
|
||||
tag_based = scan_type == ScanType.Tag
|
||||
self.filterHardnessSlider.setEnabled(word_based)
|
||||
self.matchSimilarBox.setEnabled(word_based)
|
||||
self.wordWeightingBox.setEnabled(word_based)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import sys
|
||||
import sip
|
||||
sip.setapi('QVariant', 1)
|
||||
|
||||
from PyQt4.QtCore import QCoreApplication
|
||||
from PyQt4.QtGui import QApplication, QIcon, QPixmap
|
||||
|
||||
19
qt/pe/app.py
19
qt/pe/app.py
@@ -9,8 +9,7 @@
|
||||
import os.path as op
|
||||
import logging
|
||||
|
||||
from PyQt4.QtGui import QImage
|
||||
import PIL.Image
|
||||
from PyQt4.QtGui import QImage, QImageReader
|
||||
|
||||
from hsutil.str import get_file_ext
|
||||
|
||||
@@ -41,14 +40,18 @@ class File(fs.File):
|
||||
fs.File._read_info(self, field)
|
||||
if field == 'dimensions':
|
||||
try:
|
||||
im = PIL.Image.open(unicode(self.path))
|
||||
self.dimensions = im.size
|
||||
except IOError:
|
||||
ir = QImageReader(str(self.path))
|
||||
size = ir.size()
|
||||
if size.isValid():
|
||||
self.dimensions = (size.width(), size.height())
|
||||
else:
|
||||
self.dimensions = (0, 0)
|
||||
except EnvironmentError:
|
||||
self.dimensions = (0, 0)
|
||||
logging.warning(u"Could not read image '%s'", unicode(self.path))
|
||||
logging.warning("Could not read image '%s'", str(self.path))
|
||||
|
||||
def get_blocks(self, block_count_per_side):
|
||||
image = QImage(unicode(self.path))
|
||||
image = QImage(str(self.path))
|
||||
image = image.convertToFormat(QImage.Format_RGB888)
|
||||
return getblocks(image, block_count_per_side)
|
||||
|
||||
@@ -58,7 +61,7 @@ class DupeGuru(DupeGuruBase):
|
||||
LOGO_NAME = 'logo_pe'
|
||||
NAME = 'dupeGuru Picture Edition'
|
||||
VERSION = '1.9.1'
|
||||
DELTA_COLUMNS = frozenset([2, 5, 6])
|
||||
DELTA_COLUMNS = frozenset([2, 5])
|
||||
|
||||
def __init__(self):
|
||||
DupeGuruBase.__init__(self, data_pe, appid=5)
|
||||
|
||||
@@ -28,11 +28,11 @@ class DetailsDialog(DetailsDialogBase, Ui_DetailsDialog):
|
||||
group = self.app.results.get_group_of_duplicate(dupe)
|
||||
ref = group.ref
|
||||
|
||||
self.selectedPixmap = QPixmap(unicode(dupe.path))
|
||||
self.selectedPixmap = QPixmap(str(dupe.path))
|
||||
if ref is dupe:
|
||||
self.referencePixmap = None
|
||||
else:
|
||||
self.referencePixmap = QPixmap(unicode(ref.path))
|
||||
self.referencePixmap = QPixmap(str(ref.path))
|
||||
self._updateImages()
|
||||
|
||||
def _updateImages(self):
|
||||
|
||||
25
qt/pe/gen.py
25
qt/pe/gen.py
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-05-22
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import os
|
||||
import os.path as op
|
||||
|
||||
def move(src, dst):
|
||||
if not op.exists(src):
|
||||
return
|
||||
if op.exists(dst):
|
||||
os.remove(dst)
|
||||
print 'Moving %s --> %s' % (src, dst)
|
||||
os.rename(src, dst)
|
||||
|
||||
os.chdir('modules')
|
||||
os.system('python setup.py build_ext --inplace')
|
||||
os.chdir('..')
|
||||
move(op.join('modules', '_block.so'), op.join('.', '_block.so'))
|
||||
move(op.join('modules', '_block.pyd'), op.join('.', '_block.pyd'))
|
||||
@@ -40,14 +40,14 @@ getblock(PyObject *image, int width, int height)
|
||||
int i;
|
||||
|
||||
pi = PyObject_CallMethod(image, "bytesPerLine", NULL);
|
||||
bytes_per_line = PyInt_AsSsize_t(pi);
|
||||
bytes_per_line = PyLong_AsLong(pi);
|
||||
Py_DECREF(pi);
|
||||
|
||||
sipptr = PyObject_CallMethod(image, "bits", NULL);
|
||||
/* int(sipptr) returns the address of the pointer */
|
||||
pi = PyObject_CallMethod(sipptr, "__int__", NULL);
|
||||
Py_DECREF(sipptr);
|
||||
s = (char *)PyInt_AsSsize_t(pi);
|
||||
s = (char *)PyLong_AsLong(pi);
|
||||
Py_DECREF(pi);
|
||||
/* Qt aligns all its lines on 32bit, which means that if the number of bytes per
|
||||
* line for image is not divisible by 4, there's going to be crap inserted in "s"
|
||||
@@ -74,9 +74,9 @@ getblock(PyObject *image, int width, int height)
|
||||
blue /= pixel_count;
|
||||
}
|
||||
|
||||
pred = PyInt_FromSsize_t(red);
|
||||
pgreen = PyInt_FromSsize_t(green);
|
||||
pblue = PyInt_FromSsize_t(blue);
|
||||
pred = PyLong_FromLong(red);
|
||||
pgreen = PyLong_FromLong(green);
|
||||
pblue = PyLong_FromLong(blue);
|
||||
result = PyTuple_Pack(3, pred, pgreen, pblue);
|
||||
Py_DECREF(pred);
|
||||
Py_DECREF(pgreen);
|
||||
@@ -107,10 +107,10 @@ block_getblocks(PyObject *self, PyObject *args)
|
||||
}
|
||||
|
||||
pi = PyObject_CallMethod(image, "width", NULL);
|
||||
width = PyInt_AsSsize_t(pi);
|
||||
width = PyLong_AsLong(pi);
|
||||
Py_DECREF(pi);
|
||||
pi = PyObject_CallMethod(image, "height", NULL);
|
||||
height = PyInt_AsSsize_t(pi);
|
||||
height = PyLong_AsLong(pi);
|
||||
Py_DECREF(pi);
|
||||
|
||||
if (!(width && height)) {
|
||||
@@ -157,11 +157,24 @@ static PyMethodDef BlockMethods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
init_block(void)
|
||||
static struct PyModuleDef BlockDef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_block",
|
||||
NULL,
|
||||
-1,
|
||||
BlockMethods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyObject *
|
||||
PyInit__block(void)
|
||||
{
|
||||
PyObject *m = Py_InitModule("_block", BlockMethods);
|
||||
PyObject *m = PyModule_Create(&BlockDef);
|
||||
if (m == NULL) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
|
||||
setup(
|
||||
ext_modules = [Extension("_block", ["block.c"])]
|
||||
)
|
||||
@@ -18,19 +18,18 @@ class Preferences(PreferencesBase):
|
||||
(60, True), # size
|
||||
(40, False), # kind
|
||||
(100, True), # dimensions
|
||||
(120, False), # creation
|
||||
(120, False), # modification
|
||||
(60, True), # match %
|
||||
(80, False), # dupe count
|
||||
]
|
||||
|
||||
def _load_specific(self, settings, get):
|
||||
self.match_scaled = get('MatchScaled', self.match_scaled)
|
||||
def _load_specific(self, settings):
|
||||
self.match_scaled = self.get_value('MatchScaled', self.match_scaled)
|
||||
|
||||
def _reset_specific(self):
|
||||
self.filter_hardness = 95
|
||||
self.match_scaled = False
|
||||
|
||||
def _save_specific(self, settings, set_):
|
||||
set_('MatchScaled', self.match_scaled)
|
||||
def _save_specific(self, settings):
|
||||
self.set_value('MatchScaled', self.match_scaled)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
prefs.use_regexp = ischecked(self.useRegexpBox)
|
||||
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
|
||||
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
|
||||
prefs.custom_command = unicode(self.customCommandEdit.text())
|
||||
prefs.custom_command = str(self.customCommandEdit.text())
|
||||
|
||||
def resetToDefaults(self):
|
||||
self.load(preferences.Preferences())
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import sys
|
||||
import sip
|
||||
sip.setapi('QVariant', 1)
|
||||
|
||||
from PyQt4.QtCore import QCoreApplication
|
||||
from PyQt4.QtGui import QApplication, QIcon, QPixmap
|
||||
@@ -16,8 +18,6 @@ from app import DupeGuru
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import base.cxfreeze_fix
|
||||
# This is a workaround for a cxfreeze problem where compiled dupeguru can't read tiff files
|
||||
from PIL import TiffImagePlugin, TiffTags
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
@@ -27,8 +27,8 @@ class DupeGuru(DupeGuruBase):
|
||||
EDITION = 'se'
|
||||
LOGO_NAME = 'logo_se'
|
||||
NAME = 'dupeGuru'
|
||||
VERSION = '2.10.1'
|
||||
DELTA_COLUMNS = frozenset([2, 4, 5])
|
||||
VERSION = '2.11.0'
|
||||
DELTA_COLUMNS = frozenset([2, 4])
|
||||
|
||||
def __init__(self):
|
||||
DupeGuruBase.__init__(self, data, appid=4)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from core.scanner import SCAN_TYPE_FILENAME, SCAN_TYPE_CONTENT
|
||||
from core.scanner import ScanType
|
||||
|
||||
from base.preferences import Preferences as PreferencesBase
|
||||
|
||||
@@ -17,14 +17,14 @@ class Preferences(PreferencesBase):
|
||||
(180, True), # path
|
||||
(60, True), # size
|
||||
(40, False), # Kind
|
||||
(120, False), # creation
|
||||
(120, False), # modification
|
||||
(60, True), # match %
|
||||
(120, False), # Words Used
|
||||
(80, False), # dupe count
|
||||
]
|
||||
|
||||
def _load_specific(self, settings, get):
|
||||
def _load_specific(self, settings):
|
||||
get = self.get_value
|
||||
self.scan_type = get('ScanType', self.scan_type)
|
||||
self.word_weighting = get('WordWeighting', self.word_weighting)
|
||||
self.match_similar = get('MatchSimilar', self.match_similar)
|
||||
@@ -33,13 +33,14 @@ class Preferences(PreferencesBase):
|
||||
|
||||
def _reset_specific(self):
|
||||
self.filter_hardness = 80
|
||||
self.scan_type = SCAN_TYPE_CONTENT
|
||||
self.scan_type = ScanType.Contents
|
||||
self.word_weighting = True
|
||||
self.match_similar = False
|
||||
self.ignore_small_files = True
|
||||
self.small_file_threshold = 10 # KB
|
||||
|
||||
def _save_specific(self, settings, set_):
|
||||
def _save_specific(self, settings):
|
||||
set_ = self.set_value
|
||||
set_('ScanType', self.scan_type)
|
||||
set_('WordWeighting', self.word_weighting)
|
||||
set_('MatchSimilar', self.match_similar)
|
||||
|
||||
@@ -11,14 +11,14 @@ from PyQt4.QtGui import QDialog, QDialogButtonBox
|
||||
|
||||
from hsutil.misc import tryint
|
||||
|
||||
from core.scanner import SCAN_TYPE_FILENAME, SCAN_TYPE_CONTENT
|
||||
from core.scanner import ScanType
|
||||
|
||||
from preferences_dialog_ui import Ui_PreferencesDialog
|
||||
import preferences
|
||||
|
||||
SCAN_TYPE_ORDER = [
|
||||
SCAN_TYPE_FILENAME,
|
||||
SCAN_TYPE_CONTENT,
|
||||
ScanType.Filename,
|
||||
ScanType.Contents,
|
||||
]
|
||||
|
||||
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
@@ -48,7 +48,7 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
setchecked(self.useRegexpBox, prefs.use_regexp)
|
||||
setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
|
||||
setchecked(self.ignoreSmallFilesBox, prefs.ignore_small_files)
|
||||
self.sizeThresholdEdit.setText(unicode(prefs.small_file_threshold))
|
||||
self.sizeThresholdEdit.setText(str(prefs.small_file_threshold))
|
||||
self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
|
||||
self.customCommandEdit.setText(prefs.custom_command)
|
||||
|
||||
@@ -65,7 +65,7 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox)
|
||||
prefs.small_file_threshold = tryint(self.sizeThresholdEdit.text())
|
||||
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
|
||||
prefs.custom_command = unicode(self.customCommandEdit.text())
|
||||
prefs.custom_command = str(self.customCommandEdit.text())
|
||||
|
||||
def resetToDefaults(self):
|
||||
self.load(preferences.Preferences())
|
||||
@@ -78,7 +78,7 @@ class PreferencesDialog(QDialog, Ui_PreferencesDialog):
|
||||
|
||||
def scanTypeChanged(self, index):
|
||||
scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
|
||||
word_based = scan_type == SCAN_TYPE_FILENAME
|
||||
word_based = scan_type == ScanType.Filename
|
||||
self.filterHardnessSlider.setEnabled(word_based)
|
||||
self.matchSimilarBox.setEnabled(word_based)
|
||||
self.wordWeightingBox.setEnabled(word_based)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
@@ -6,18 +6,25 @@
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import sip
|
||||
sip.setapi('QVariant', 1)
|
||||
|
||||
from PyQt4.QtCore import QCoreApplication
|
||||
from PyQt4.QtGui import QApplication, QIcon, QPixmap
|
||||
|
||||
import base.dg_rc
|
||||
|
||||
if sys.platform == 'linux2':
|
||||
# Under Python3, we have to add 'base' to pythonpath because UI files don't use
|
||||
# relative imports.
|
||||
sys.path.append(os.path.dirname(base.dg_rc.__file__))
|
||||
|
||||
from app import DupeGuru
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import base.cxfreeze_fix
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setWindowIcon(QIcon(QPixmap(":/logo_se")))
|
||||
@@ -25,4 +32,4 @@ if __name__ == "__main__":
|
||||
QCoreApplication.setApplicationName(DupeGuru.NAME)
|
||||
QCoreApplication.setApplicationVersion(DupeGuru.VERSION)
|
||||
dgapp = DupeGuru()
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec_())
|
||||
|
||||
5
run.py
5
run.py
@@ -20,7 +20,7 @@ def main():
|
||||
edition = conf['edition']
|
||||
ui = conf['ui']
|
||||
dev = conf['dev']
|
||||
print "Running dupeGuru {0} with UI {1}".format(edition.upper(), ui)
|
||||
print("Running dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||
if ui == 'cocoa':
|
||||
subfolder = 'dev' if dev else 'release'
|
||||
app_path = {
|
||||
@@ -32,8 +32,9 @@ def main():
|
||||
elif ui == 'qt':
|
||||
add_to_pythonpath('.')
|
||||
add_to_pythonpath('qt')
|
||||
add_to_pythonpath(op.join('qt', 'base'))
|
||||
os.chdir(op.join('qt', edition))
|
||||
os.system('python start.py')
|
||||
os.system('{0} start.py'.format(sys.executable))
|
||||
os.chdir('..')
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user