mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 16:11:39 +00:00
Compare commits
149 Commits
me5.7.2
...
before-fai
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05b79f81af | ||
|
|
96ef2f2dd3 | ||
|
|
2542af17b6 | ||
|
|
c86bc649ff | ||
|
|
4b8e48ed88 | ||
|
|
a1addfd416 | ||
|
|
a1a57d8933 | ||
|
|
864970b860 | ||
|
|
a056be0842 | ||
|
|
c672e75739 | ||
|
|
7b5dd3f964 | ||
|
|
a6072f608b | ||
|
|
06462c65a5 | ||
|
|
359f9c0680 | ||
|
|
01db7c4948 | ||
|
|
f67f14a78d | ||
|
|
0a64d653e1 | ||
|
|
456a835285 | ||
|
|
0d8ed92a68 | ||
|
|
9bd093a03c | ||
|
|
361d4698a9 | ||
|
|
b342b15011 | ||
|
|
95638a3a80 | ||
|
|
2204fe3355 | ||
|
|
abcd774c9d | ||
|
|
ee209f8f88 | ||
|
|
b1f2e1c191 | ||
|
|
33f372f6c6 | ||
|
|
8e5c2a8875 | ||
|
|
36f3638ae4 | ||
|
|
d10210011f | ||
|
|
e867840d81 | ||
|
|
fb7e3189a8 | ||
|
|
5733c0143b | ||
|
|
ac4881f231 | ||
|
|
939efd7dab | ||
|
|
a93d96d742 | ||
|
|
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 | ||
|
|
09f73988b3 | ||
|
|
9e6f289319 | ||
|
|
d2a55ffd31 | ||
|
|
793c2aa423 | ||
|
|
5daa332b6c | ||
|
|
d5511a857c | ||
|
|
7fecd21331 | ||
|
|
88b79e512f | ||
|
|
853bf63777 | ||
|
|
ff16fea54a | ||
|
|
a03e2a69d4 | ||
|
|
56a39df635 | ||
|
|
ac1593ff75 | ||
|
|
4d66b4667c | ||
|
|
fdde538b66 | ||
|
|
de1147219c | ||
|
|
371426a08e | ||
|
|
75eb005ba0 | ||
|
|
601b67145c | ||
|
|
c65afbc057 | ||
|
|
378589a473 | ||
|
|
fa264972a4 | ||
|
|
6b10e01c03 | ||
|
|
5a6d74ab37 | ||
|
|
73f1bb6968 | ||
|
|
d1a7f51859 | ||
|
|
2ae16396a6 | ||
|
|
ef090a5dc5 | ||
|
|
5c0799e82b | ||
|
|
fa2ee01d3f | ||
|
|
d6ba80bd3f | ||
|
|
ee96d5f88c | ||
|
|
e96a917bef | ||
|
|
769b816998 | ||
|
|
ff891c210c | ||
|
|
3ed5e1bf95 | ||
|
|
5bc8581389 | ||
|
|
7346b422d5 | ||
|
|
5c80ac1c74 | ||
|
|
699023992c | ||
|
|
454ce604ad | ||
|
|
1e0f6bfecb | ||
|
|
7f10aa3de2 | ||
|
|
f8764ab85e | ||
|
|
aa8544308e | ||
|
|
31fc70e0f8 | ||
|
|
a16af4560b | ||
|
|
0782ba0dab | ||
|
|
83725667a4 | ||
|
|
f4b3163b04 | ||
|
|
6cd745f429 | ||
|
|
6131f7f6bf | ||
|
|
dd4faa030f | ||
|
|
ab8691f5ac | ||
|
|
77ab073cdb | ||
|
|
87e0011525 | ||
|
|
7af3bb7226 | ||
|
|
5573352ce6 | ||
|
|
e6486e08ab | ||
|
|
48badaa927 | ||
|
|
2f13bf677e | ||
|
|
e63abc1b4b | ||
|
|
88334acdef | ||
|
|
0491aa9f6e | ||
|
|
5be76d7c0f | ||
|
|
3b510389fc | ||
|
|
32d88e9249 | ||
|
|
7b1a1ff4bb | ||
|
|
19beb919d0 | ||
|
|
ba09e8bf4d | ||
|
|
26dd2d0e8e | ||
|
|
69b15d58a2 | ||
|
|
ba68789fb9 | ||
|
|
47a6ceffbc | ||
|
|
b17ca66f73 | ||
|
|
93bc609026 | ||
|
|
3ea51c2e15 | ||
|
|
1d9897ea60 | ||
|
|
b6cb00bc79 | ||
|
|
6dd53c6bfd | ||
|
|
07df5126b3 | ||
|
|
47b38c7d45 | ||
|
|
0e97bec7b2 |
21
.hgtags
21
.hgtags
@@ -11,3 +11,24 @@ cbcf9c80fee4c908ef2efbf1c143c9e47676c9b2 pe1.8.0
|
|||||||
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
|
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
|
||||||
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
|
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
|
||||||
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3
|
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3
|
||||||
|
90ed56ee602666db2f267f73eac6f824347039b5 me5.7.2
|
||||||
|
4c3cb1e671a333eabde1151c7c6ffb3609cab025 pe1.8.4
|
||||||
|
0a71306434bca51bea9a5d5ae54fe1bf0e4900d8 pe1.8.5
|
||||||
|
556baf4a410779e9bbf43129de133e4c4b26d679 pe1.8.6
|
||||||
|
9149024283959a50fe9a47a5f175b905d1672c19 se2.10.0
|
||||||
|
388a7e5aef6385e515189f4a15b4c4fed3ae2fcf me5.8.0
|
||||||
|
27501167e3b9262ecb60c967941294f36d77eb25 pe1.9.0
|
||||||
|
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||||
|
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||||
|
f71d405e62badcfdc1b037facaac043cece40ee5 se2.10.1
|
||||||
|
3742e83edd9eadf44e1a501859f5e2462b1ef6fd me5.8.1
|
||||||
|
724ff565dd785fb739774588c6ee652cfc0612d5 pe1.9.1
|
||||||
|
634b66415c6529f46ae4f837318027cc9d70c3b5 before-py3k
|
||||||
|
2b67955db2b0580a8b0854dc918b6ab0d1fa3b88 se2.11.0
|
||||||
|
b56fe4dd8c95bca270b078a09e86848df77e2b2d me5.9.0
|
||||||
|
618a7365457d56fdc6920c70843a244762e2ea00 pe1.10.0
|
||||||
|
95b3a4b564c6222b414f2b40182dde2bd6d0e8a4 me5.9.1
|
||||||
|
9735a5218d2b5b3b1e1dfe17f2f874177cf8f61c se2.11.1
|
||||||
|
dbfee3ee2fa5cbb9e7ab36570659c17cd5b8561f se2.12.0
|
||||||
|
d3fe0d0dcda1e0bf1100d02f117503d3bf6baacf me5.10.0
|
||||||
|
b07ac1398703dd358912c1f3d20bd995633db9fe pe1.11.0
|
||||||
|
|||||||
27
README
27
README
@@ -12,9 +12,7 @@ This package contains the source for dupeGuru. To learns how to build it, refer
|
|||||||
There are also other sub-folder that comes from external repositories (automatically checked out
|
There are also other sub-folder that comes from external repositories (automatically checked out
|
||||||
with svn:externals):
|
with svn:externals):
|
||||||
|
|
||||||
- hsutil: A collection of helpers used across HS applications.
|
- hscommon: A collection of helpers used across HS applications.
|
||||||
- hsdocgen: An ad-hoc document generation used across HS project (used for help files)
|
|
||||||
- hsmedia: A library to read audio file metadata, used in dupeGuru ME.
|
|
||||||
- cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
|
- cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
|
||||||
- qtlib: A collection of helpers used across Qt UI codebases of HS applications.
|
- qtlib: A collection of helpers used across Qt UI codebases of HS applications.
|
||||||
|
|
||||||
@@ -26,26 +24,28 @@ Before being able to build dupeGuru, a few dependencies have to be installed:
|
|||||||
General dependencies
|
General dependencies
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- Python 2.6 (http://www.python.org)
|
- Python 3.1 (http://www.python.org)
|
||||||
- Mako, to generate help files. (http://www.makotemplates.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/)
|
- PyYaml, for help files and the build system. (http://pyyaml.org/)
|
||||||
- Nose, to run unit tests. (http://somethingaboutorange.com/mrl/projects/nose/)
|
- py.test, to run unit tests. (http://codespeak.net/py/dist/test/)
|
||||||
|
|
||||||
OS X prerequisites
|
OS X prerequisites
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- XCode 3.1 (http://developer.apple.com/TOOLS/xcode/)
|
- XCode 3.1 (http://developer.apple.com/TOOLS/xcode/)
|
||||||
- Sparkle (http://sparkle.andymatuschak.org/)
|
- Sparkle (http://sparkle.andymatuschak.org/)
|
||||||
- PyObjC 2.2. (http://pyobjc.sourceforge.net/)
|
- PyObjC 2.3. (http://pyobjc.sourceforge.net/)
|
||||||
- py2app (http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html)
|
- py2app 0.5.4 (http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html)
|
||||||
|
|
||||||
Windows prerequisites
|
Windows prerequisites
|
||||||
---
|
---
|
||||||
|
|
||||||
- Visual Studio 2008 (Express is enough) is needed to build C extensions. (http://www.microsoft.com/Express/)
|
- 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)
|
- PyQt 4.7.5 (http://www.riverbankcomputing.co.uk/news)
|
||||||
- Python Imaging Library for dupeGuru PE. (http://www.pythonware.com/products/pil/)
|
- 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/)
|
||||||
- PyInstaller, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://www.pyinstaller.org/)
|
|
||||||
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
|
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
|
||||||
|
|
||||||
Building dupeGuru
|
Building dupeGuru
|
||||||
@@ -65,8 +65,3 @@ Then, just build the thing and then run it with:
|
|||||||
If you want to create ready-to-upload package, run:
|
If you want to create ready-to-upload package, run:
|
||||||
|
|
||||||
python package.py
|
python package.py
|
||||||
|
|
||||||
64-bit on OS X
|
|
||||||
---
|
|
||||||
|
|
||||||
The "release" configuration of dupeGuru's XCode project build with archs "i386 x86_64 ppc". However there are currently problems with py2app and 64 bit. If you want to correctly build 64-bit apps, refer to http://www.hardcoded.net/articles/building-64-bit-pyobjc-applications-with-py2app.htm .
|
|
||||||
75
build.py
75
build.py
@@ -13,27 +13,28 @@ import os.path as op
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
from distutils.extension import Extension
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from hsdocgen import generate_help, filters
|
from hscommon import helpgen
|
||||||
from hsutil.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages
|
from hscommon.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages
|
||||||
|
|
||||||
def build_cocoa(edition, dev, help_destpath):
|
def build_cocoa(edition, dev, help_destpath):
|
||||||
if not dev:
|
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))
|
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'):
|
if op.exists('build'):
|
||||||
shutil.rmtree('build')
|
shutil.rmtree('build')
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
if not dev:
|
if not dev:
|
||||||
specific_packages = {
|
specific_packages = {
|
||||||
'se': ['core_se'],
|
'se': ['core_se'],
|
||||||
'me': ['core_me', 'hsmedia'],
|
'me': ['core_me'],
|
||||||
'pe': ['core_pe'],
|
'pe': ['core_pe'],
|
||||||
}[edition]
|
}[edition]
|
||||||
copy_packages(['core', 'hsutil'] + specific_packages, 'build')
|
copy_packages(['core', 'hscommon'] + specific_packages, 'build')
|
||||||
cocoa_project_path = 'cocoa/{0}'.format(edition)
|
cocoa_project_path = 'cocoa/{0}'.format(edition)
|
||||||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
||||||
os.chdir('build')
|
os.chdir('build')
|
||||||
@@ -54,7 +55,7 @@ def build_cocoa(edition, dev, help_destpath):
|
|||||||
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
|
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
|
||||||
open(pthpath, 'w').write(op.abspath('.'))
|
open(pthpath, 'w').write(op.abspath('.'))
|
||||||
os.chdir(cocoa_project_path)
|
os.chdir(cocoa_project_path)
|
||||||
print "Building the XCode project"
|
print("Building the XCode project")
|
||||||
args = []
|
args = []
|
||||||
if dev:
|
if dev:
|
||||||
args.append('-configuration dev')
|
args.append('-configuration dev')
|
||||||
@@ -65,38 +66,68 @@ def build_cocoa(edition, dev, help_destpath):
|
|||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
|
|
||||||
def build_qt(edition, dev):
|
def build_qt(edition, dev):
|
||||||
|
print("Building Qt stuff")
|
||||||
build_all_qt_ui(op.join('qtlib', 'ui'))
|
build_all_qt_ui(op.join('qtlib', 'ui'))
|
||||||
build_all_qt_ui(op.join('qt', 'base'))
|
build_all_qt_ui(op.join('qt', 'base'))
|
||||||
build_all_qt_ui(op.join('qt', edition))
|
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')))
|
print_and_do("pyrcc4 -py3 {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))
|
def build_pe_modules(ui):
|
||||||
os.system('python gen.py')
|
def move(src, dst):
|
||||||
os.chdir(op.join('..', '..'))
|
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_qt.so'))
|
||||||
|
move('_block_qt.pyd', op.join('qt', 'pe', '_block_qt.pyd'))
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conf = yaml.load(open('conf.yaml'))
|
conf = yaml.load(open('conf.yaml'))
|
||||||
edition = conf['edition']
|
edition = conf['edition']
|
||||||
ui = conf['ui']
|
ui = conf['ui']
|
||||||
dev = conf['dev']
|
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:
|
if dev:
|
||||||
print "Building in Dev mode"
|
print("Building in Dev mode")
|
||||||
add_to_pythonpath('.')
|
add_to_pythonpath('.')
|
||||||
print "Generating Help"
|
print("Generating Help")
|
||||||
windows = sys.platform == 'win32'
|
windows = sys.platform == 'win32'
|
||||||
tix = filters.tixgen("https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}")
|
profile = 'win_en' if windows else 'osx_en'
|
||||||
help_dir = 'help_{0}'.format(edition)
|
help_dir = 'help_{0}'.format(edition)
|
||||||
dest_dir = 'dupeguru_{0}_help'.format(edition) if edition != 'se' else 'dupeguru_help'
|
dest_dir = 'dupeguru_{0}_help'.format(edition) if edition != 'se' else 'dupeguru_help'
|
||||||
help_basepath = op.abspath(help_dir)
|
help_basepath = op.abspath(help_dir)
|
||||||
help_destpath = op.abspath(op.join(help_dir, dest_dir))
|
help_destpath = op.abspath(op.join(help_dir, dest_dir))
|
||||||
generate_help.main(help_basepath, help_destpath, force_render=not dev, tix=tix, windows=windows)
|
helpgen.gen(help_basepath, help_destpath, profile=profile)
|
||||||
|
print("Building dupeGuru")
|
||||||
print "Building dupeGuru"
|
|
||||||
if edition == 'pe':
|
if edition == 'pe':
|
||||||
os.chdir('core_pe')
|
build_pe_modules(ui)
|
||||||
os.system('python gen.py')
|
|
||||||
os.chdir('..')
|
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
build_cocoa(edition, dev, help_destpath)
|
build_cocoa(edition, dev, help_destpath)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
[op setCanChooseFiles:YES];
|
[op setCanChooseFiles:YES];
|
||||||
[op setCanChooseDirectories:YES];
|
[op setCanChooseDirectories:YES];
|
||||||
[op setAllowsMultipleSelection:NO];
|
[op setAllowsMultipleSelection:YES];
|
||||||
[op setTitle:@"Select a directory to add to the scanning list"];
|
[op setTitle:@"Select a directory to add to the scanning list"];
|
||||||
[op setDelegate:self];
|
[op setDelegate:self];
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
if ([op runModal] == NSOKButton) {
|
||||||
{
|
for (NSString *directory in [op filenames]) {
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
[self addDirectory:directory];
|
||||||
[self addDirectory:directory];
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,18 +95,14 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
{
|
{
|
||||||
NSInteger r = [[_py addDirectory:directory] intValue];
|
NSInteger r = [[_py addDirectory:directory] intValue];
|
||||||
if (r) {
|
if (r) {
|
||||||
NSString *m;
|
NSString *m = @"";
|
||||||
switch (r) {
|
if (r == 1) {
|
||||||
case 1: {
|
m = @"'%@' already is in the list.";
|
||||||
m = @"This directory already is in the list.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
m = @"This directory does not exist.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[Dialogs showMessage:m];
|
else if (r == 2) {
|
||||||
|
m = @"'%@' does not exist.";
|
||||||
|
}
|
||||||
|
[Dialogs showMessage:[NSString stringWithFormat:m,directory]];
|
||||||
}
|
}
|
||||||
[_recentDirectories addDirectory:directory];
|
[_recentDirectories addDirectory:directory];
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
|||||||
25
cocoa/base/ProblemDialog.h
Normal file
25
cocoa/base/ProblemDialog.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/hs_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "HSWindowController.h"
|
||||||
|
#import "PyApp.h"
|
||||||
|
#import "PyProblemDialog.h"
|
||||||
|
#import "HSTable.h"
|
||||||
|
|
||||||
|
@interface ProblemDialog : HSWindowController
|
||||||
|
{
|
||||||
|
IBOutlet NSTableView *problemTableView;
|
||||||
|
|
||||||
|
HSTable *problemTable;
|
||||||
|
}
|
||||||
|
- (id)initWithPy:(PyApp *)aPy;
|
||||||
|
- (PyProblemDialog *)py;
|
||||||
|
|
||||||
|
- (IBAction)revealSelected:(id)sender;
|
||||||
|
@end
|
||||||
40
cocoa/base/ProblemDialog.m
Normal file
40
cocoa/base/ProblemDialog.m
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/hs_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ProblemDialog.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation ProblemDialog
|
||||||
|
- (id)initWithPy:(PyApp *)aPy
|
||||||
|
{
|
||||||
|
self = [super initWithNibName:@"ProblemDialog" pyClassName:@"PyProblemDialog" pyParent:aPy];
|
||||||
|
[self window]; //So the detailsTable is initialized.
|
||||||
|
problemTable = [[HSTable alloc] initWithPyClassName:@"PyProblemTable" pyParent:[self py] view:problemTableView];
|
||||||
|
[self connect];
|
||||||
|
[problemTable connect];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[problemTable disconnect];
|
||||||
|
[self disconnect];
|
||||||
|
[problemTable release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyProblemDialog *)py
|
||||||
|
{
|
||||||
|
return (PyProblemDialog *)py;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)revealSelected:(id)sender
|
||||||
|
{
|
||||||
|
[[self py] revealSelected];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -14,12 +14,15 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (NSNumber *)addDirectory:(NSString *)name;
|
- (NSNumber *)addDirectory:(NSString *)name;
|
||||||
- (void)removeDirectory:(NSNumber *)index;
|
- (void)removeDirectory:(NSNumber *)index;
|
||||||
- (void)loadResults;
|
- (void)loadResults;
|
||||||
|
- (void)loadResultsFrom:(NSString *)filename;
|
||||||
- (void)saveResults;
|
- (void)saveResults;
|
||||||
|
- (void)saveResultsAs:(NSString *)filename;
|
||||||
- (void)loadIgnoreList;
|
- (void)loadIgnoreList;
|
||||||
- (void)saveIgnoreList;
|
- (void)saveIgnoreList;
|
||||||
- (void)clearIgnoreList;
|
- (void)clearIgnoreList;
|
||||||
- (void)purgeIgnoreList;
|
- (void)purgeIgnoreList;
|
||||||
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds;
|
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds;
|
||||||
|
- (void)invokeCommand:(NSString *)cmd;
|
||||||
|
|
||||||
- (NSNumber *)doScan;
|
- (NSNumber *)doScan;
|
||||||
|
|
||||||
@@ -36,17 +39,19 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
|
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
|
||||||
- (void)deleteMarked;
|
- (void)deleteMarked;
|
||||||
|
- (void)hardlinkMarked;
|
||||||
- (void)removeMarked;
|
- (void)removeMarked;
|
||||||
|
|
||||||
//Data
|
//Data
|
||||||
- (NSNumber *)getIgnoreListCount;
|
- (NSNumber *)getIgnoreListCount;
|
||||||
- (NSNumber *)getMarkCount;
|
- (NSNumber *)getMarkCount;
|
||||||
- (NSNumber *)getOperationalErrorCount;
|
- (BOOL)scanWasProblematic;
|
||||||
|
|
||||||
//Scanning options
|
//Scanning options
|
||||||
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
||||||
- (void)setMixFileKind:(NSNumber *)mix_file_kind;
|
- (void)setMixFileKind:(BOOL)mix_file_kind;
|
||||||
- (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp;
|
- (void)setEscapeFilterRegexp:(BOOL)escape_filter_regexp;
|
||||||
- (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders;
|
- (void)setRemoveEmptyFolders:(BOOL)remove_empty_folders;
|
||||||
|
- (void)setIgnoreHardlinkMatches:(BOOL)ignore_hardlink_matches;
|
||||||
- (void)setSizeThreshold:(NSInteger)size_threshold;
|
- (void)setSizeThreshold:(NSInteger)size_threshold;
|
||||||
@end
|
@end
|
||||||
|
|||||||
14
cocoa/base/PyProblemDialog.h
Normal file
14
cocoa/base/PyProblemDialog.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/hs_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyGUI.h"
|
||||||
|
|
||||||
|
@interface PyProblemDialog : PyGUI
|
||||||
|
- (void)revealSelected;
|
||||||
|
@end
|
||||||
@@ -7,18 +7,18 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "PyOutline.h"
|
#import "PyTable.h"
|
||||||
|
|
||||||
@interface PyResultTree : PyOutline
|
@interface PyResultTable : PyTable
|
||||||
- (BOOL)powerMarkerMode;
|
- (BOOL)powerMarkerMode;
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
||||||
- (BOOL)deltaValuesMode;
|
- (BOOL)deltaValuesMode;
|
||||||
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
||||||
|
|
||||||
- (NSString *)valueForPath:(NSArray *)aPath column:(NSInteger)aColumn;
|
- (NSString *)valueForRow:(NSInteger)rowIndex column:(NSInteger)aColumn;
|
||||||
- (BOOL)renameSelected:(NSString *)aNewName;
|
- (BOOL)renameSelected:(NSString *)aNewName;
|
||||||
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
|
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
|
||||||
- (void)markSelected;
|
- (void)markSelected;
|
||||||
- (void)removeSelected;
|
- (void)removeSelected;
|
||||||
- (NSArray *)rootChildrenCounts;
|
- (NSInteger)selectedDupeCount;
|
||||||
@end
|
@end
|
||||||
@@ -1,207 +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
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultOutline.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation ResultOutline
|
|
||||||
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView
|
|
||||||
{
|
|
||||||
self = [super initWithPyClassName:@"PyResultOutline" pyParent:aPyParent view:aOutlineView];
|
|
||||||
_rootChildrenCounts = nil;
|
|
||||||
[self connect];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[self disconnect];
|
|
||||||
[_deltaColumns release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (PyResultTree *)py
|
|
||||||
{
|
|
||||||
return (PyResultTree *)py;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (BOOL)powerMarkerMode
|
|
||||||
{
|
|
||||||
return [[self py] powerMarkerMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
|
||||||
{
|
|
||||||
[[self py] setPowerMarkerMode:aPowerMarkerMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)deltaValuesMode
|
|
||||||
{
|
|
||||||
return [[self py] deltaValuesMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
|
||||||
{
|
|
||||||
[[self py] setDeltaValuesMode:aDeltaValuesMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns
|
|
||||||
{
|
|
||||||
[_deltaColumns release];
|
|
||||||
_deltaColumns = [aDeltaColumns retain];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)selectedDupeCount
|
|
||||||
{
|
|
||||||
NSArray *selected = [self selectedIndexPaths];
|
|
||||||
if ([self powerMarkerMode]) {
|
|
||||||
return [selected count];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
NSInteger r = 0;
|
|
||||||
for (NSIndexPath *path in selected) {
|
|
||||||
if ([path length] == 2) {
|
|
||||||
r++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeSelected
|
|
||||||
{
|
|
||||||
NSInteger selectedDupeCount = [self selectedDupeCount];
|
|
||||||
if (!selectedDupeCount)
|
|
||||||
return;
|
|
||||||
NSString *msg = [NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",selectedDupeCount];
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[[self py] removeSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Datasource */
|
|
||||||
- (NSInteger)outlineView:(NSOutlineView *)aOutlineView numberOfChildrenOfItem:(id)item
|
|
||||||
{
|
|
||||||
NSIndexPath *path = item;
|
|
||||||
if ((path != nil) && ([path length] == 1)) {
|
|
||||||
if (_rootChildrenCounts == nil) {
|
|
||||||
_rootChildrenCounts = [[[self py] rootChildrenCounts] retain];
|
|
||||||
}
|
|
||||||
NSInteger index = [path indexAtPosition:0];
|
|
||||||
return n2i([_rootChildrenCounts objectAtIndex:index]);
|
|
||||||
}
|
|
||||||
return [super outlineView:aOutlineView numberOfChildrenOfItem:item];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item
|
|
||||||
{
|
|
||||||
NSIndexPath *path = item;
|
|
||||||
NSString *identifier = [column identifier];
|
|
||||||
if ([identifier isEqual:@"marked"]) {
|
|
||||||
return b2n([self boolProperty:@"marked" valueAtPath:path]);
|
|
||||||
}
|
|
||||||
NSInteger columnId = [identifier integerValue];
|
|
||||||
return [[self py] valueForPath:p2a(path) column:columnId];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)aOutlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
|
|
||||||
{
|
|
||||||
if ([[tableColumn identifier] isEqual:@"0"]) {
|
|
||||||
NSIndexPath *path = item;
|
|
||||||
NSString *oldName = [[self py] valueForPath:p2a(path) column:0];
|
|
||||||
NSString *newName = object;
|
|
||||||
if (![newName isEqual:oldName]) {
|
|
||||||
BOOL renamed = [[self py] renameSelected:newName];
|
|
||||||
if (!renamed) {
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.", newName]];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[self refreshItemAtPath:path];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[super outlineView:aOutlineView setObjectValue:object forTableColumn:tableColumn byItem:item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
- (void)outlineView:(NSOutlineView *)aOutlineView didClickTableColumn:(NSTableColumn *)tableColumn
|
|
||||||
{
|
|
||||||
if ([[outlineView sortDescriptors] count] < 1)
|
|
||||||
return;
|
|
||||||
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
|
|
||||||
[[self py] sortBy:[[sd key] integerValue] ascending:[sd ascending]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
||||||
{
|
|
||||||
NSIndexPath *path = item;
|
|
||||||
BOOL isMarkable = [self boolProperty:@"markable" valueAtPath:path];
|
|
||||||
if ([[tableColumn identifier] isEqual:@"marked"]) {
|
|
||||||
[cell setEnabled:isMarkable];
|
|
||||||
}
|
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
|
||||||
// Determine if the text color will be blue due to directory being reference.
|
|
||||||
NSTextFieldCell *textCell = cell;
|
|
||||||
if (isMarkable) {
|
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
|
||||||
}
|
|
||||||
if (([self deltaValuesMode]) && ([self powerMarkerMode] || ([path length] > 1))) {
|
|
||||||
NSInteger i = [[tableColumn identifier] integerValue];
|
|
||||||
if ([_deltaColumns containsIndex:i]) {
|
|
||||||
[textCell setTextColor:[NSColor orangeColor]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
|
||||||
{
|
|
||||||
[self removeSelected];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
|
||||||
{
|
|
||||||
[[self py] markSelected];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* don't calls saveEdits and cancelEdits */
|
|
||||||
- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
|
||||||
- (void)refresh /* Override */
|
|
||||||
{
|
|
||||||
[_rootChildrenCounts release];
|
|
||||||
_rootChildrenCounts = nil;
|
|
||||||
[super refresh];
|
|
||||||
[outlineView expandItem:nil expandChildren:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)invalidateMarkings
|
|
||||||
{
|
|
||||||
for (NSMutableDictionary *props in [itemData objectEnumerator]) {
|
|
||||||
[props removeObjectForKey:@"marked"];
|
|
||||||
}
|
|
||||||
[outlineView setNeedsDisplay:YES];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -7,15 +7,15 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "HSOutline.h"
|
#import "HSTable.h"
|
||||||
#import "PyResultTree.h"
|
#import "PyResultTable.h"
|
||||||
|
|
||||||
@interface ResultOutline : HSOutline
|
@interface ResultTable : HSTable
|
||||||
{
|
{
|
||||||
NSIndexSet *_deltaColumns;
|
NSIndexSet *_deltaColumns;
|
||||||
NSArray *_rootChildrenCounts;
|
|
||||||
}
|
}
|
||||||
- (PyResultTree *)py;
|
- (id)initWithPyParent:(id)aPyParent view:(NSTableView *)aTableView;
|
||||||
|
- (PyResultTable *)py;
|
||||||
- (BOOL)powerMarkerMode;
|
- (BOOL)powerMarkerMode;
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
||||||
- (BOOL)deltaValuesMode;
|
- (BOOL)deltaValuesMode;
|
||||||
162
cocoa/base/ResultTable.m
Normal file
162
cocoa/base/ResultTable.m
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
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 "ResultTable.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
|
||||||
|
@implementation ResultTable
|
||||||
|
- (id)initWithPyParent:(id)aPyParent view:(NSTableView *)aTableView
|
||||||
|
{
|
||||||
|
self = [super initWithPyClassName:@"PyResultTable" pyParent:aPyParent view:aTableView];
|
||||||
|
[self connect];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[self disconnect];
|
||||||
|
[_deltaColumns release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyResultTable *)py
|
||||||
|
{
|
||||||
|
return (PyResultTable *)py;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (BOOL)powerMarkerMode
|
||||||
|
{
|
||||||
|
return [[self py] powerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
||||||
|
{
|
||||||
|
[[self py] setPowerMarkerMode:aPowerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)deltaValuesMode
|
||||||
|
{
|
||||||
|
return [[self py] deltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
||||||
|
{
|
||||||
|
[[self py] setDeltaValuesMode:aDeltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns
|
||||||
|
{
|
||||||
|
[_deltaColumns release];
|
||||||
|
_deltaColumns = [aDeltaColumns retain];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)selectedDupeCount
|
||||||
|
{
|
||||||
|
return [[self py] selectedDupeCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeSelected
|
||||||
|
{
|
||||||
|
NSInteger selectedDupeCount = [self selectedDupeCount];
|
||||||
|
if (!selectedDupeCount)
|
||||||
|
return;
|
||||||
|
NSString *msg = [NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",selectedDupeCount];
|
||||||
|
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
||||||
|
return;
|
||||||
|
[[self py] removeSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datasource */
|
||||||
|
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
return [[self py] valueForColumn:@"marked" row:row];
|
||||||
|
}
|
||||||
|
NSInteger columnId = [identifier integerValue];
|
||||||
|
return [[self py] valueForRow:row column:columnId];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
[[self py] setValue:object forColumn:identifier row:row];
|
||||||
|
}
|
||||||
|
else if ([identifier isEqual:@"0"]) {
|
||||||
|
NSString *oldName = [[self py] valueForRow:row column:0];
|
||||||
|
NSString *newName = object;
|
||||||
|
if (![newName isEqual:oldName]) {
|
||||||
|
BOOL renamed = [[self py] renameSelected:newName];
|
||||||
|
if (!renamed) {
|
||||||
|
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.", newName]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[tableView setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
|
||||||
|
{
|
||||||
|
if ([[tableView sortDescriptors] count] < 1)
|
||||||
|
return;
|
||||||
|
NSSortDescriptor *sd = [[tableView sortDescriptors] objectAtIndex:0];
|
||||||
|
[[self py] sortBy:[[sd key] integerValue] ascending:[sd ascending]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
BOOL isMarkable = n2b([[self py] valueForColumn:@"markable" row:row]);
|
||||||
|
if ([[column identifier] isEqual:@"marked"]) {
|
||||||
|
[cell setEnabled:isMarkable];
|
||||||
|
// Low-tech solution, for indentation, but it works...
|
||||||
|
NSCellImagePosition pos = isMarkable ? NSImageRight : NSImageLeft;
|
||||||
|
[cell setImagePosition:pos];
|
||||||
|
}
|
||||||
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
|
// Determine if the text color will be blue due to directory being reference.
|
||||||
|
NSTextFieldCell *textCell = cell;
|
||||||
|
if (isMarkable) {
|
||||||
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[textCell setTextColor:[NSColor blueColor]];
|
||||||
|
if ([self deltaValuesMode]) {
|
||||||
|
NSInteger i = [[column identifier] integerValue];
|
||||||
|
if ([_deltaColumns containsIndex:i]) {
|
||||||
|
[textCell setTextColor:[NSColor orangeColor]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[self removeSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[[self py] markSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)invalidateMarkings
|
||||||
|
{
|
||||||
|
[tableView setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -7,9 +7,10 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "HSOutlineView.h"
|
|
||||||
#import "StatsLabel.h"
|
#import "StatsLabel.h"
|
||||||
#import "ResultOutline.h"
|
#import "ResultTable.h"
|
||||||
|
#import "ProblemDialog.h"
|
||||||
|
#import "HSTableView.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface ResultWindowBase : NSWindowController
|
@interface ResultWindowBase : NSWindowController
|
||||||
@@ -18,7 +19,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
IBOutlet PyDupeGuruBase *py;
|
IBOutlet PyDupeGuruBase *py;
|
||||||
IBOutlet id app;
|
IBOutlet id app;
|
||||||
IBOutlet NSSegmentedControl *deltaSwitch;
|
IBOutlet NSSegmentedControl *deltaSwitch;
|
||||||
IBOutlet HSOutlineView *matches;
|
IBOutlet HSTableView *matches;
|
||||||
IBOutlet NSSegmentedControl *pmSwitch;
|
IBOutlet NSSegmentedControl *pmSwitch;
|
||||||
IBOutlet NSTextField *stats;
|
IBOutlet NSTextField *stats;
|
||||||
IBOutlet NSMenu *columnsMenu;
|
IBOutlet NSMenu *columnsMenu;
|
||||||
@@ -26,8 +27,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
NSMutableArray *_resultColumns;
|
NSMutableArray *_resultColumns;
|
||||||
NSWindowController *preferencesPanel;
|
NSWindowController *preferencesPanel;
|
||||||
ResultOutline *outline;
|
ResultTable *table;
|
||||||
StatsLabel *statsLabel;
|
StatsLabel *statsLabel;
|
||||||
|
ProblemDialog *problemDialog;
|
||||||
}
|
}
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
- (void)fillColumnsMenu;
|
- (void)fillColumnsMenu;
|
||||||
@@ -36,6 +38,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (NSDictionary *)getColumnsWidth;
|
- (NSDictionary *)getColumnsWidth;
|
||||||
- (void)initResultColumns;
|
- (void)initResultColumns;
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
||||||
|
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted;
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
- (IBAction)clearIgnoreList:(id)sender;
|
||||||
@@ -43,9 +46,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)changePowerMarker:(id)sender;
|
- (IBAction)changePowerMarker:(id)sender;
|
||||||
- (IBAction)copyMarked:(id)sender;
|
- (IBAction)copyMarked:(id)sender;
|
||||||
- (IBAction)deleteMarked:(id)sender;
|
- (IBAction)deleteMarked:(id)sender;
|
||||||
|
- (IBAction)hardlinkMarked:(id)sender;
|
||||||
- (IBAction)exportToXHTML:(id)sender;
|
- (IBAction)exportToXHTML:(id)sender;
|
||||||
- (IBAction)filter:(id)sender;
|
- (IBAction)filter:(id)sender;
|
||||||
- (IBAction)ignoreSelected:(id)sender;
|
- (IBAction)ignoreSelected:(id)sender;
|
||||||
|
- (IBAction)invokeCustomCommand:(id)sender;
|
||||||
|
- (IBAction)loadResults:(id)sender;
|
||||||
- (IBAction)markAll:(id)sender;
|
- (IBAction)markAll:(id)sender;
|
||||||
- (IBAction)markInvert:(id)sender;
|
- (IBAction)markInvert:(id)sender;
|
||||||
- (IBAction)markNone:(id)sender;
|
- (IBAction)markNone:(id)sender;
|
||||||
@@ -58,6 +64,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)renameSelected:(id)sender;
|
- (IBAction)renameSelected:(id)sender;
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender;
|
- (IBAction)resetColumnsToDefault:(id)sender;
|
||||||
- (IBAction)revealSelected:(id)sender;
|
- (IBAction)revealSelected:(id)sender;
|
||||||
|
- (IBAction)saveResults:(id)sender;
|
||||||
- (IBAction)showPreferencesPanel:(id)sender;
|
- (IBAction)showPreferencesPanel:(id)sender;
|
||||||
- (IBAction)startDuplicateScan:(id)sender;
|
- (IBAction)startDuplicateScan:(id)sender;
|
||||||
- (IBAction)switchSelected:(id)sender;
|
- (IBAction)switchSelected:(id)sender;
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)awakeFromNib
|
- (void)awakeFromNib
|
||||||
{
|
{
|
||||||
[self window];
|
[self window];
|
||||||
|
/* Put a cute iTunes-like bottom bar */
|
||||||
|
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
|
||||||
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
||||||
outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
|
table = [[ResultTable alloc] initWithPyParent:py view:matches];
|
||||||
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
|
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
|
||||||
|
problemDialog = [[ProblemDialog alloc] initWithPy:py];
|
||||||
[self initResultColumns];
|
[self initResultColumns];
|
||||||
[self fillColumnsMenu];
|
[self fillColumnsMenu];
|
||||||
[deltaSwitch setSelectedSegment:0];
|
[deltaSwitch setSelectedSegment:0];
|
||||||
@@ -36,8 +39,10 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[outline release];
|
[table release];
|
||||||
[preferencesPanel release];
|
[preferencesPanel release];
|
||||||
|
[statsLabel release];
|
||||||
|
[problemDialog release];
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +126,29 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted
|
||||||
|
{
|
||||||
|
NSInteger mark_count = [[py getMarkCount] intValue];
|
||||||
|
if (!mark_count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSString *msg = @"You are about to send %d files to Trash. Continue?";
|
||||||
|
if (hardlinkDeleted) {
|
||||||
|
msg = @"You are about to send %d files to Trash (and hardlink them afterwards). Continue?";
|
||||||
|
}
|
||||||
|
if ([Dialogs askYesNo:[NSString stringWithFormat:msg,mark_count]] == NSAlertSecondButtonReturn) { // NO
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
if (hardlinkDeleted) {
|
||||||
|
[py hardlinkMarked];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[py deleteMarked];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
- (IBAction)clearIgnoreList:(id)sender
|
- (IBAction)clearIgnoreList:(id)sender
|
||||||
{
|
{
|
||||||
@@ -134,12 +162,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (IBAction)changeDelta:(id)sender
|
- (IBAction)changeDelta:(id)sender
|
||||||
{
|
{
|
||||||
[outline setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
|
[table setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)changePowerMarker:(id)sender
|
- (IBAction)changePowerMarker:(id)sender
|
||||||
{
|
{
|
||||||
[outline setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
|
[table setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)copyMarked:(id)sender
|
- (IBAction)copyMarked:(id)sender
|
||||||
@@ -163,14 +191,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (IBAction)deleteMarked:(id)sender
|
- (IBAction)deleteMarked:(id)sender
|
||||||
{
|
{
|
||||||
NSInteger mark_count = [[py getMarkCount] intValue];
|
[self sendMarkedToTrash:NO];
|
||||||
if (!mark_count)
|
}
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
|
- (IBAction)hardlinkMarked:(id)sender
|
||||||
return;
|
{
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
[self sendMarkedToTrash:YES];
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
|
||||||
[py deleteMarked];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)exportToXHTML:(id)sender
|
- (IBAction)exportToXHTML:(id)sender
|
||||||
@@ -182,13 +208,13 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (IBAction)filter:(id)sender
|
- (IBAction)filter:(id)sender
|
||||||
{
|
{
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
|
[py setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
|
||||||
[py applyFilter:[filterField stringValue]];
|
[py applyFilter:[filterField stringValue]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)ignoreSelected:(id)sender
|
- (IBAction)ignoreSelected:(id)sender
|
||||||
{
|
{
|
||||||
NSInteger selectedDupeCount = [outline selectedDupeCount];
|
NSInteger selectedDupeCount = [table selectedDupeCount];
|
||||||
if (!selectedDupeCount)
|
if (!selectedDupeCount)
|
||||||
return;
|
return;
|
||||||
NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",selectedDupeCount];
|
NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",selectedDupeCount];
|
||||||
@@ -197,6 +223,33 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[py addSelectedToIgnoreList];
|
[py addSelectedToIgnoreList];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IBAction)invokeCustomCommand:(id)sender
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
NSString *cmd = [ud stringForKey:@"CustomCommand"];
|
||||||
|
if ((cmd != nil) && ([cmd length] > 0)) {
|
||||||
|
[py invokeCommand:cmd];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[Dialogs showMessage:@"You have no custom command set up. Set it up in your preferences."];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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
|
- (IBAction)markAll:(id)sender
|
||||||
{
|
{
|
||||||
[py markAll];
|
[py markAll];
|
||||||
@@ -232,7 +285,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
{
|
{
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
NSString *directory = [[op filenames] objectAtIndex:0];
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +316,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (IBAction)removeSelected:(id)sender
|
- (IBAction)removeSelected:(id)sender
|
||||||
{
|
{
|
||||||
[outline removeSelected];
|
[table removeSelected];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)renameSelected:(id)sender
|
- (IBAction)renameSelected:(id)sender
|
||||||
@@ -288,6 +341,17 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[preferencesPanel showWindow:sender];
|
[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
|
- (IBAction)startDuplicateScan:(id)sender
|
||||||
{
|
{
|
||||||
// Virtual
|
// Virtual
|
||||||
@@ -349,32 +413,34 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
- (void)jobCompleted:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
NSInteger r = n2i([py getOperationalErrorCount]);
|
|
||||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
id lastAction = [[ProgressController mainProgressController] jobId];
|
||||||
if ([lastAction isEqualTo:jobCopy]) {
|
if ([lastAction isEqualTo:jobCopy]) {
|
||||||
if (r > 0)
|
if ([py scanWasProblematic]) {
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]];
|
[problemDialog showWindow:self];
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
[Dialogs showMessage:@"All marked files were copied sucessfully."];
|
[Dialogs showMessage:@"All marked files were copied sucessfully."];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ([lastAction isEqualTo:jobMove]) {
|
else if ([lastAction isEqualTo:jobMove]) {
|
||||||
if (r > 0)
|
if ([py scanWasProblematic]) {
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]];
|
[problemDialog showWindow:self];
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
[Dialogs showMessage:@"All marked files were moved sucessfully."];
|
[Dialogs showMessage:@"All marked files were moved sucessfully."];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ([lastAction isEqualTo:jobDelete]) {
|
else if ([lastAction isEqualTo:jobDelete]) {
|
||||||
if (r > 0) {
|
if ([py scanWasProblematic]) {
|
||||||
NSString *msg = @"%d file(s) couldn't be sent to Trash. They were kept in the results, "\
|
[problemDialog showWindow:self];
|
||||||
"and still are marked. See the F.A.Q. section in the help file for details.";
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:msg,r]];
|
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ([lastAction isEqualTo:jobScan]) {
|
else if ([lastAction isEqualTo:jobScan]) {
|
||||||
NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];
|
NSInteger rowCount = [[table py] numberOfRows];
|
||||||
if (groupCount == 0)
|
if (rowCount == 0)
|
||||||
[Dialogs showMessage:@"No duplicates found."];
|
[Dialogs showMessage:@"No duplicates found."];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||||
<data>
|
<data>
|
||||||
<int key="IBDocument.SystemTarget">1050</int>
|
<int key="IBDocument.SystemTarget">1050</int>
|
||||||
<string key="IBDocument.SystemVersion">10C540</string>
|
<string key="IBDocument.SystemVersion">10F569</string>
|
||||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<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>
|
||||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<object class="NSWindowTemplate" id="476890502">
|
<object class="NSWindowTemplate" id="476890502">
|
||||||
<int key="NSWindowStyleMask">155</int>
|
<int key="NSWindowStyleMask">155</int>
|
||||||
<int key="NSWindowBacking">2</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>
|
<int key="NSWTFlags">-260571136</int>
|
||||||
<string key="NSWindowTitle">Details of Selected File</string>
|
<string key="NSWindowTitle">Details of Selected File</string>
|
||||||
<object class="NSMutableString" key="NSWindowClass">
|
<object class="NSMutableString" key="NSWindowClass">
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<characters key="NS.bytes">View</characters>
|
<characters key="NS.bytes">View</characters>
|
||||||
</object>
|
</object>
|
||||||
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
<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">
|
<object class="NSView" key="NSWindowView" id="1027711962">
|
||||||
<reference key="NSNextResponder"/>
|
<reference key="NSNextResponder"/>
|
||||||
<int key="NSvFlags">256</int>
|
<int key="NSvFlags">256</int>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<object class="NSTableView" id="251969872">
|
<object class="NSTableView" id="251969872">
|
||||||
<reference key="NSNextResponder" ref="488480549"/>
|
<reference key="NSNextResponder" ref="488480549"/>
|
||||||
<int key="NSvFlags">256</int>
|
<int key="NSvFlags">256</int>
|
||||||
<string key="NSFrameSize">{449, 143}</string>
|
<string key="NSFrameSize">{449, 128}</string>
|
||||||
<reference key="NSSuperview" ref="488480549"/>
|
<reference key="NSSuperview" ref="488480549"/>
|
||||||
<int key="NSTag">2</int>
|
<int key="NSTag">2</int>
|
||||||
<bool key="NSEnabled">YES</bool>
|
<bool key="NSEnabled">YES</bool>
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
</object>
|
</object>
|
||||||
</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="NSSuperview" ref="362108788"/>
|
||||||
<reference key="NSNextKeyView" ref="251969872"/>
|
<reference key="NSNextKeyView" ref="251969872"/>
|
||||||
<reference key="NSDocView" ref="251969872"/>
|
<reference key="NSDocView" ref="251969872"/>
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<reference ref="717380566"/>
|
<reference ref="717380566"/>
|
||||||
</object>
|
</object>
|
||||||
<string key="NSFrameSize">{451, 161}</string>
|
<string key="NSFrameSize">{451, 146}</string>
|
||||||
<reference key="NSSuperview" ref="1027711962"/>
|
<reference key="NSSuperview" ref="1027711962"/>
|
||||||
<reference key="NSNextKeyView" ref="488480549"/>
|
<reference key="NSNextKeyView" ref="488480549"/>
|
||||||
<int key="NSsFlags">530</int>
|
<int key="NSsFlags">530</int>
|
||||||
@@ -278,12 +278,13 @@
|
|||||||
<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
|
<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
|
||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<string key="NSFrameSize">{451, 161}</string>
|
<string key="NSFrameSize">{451, 146}</string>
|
||||||
<reference key="NSSuperview"/>
|
<reference key="NSSuperview"/>
|
||||||
</object>
|
</object>
|
||||||
<string key="NSScreenRect">{{0, 0}, {1024, 746}}</string>
|
<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="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||||
|
<string key="NSFrameAutosaveName">DetailsPanel</string>
|
||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
@@ -497,12 +498,12 @@
|
|||||||
<boolean value="YES"/>
|
<boolean value="YES"/>
|
||||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
<boolean value="YES"/>
|
<boolean value="YES"/>
|
||||||
<string>{{109, 656}, {451, 161}}</string>
|
<string>{{109, 671}, {451, 146}}</string>
|
||||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</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"/>
|
||||||
<boolean value="YES"/>
|
<boolean value="YES"/>
|
||||||
<string>{451, 161}</string>
|
<string>{451, 146}</string>
|
||||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
<boolean value="YES"/>
|
<boolean value="YES"/>
|
||||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
@@ -536,11 +537,18 @@
|
|||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
<object class="IBPartialClassDescription">
|
<object class="IBPartialClassDescription">
|
||||||
<string key="className">DetailsPanel</string>
|
<string key="className">DetailsPanel</string>
|
||||||
<string key="superclassName">NSWindowController</string>
|
<string key="superclassName">HSWindowController</string>
|
||||||
<object class="NSMutableDictionary" key="outlets">
|
<object class="NSMutableDictionary" key="outlets">
|
||||||
<string key="NS.key.0">detailsTable</string>
|
<string key="NS.key.0">detailsTable</string>
|
||||||
<string key="NS.object.0">NSTableView</string>
|
<string key="NS.object.0">NSTableView</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBProjectSource</string>
|
<string key="majorKey">IBProjectSource</string>
|
||||||
<string key="minorKey">../base/DetailsPanel.h</string>
|
<string key="minorKey">../base/DetailsPanel.h</string>
|
||||||
@@ -548,7 +556,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<object class="IBPartialClassDescription">
|
<object class="IBPartialClassDescription">
|
||||||
<string key="className">DetailsPanel</string>
|
<string key="className">DetailsPanel</string>
|
||||||
<string key="superclassName">NSWindowController</string>
|
<string key="superclassName">HSWindowController</string>
|
||||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBUserSource</string>
|
<string key="majorKey">IBUserSource</string>
|
||||||
<string key="minorKey"/>
|
<string key="minorKey"/>
|
||||||
@@ -562,6 +570,32 @@
|
|||||||
<string key="minorKey"/>
|
<string key="minorKey"/>
|
||||||
</object>
|
</object>
|
||||||
</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>
|
||||||
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
|
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
|
||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
@@ -1029,6 +1063,13 @@
|
|||||||
<string key="NS.key.0">showWindow:</string>
|
<string key="NS.key.0">showWindow:</string>
|
||||||
<string key="NS.object.0">id</string>
|
<string key="NS.object.0">id</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBFrameworkSource</string>
|
<string key="majorKey">IBFrameworkSource</string>
|
||||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||||
@@ -1037,6 +1078,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<int key="IBDocument.localizationMode">0</int>
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
<integer value="1050" key="NS.object.0"/>
|
<integer value="1050" key="NS.object.0"/>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||||
<data>
|
<data>
|
||||||
<int key="IBDocument.SystemTarget">1050</int>
|
<int key="IBDocument.SystemTarget">1050</int>
|
||||||
<string key="IBDocument.SystemVersion">10C540</string>
|
<string key="IBDocument.SystemVersion">10F569</string>
|
||||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<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>
|
||||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
@@ -428,6 +428,7 @@
|
|||||||
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
||||||
<string key="NSMinSize">{369, 291}</string>
|
<string key="NSMinSize">{369, 291}</string>
|
||||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||||
|
<string key="NSFrameAutosaveName">DirectoryPanel</string>
|
||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
@@ -869,6 +870,35 @@
|
|||||||
<string>id</string>
|
<string>id</string>
|
||||||
</object>
|
</object>
|
||||||
</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">
|
<object class="NSMutableDictionary" key="outlets">
|
||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
<object class="NSArray" key="dict.sortedKeys">
|
<object class="NSArray" key="dict.sortedKeys">
|
||||||
@@ -884,6 +914,30 @@
|
|||||||
<string>NSButton</string>
|
<string>NSButton</string>
|
||||||
</object>
|
</object>
|
||||||
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBProjectSource</string>
|
<string key="majorKey">IBProjectSource</string>
|
||||||
<string key="minorKey">../base/DirectoryPanel.h</string>
|
<string key="minorKey">../base/DirectoryPanel.h</string>
|
||||||
@@ -1437,6 +1491,13 @@
|
|||||||
<string key="NS.key.0">showWindow:</string>
|
<string key="NS.key.0">showWindow:</string>
|
||||||
<string key="NS.object.0">id</string>
|
<string key="NS.object.0">id</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBFrameworkSource</string>
|
<string key="majorKey">IBFrameworkSource</string>
|
||||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||||
@@ -1445,6 +1506,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<int key="IBDocument.localizationMode">0</int>
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
<integer value="1050" key="NS.object.0"/>
|
<integer value="1050" key="NS.object.0"/>
|
||||||
@@ -1460,5 +1522,18 @@
|
|||||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
<string key="IBDocument.LastKnownRelativeProjectPath">../../se/dupeguru.xcodeproj</string>
|
<string key="IBDocument.LastKnownRelativeProjectPath">../../se/dupeguru.xcodeproj</string>
|
||||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
<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>
|
</data>
|
||||||
</archive>
|
</archive>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1142
cocoa/base/xib/ProblemDialog.xib
Normal file
1142
cocoa/base/xib/ProblemDialog.xib
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
||||||
[d setObject:b2n(YES) forKey:@"mixFileKind"];
|
[d setObject:b2n(YES) forKey:@"mixFileKind"];
|
||||||
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
|
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"];
|
||||||
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
|
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
|
||||||
[d setObject:b2n(NO) forKey:@"debug"];
|
[d setObject:b2n(NO) forKey:@"debug"];
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
|
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>hsft</string>
|
<string>hsft</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>5.7.2</string>
|
<string>5.10.0</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
{
|
{
|
||||||
[super awakeFromNib];
|
[super awakeFromNib];
|
||||||
[[self window] setTitle:@"dupeGuru Music Edition"];
|
[[self window] setTitle:@"dupeGuru Music Edition"];
|
||||||
NSMutableIndexSet *deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
|
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,6)];
|
||||||
[deltaColumns removeIndex:6];
|
[deltaColumns removeIndex:6];
|
||||||
[outline setDeltaColumns:deltaColumns];
|
[table setDeltaColumns:deltaColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -38,13 +38,15 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[columnsOrder addObject:@"2"];
|
[columnsOrder addObject:@"2"];
|
||||||
[columnsOrder addObject:@"3"];
|
[columnsOrder addObject:@"3"];
|
||||||
[columnsOrder addObject:@"4"];
|
[columnsOrder addObject:@"4"];
|
||||||
[columnsOrder addObject:@"16"];
|
[columnsOrder addObject:@"6"];
|
||||||
|
[columnsOrder addObject:@"15"];
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||||
[columnsWidth setObject:i2n(214) forKey:@"0"];
|
[columnsWidth setObject:i2n(235) forKey:@"0"];
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||||
[columnsWidth setObject:i2n(50) forKey:@"3"];
|
[columnsWidth setObject:i2n(50) forKey:@"3"];
|
||||||
[columnsWidth setObject:i2n(50) forKey:@"4"];
|
[columnsWidth setObject:i2n(50) forKey:@"4"];
|
||||||
[columnsWidth setObject:i2n(57) forKey:@"16"];
|
[columnsWidth setObject:i2n(40) forKey:@"6"];
|
||||||
|
[columnsWidth setObject:i2n(57) forKey:@"15"];
|
||||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +68,10 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[_py enable:[ud objectForKey:@"scanTagYear"] scanForTag:@"year"];
|
[_py enable:[ud objectForKey:@"scanTagYear"] scanForTag:@"year"];
|
||||||
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
||||||
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
|
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
|
||||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
[_py setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
||||||
|
[_py setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
||||||
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
||||||
NSInteger r = n2i([py doScan]);
|
NSInteger r = n2i([py doScan]);
|
||||||
if (r == 1)
|
|
||||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
|
||||||
if (r == 3)
|
if (r == 3)
|
||||||
{
|
{
|
||||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||||
@@ -96,18 +97,17 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[_resultColumns addObject:brCol];
|
[_resultColumns addObject:brCol];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Creation" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Modification" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Modification" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Title" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Title" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Artist" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Artist" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Album" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Album" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Genre" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Genre" width:80 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Year" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Year" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Track Number" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Track Number" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Comment" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Comment" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Match %" width:57 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:16 title:@"Match %" width:57 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:16 title:@"Words Used" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Words Used" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
|
|||||||
@@ -4,16 +4,23 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsutil.cocoa import signature
|
from hscommon.cocoa import signature
|
||||||
|
|
||||||
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||||
from core_me.app_cocoa import DupeGuruME
|
from core_me.app_cocoa import DupeGuruME
|
||||||
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
from core.scanner import ScanType
|
||||||
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
|
||||||
|
|
||||||
# Fix py2app imports which chokes on relative imports
|
# Fix py2app imports which chokes on relative imports and other stuff
|
||||||
from core_me import app_cocoa, data, fs, scanner
|
import core_me.app_cocoa, core_me.data, core_me.fs, core_me.scanner
|
||||||
from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
import hsaudiotag.aiff, hsaudiotag.flac, hsaudiotag.genres, hsaudiotag.id3v1,\
|
||||||
|
hsaudiotag.id3v2, hsaudiotag.mp4, hsaudiotag.mpeg, hsaudiotag.ogg, hsaudiotag.wma
|
||||||
|
from hsaudiotag import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
||||||
|
import hsutil.conflict
|
||||||
|
import core.engine, core.fs, core.app
|
||||||
|
import xml.etree.ElementPath
|
||||||
|
import gzip
|
||||||
|
import aem.kae
|
||||||
|
import appscript.defaultterminology
|
||||||
|
|
||||||
class PyDupeGuru(PyDupeGuruBase):
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
def init(self):
|
def init(self):
|
||||||
@@ -39,12 +46,12 @@ class PyDupeGuru(PyDupeGuruBase):
|
|||||||
def setScanType_(self, scan_type):
|
def setScanType_(self, scan_type):
|
||||||
try:
|
try:
|
||||||
self.py.scanner.scan_type = [
|
self.py.scanner.scan_type = [
|
||||||
SCAN_TYPE_FILENAME,
|
ScanType.Filename,
|
||||||
SCAN_TYPE_FIELDS,
|
ScanType.Fields,
|
||||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
ScanType.FieldsNoOrder,
|
||||||
SCAN_TYPE_TAG,
|
ScanType.Tag,
|
||||||
SCAN_TYPE_CONTENT,
|
ScanType.Contents,
|
||||||
SCAN_TYPE_CONTENT_AUDIO
|
ScanType.ContentsAudio,
|
||||||
][scan_type]
|
][scan_type]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CC511242D00004B0AA7 /* NSTableViewAdditions.m */; };
|
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CC511242D00004B0AA7 /* NSTableViewAdditions.m */; };
|
||||||
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */; };
|
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */; };
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
|
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
|
||||||
CE0B3D6711243F83009A7A30 /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0B3D6611243F83009A7A30 /* ResultOutline.m */; };
|
CE0A0C001175A1C000DCA3C6 /* HSTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0A0BFF1175A1C000DCA3C6 /* HSTable.m */; };
|
||||||
|
CE0A0C041175A1DE00DCA3C6 /* ProblemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0A0C021175A1DE00DCA3C6 /* ProblemDialog.m */; };
|
||||||
|
CE0A0C061175A24800DCA3C6 /* ProblemDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE0A0C051175A24800DCA3C6 /* ProblemDialog.xib */; };
|
||||||
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
||||||
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
||||||
@@ -41,7 +43,6 @@
|
|||||||
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
||||||
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */; };
|
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */; };
|
||||||
CE4B59C91119919700C06C9E /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C61119919700C06C9E /* progress.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 */; };
|
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
|
||||||
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; };
|
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; };
|
||||||
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; };
|
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; };
|
||||||
@@ -52,12 +53,15 @@
|
|||||||
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E160FC6C19300EC695D /* AppDelegate.m */; };
|
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E160FC6C19300EC695D /* AppDelegate.m */; };
|
||||||
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E190FC6C19300EC695D /* DirectoryPanel.m */; };
|
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E190FC6C19300EC695D /* DirectoryPanel.m */; };
|
||||||
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E1C0FC6C19300EC695D /* ResultWindow.m */; };
|
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E1C0FC6C19300EC695D /* ResultWindow.m */; };
|
||||||
|
CE578303124DFC660004769C /* HSTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE578302124DFC660004769C /* HSTableView.m */; };
|
||||||
CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6032BF0FE6784C007E33FF /* DetailsPanel.m */; };
|
CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6032BF0FE6784C007E33FF /* DetailsPanel.m */; };
|
||||||
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
||||||
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */; };
|
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */; };
|
||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||||
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
|
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
|
||||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
|
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
|
||||||
|
CEB14D29124DFC2800FA7481 /* ResultTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CEB14D28124DFC2800FA7481 /* ResultTable.m */; };
|
||||||
|
CECC563B12144A9000ABF262 /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CECC563912144A9000ABF262 /* registration.xib */; };
|
||||||
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDF07A2112493B200EE5BC0 /* StatsLabel.m */; };
|
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDF07A2112493B200EE5BC0 /* StatsLabel.m */; };
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
||||||
@@ -108,9 +112,13 @@
|
|||||||
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryOutline.m; path = ../base/DirectoryOutline.m; sourceTree = SOURCE_ROOT; };
|
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryOutline.m; path = ../base/DirectoryOutline.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDirectoryOutline.h; path = ../base/PyDirectoryOutline.h; sourceTree = SOURCE_ROOT; };
|
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDirectoryOutline.h; path = ../base/PyDirectoryOutline.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = ../../help_me/dupeguru_me_help; sourceTree = "<group>"; };
|
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = ../../help_me/dupeguru_me_help; sourceTree = "<group>"; };
|
||||||
CE0B3D6411243F83009A7A30 /* PyResultTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTree.h; path = ../base/PyResultTree.h; sourceTree = SOURCE_ROOT; };
|
CE0A0BFE1175A1C000DCA3C6 /* HSTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSTable.h; sourceTree = "<group>"; };
|
||||||
CE0B3D6511243F83009A7A30 /* ResultOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultOutline.h; path = ../base/ResultOutline.h; sourceTree = SOURCE_ROOT; };
|
CE0A0BFF1175A1C000DCA3C6 /* HSTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSTable.m; sourceTree = "<group>"; };
|
||||||
CE0B3D6611243F83009A7A30 /* ResultOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultOutline.m; path = ../base/ResultOutline.m; sourceTree = SOURCE_ROOT; };
|
CE0A0C011175A1DE00DCA3C6 /* ProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProblemDialog.h; path = ../base/ProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0A0C021175A1DE00DCA3C6 /* ProblemDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProblemDialog.m; path = ../base/ProblemDialog.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0A0C031175A1DE00DCA3C6 /* PyProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyProblemDialog.h; path = ../base/PyProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0A0C051175A24800DCA3C6 /* ProblemDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = ProblemDialog.xib; path = ../base/xib/ProblemDialog.xib; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0A0C131175A28100DCA3C6 /* PyTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyTable.h; sourceTree = "<group>"; };
|
||||||
CE1425880AFB718500BD5167 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
CE1425880AFB718500BD5167 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
||||||
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
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; };
|
CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -123,7 +131,6 @@
|
|||||||
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ErrorReportWindow.xib; sourceTree = "<group>"; };
|
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>"; };
|
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; };
|
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; };
|
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; };
|
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -147,6 +154,8 @@
|
|||||||
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDupeGuru.h; path = ../base/PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
|
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDupeGuru.h; path = ../base/PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE515E1B0FC6C19300EC695D /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
CE515E1B0FC6C19300EC695D /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE515E1C0FC6C19300EC695D /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
CE515E1C0FC6C19300EC695D /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE578301124DFC660004769C /* HSTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSTableView.h; path = ../../cocoalib/views/HSTableView.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE578302124DFC660004769C /* HSTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSTableView.m; path = ../../cocoalib/views/HSTableView.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE6032BE0FE6784C007E33FF /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = ../base/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
CE6032BE0FE6784C007E33FF /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = ../base/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsPanel.m; path = ../base/DetailsPanel.m; sourceTree = SOURCE_ROOT; };
|
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsPanel.m; path = ../base/DetailsPanel.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -155,8 +164,10 @@
|
|||||||
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
||||||
CE900AD1109B238600754048 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; 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>"; };
|
CE900AD6109B2A9B00754048 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
CEB14D26124DFC2800FA7481 /* PyResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTable.h; path = ../base/PyResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
CEB14D27124DFC2800FA7481 /* ResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultTable.h; path = ../base/ResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CEB14D28124DFC2800FA7481 /* ResultTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultTable.m; path = ../base/ResultTable.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
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; };
|
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; };
|
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; };
|
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -181,21 +192,19 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
080E96DDFE201D6D7F000001 /* Classes */ = {
|
080E96DDFE201D6D7F000001 /* DGME */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CE381C9509914ACE003581CE /* AppDelegate.h */,
|
CE381C9509914ACE003581CE /* AppDelegate.h */,
|
||||||
CE381C9409914ACE003581CE /* AppDelegate.m */,
|
CE381C9409914ACE003581CE /* AppDelegate.m */,
|
||||||
CE848A1809DD85810004CB44 /* Consts.h */,
|
CE848A1809DD85810004CB44 /* Consts.h */,
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */,
|
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */,
|
|
||||||
CE68EE6509ABC48000971085 /* DirectoryPanel.h */,
|
CE68EE6509ABC48000971085 /* DirectoryPanel.h */,
|
||||||
CE68EE6609ABC48000971085 /* DirectoryPanel.m */,
|
CE68EE6609ABC48000971085 /* DirectoryPanel.m */,
|
||||||
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */,
|
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */,
|
||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */,
|
CE381C9B09914ADF003581CE /* ResultWindow.h */,
|
||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */,
|
CE381C9A09914ADF003581CE /* ResultWindow.m */,
|
||||||
);
|
);
|
||||||
name = Classes;
|
name = DGME;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
||||||
@@ -228,7 +237,7 @@
|
|||||||
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
|
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
080E96DDFE201D6D7F000001 /* Classes */,
|
080E96DDFE201D6D7F000001 /* DGME */,
|
||||||
CE515E140FC6C17900EC695D /* dgbase */,
|
CE515E140FC6C17900EC695D /* dgbase */,
|
||||||
CE515DDD0FC6C09400EC695D /* cocoalib */,
|
CE515DDD0FC6C09400EC695D /* cocoalib */,
|
||||||
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
||||||
@@ -277,6 +286,8 @@
|
|||||||
CE003CB411242D00004B0AA7 /* HSGUIController.m */,
|
CE003CB411242D00004B0AA7 /* HSGUIController.m */,
|
||||||
CE003CB511242D00004B0AA7 /* HSOutline.h */,
|
CE003CB511242D00004B0AA7 /* HSOutline.h */,
|
||||||
CE003CB611242D00004B0AA7 /* HSOutline.m */,
|
CE003CB611242D00004B0AA7 /* HSOutline.m */,
|
||||||
|
CE0A0BFE1175A1C000DCA3C6 /* HSTable.h */,
|
||||||
|
CE0A0BFF1175A1C000DCA3C6 /* HSTable.m */,
|
||||||
CE003CB711242D00004B0AA7 /* HSWindowController.h */,
|
CE003CB711242D00004B0AA7 /* HSWindowController.h */,
|
||||||
CE003CB811242D00004B0AA7 /* HSWindowController.m */,
|
CE003CB811242D00004B0AA7 /* HSWindowController.m */,
|
||||||
);
|
);
|
||||||
@@ -289,6 +300,7 @@
|
|||||||
children = (
|
children = (
|
||||||
CE003CBC11242D00004B0AA7 /* PyGUI.h */,
|
CE003CBC11242D00004B0AA7 /* PyGUI.h */,
|
||||||
CE003CBD11242D00004B0AA7 /* PyOutline.h */,
|
CE003CBD11242D00004B0AA7 /* PyOutline.h */,
|
||||||
|
CE0A0C131175A28100DCA3C6 /* PyTable.h */,
|
||||||
);
|
);
|
||||||
name = proxies;
|
name = proxies;
|
||||||
path = ../../cocoalib/proxies;
|
path = ../../cocoalib/proxies;
|
||||||
@@ -297,6 +309,8 @@
|
|||||||
CE003CBF11242D00004B0AA7 /* views */ = {
|
CE003CBF11242D00004B0AA7 /* views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE578301124DFC660004769C /* HSTableView.h */,
|
||||||
|
CE578302124DFC660004769C /* HSTableView.m */,
|
||||||
CE003CC011242D00004B0AA7 /* HSOutlineView.h */,
|
CE003CC011242D00004B0AA7 /* HSOutlineView.h */,
|
||||||
CE003CC111242D00004B0AA7 /* HSOutlineView.m */,
|
CE003CC111242D00004B0AA7 /* HSOutlineView.m */,
|
||||||
CE003CC211242D00004B0AA7 /* NSIndexPathAdditions.h */,
|
CE003CC211242D00004B0AA7 /* NSIndexPathAdditions.h */,
|
||||||
@@ -315,6 +329,7 @@
|
|||||||
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */,
|
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */,
|
||||||
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */,
|
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */,
|
||||||
CE900AD1109B238600754048 /* Preferences.xib */,
|
CE900AD1109B238600754048 /* Preferences.xib */,
|
||||||
|
CE0A0C051175A24800DCA3C6 /* ProblemDialog.xib */,
|
||||||
);
|
);
|
||||||
path = xib;
|
path = xib;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -332,9 +347,9 @@
|
|||||||
CE4B59C41119919700C06C9E /* xib */ = {
|
CE4B59C41119919700C06C9E /* xib */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CECC563912144A9000ABF262 /* registration.xib */,
|
||||||
CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */,
|
CE4B59C51119919700C06C9E /* ErrorReportWindow.xib */,
|
||||||
CE4B59C61119919700C06C9E /* progress.xib */,
|
CE4B59C61119919700C06C9E /* progress.xib */,
|
||||||
CE4B59C71119919700C06C9E /* registration.xib */,
|
|
||||||
);
|
);
|
||||||
name = xib;
|
name = xib;
|
||||||
path = ../../cocoalib/xib;
|
path = ../../cocoalib/xib;
|
||||||
@@ -373,6 +388,9 @@
|
|||||||
CE515E140FC6C17900EC695D /* dgbase */ = {
|
CE515E140FC6C17900EC695D /* dgbase */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CEB14D26124DFC2800FA7481 /* PyResultTable.h */,
|
||||||
|
CEB14D27124DFC2800FA7481 /* ResultTable.h */,
|
||||||
|
CEB14D28124DFC2800FA7481 /* ResultTable.m */,
|
||||||
CE003CCD11242D2C004B0AA7 /* DirectoryOutline.h */,
|
CE003CCD11242D2C004B0AA7 /* DirectoryOutline.h */,
|
||||||
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */,
|
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */,
|
||||||
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */,
|
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */,
|
||||||
@@ -383,15 +401,15 @@
|
|||||||
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */,
|
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */,
|
||||||
CE515E180FC6C19300EC695D /* DirectoryPanel.h */,
|
CE515E180FC6C19300EC695D /* DirectoryPanel.h */,
|
||||||
CE515E190FC6C19300EC695D /* DirectoryPanel.m */,
|
CE515E190FC6C19300EC695D /* DirectoryPanel.m */,
|
||||||
|
CE0A0C011175A1DE00DCA3C6 /* ProblemDialog.h */,
|
||||||
|
CE0A0C021175A1DE00DCA3C6 /* ProblemDialog.m */,
|
||||||
CE515E1B0FC6C19300EC695D /* ResultWindow.h */,
|
CE515E1B0FC6C19300EC695D /* ResultWindow.h */,
|
||||||
CE515E1C0FC6C19300EC695D /* ResultWindow.m */,
|
CE515E1C0FC6C19300EC695D /* ResultWindow.m */,
|
||||||
CE0B3D6511243F83009A7A30 /* ResultOutline.h */,
|
|
||||||
CE0B3D6611243F83009A7A30 /* ResultOutline.m */,
|
|
||||||
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */,
|
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */,
|
||||||
CEDF07A2112493B200EE5BC0 /* StatsLabel.m */,
|
CEDF07A2112493B200EE5BC0 /* StatsLabel.m */,
|
||||||
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
|
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
|
||||||
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */,
|
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */,
|
||||||
CE0B3D6411243F83009A7A30 /* PyResultTree.h */,
|
CE0A0C031175A1DE00DCA3C6 /* PyProblemDialog.h */,
|
||||||
CEDF07A0112493B200EE5BC0 /* PyStatsLabel.h */,
|
CEDF07A0112493B200EE5BC0 /* PyStatsLabel.h */,
|
||||||
);
|
);
|
||||||
name = dgbase;
|
name = dgbase;
|
||||||
@@ -438,6 +456,13 @@
|
|||||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||||
compatibilityVersion = "Xcode 3.0";
|
compatibilityVersion = "Xcode 3.0";
|
||||||
hasScannedForEncodings = 1;
|
hasScannedForEncodings = 1;
|
||||||
|
knownRegions = (
|
||||||
|
English,
|
||||||
|
Japanese,
|
||||||
|
French,
|
||||||
|
German,
|
||||||
|
en,
|
||||||
|
);
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -465,7 +490,8 @@
|
|||||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */,
|
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */,
|
||||||
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */,
|
CE4B59C81119919700C06C9E /* ErrorReportWindow.xib in Resources */,
|
||||||
CE4B59C91119919700C06C9E /* progress.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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -500,13 +526,28 @@
|
|||||||
CE003CCB11242D00004B0AA7 /* NSIndexPathAdditions.m in Sources */,
|
CE003CCB11242D00004B0AA7 /* NSIndexPathAdditions.m in Sources */,
|
||||||
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */,
|
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */,
|
||||||
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */,
|
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */,
|
||||||
CE0B3D6711243F83009A7A30 /* ResultOutline.m in Sources */,
|
|
||||||
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */,
|
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */,
|
||||||
|
CE0A0C001175A1C000DCA3C6 /* HSTable.m in Sources */,
|
||||||
|
CE0A0C041175A1DE00DCA3C6 /* ProblemDialog.m in Sources */,
|
||||||
|
CEB14D29124DFC2800FA7481 /* ResultTable.m in Sources */,
|
||||||
|
CE578303124DFC660004769C /* HSTableView.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* 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 */
|
/* Begin XCBuildConfiguration section */
|
||||||
C01FCF4C08A954540054247B /* release */ = {
|
C01FCF4C08A954540054247B /* release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"matchScaled"];
|
[d setObject:[NSNumber numberWithBool:NO] forKey:@"matchScaled"];
|
||||||
[d setObject:[NSNumber numberWithBool:YES] forKey:@"mixFileKind"];
|
[d setObject:[NSNumber numberWithBool:YES] forKey:@"mixFileKind"];
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"useRegexpFilter"];
|
[d setObject:[NSNumber numberWithBool:NO] forKey:@"useRegexpFilter"];
|
||||||
|
[d setObject:[NSNumber numberWithBool:NO] forKey:@"ignoreHardlinkMatches"];
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"removeEmptyFolders"];
|
[d setObject:[NSNumber numberWithBool:NO] forKey:@"removeEmptyFolders"];
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"debug"];
|
[d setObject:[NSNumber numberWithBool:NO] forKey:@"debug"];
|
||||||
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>hsft</string>
|
<string>hsft</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.8.3</string>
|
<string>1.11.0</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "Outline.h"
|
|
||||||
#import "../base/ResultWindow.h"
|
#import "../base/ResultWindow.h"
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase {}
|
@interface ResultWindow : ResultWindowBase {}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
{
|
{
|
||||||
[super awakeFromNib];
|
[super awakeFromNib];
|
||||||
[[self window] setTitle:@"dupeGuru Picture Edition"];
|
[[self window] setTitle:@"dupeGuru Picture Edition"];
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain];
|
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
|
||||||
[_deltaColumns removeIndex:3];
|
[deltaColumns addIndex:5];
|
||||||
[_deltaColumns removeIndex:4];
|
[table setDeltaColumns:deltaColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -40,13 +40,13 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[columnsOrder addObject:@"1"];
|
[columnsOrder addObject:@"1"];
|
||||||
[columnsOrder addObject:@"2"];
|
[columnsOrder addObject:@"2"];
|
||||||
[columnsOrder addObject:@"4"];
|
[columnsOrder addObject:@"4"];
|
||||||
[columnsOrder addObject:@"7"];
|
[columnsOrder addObject:@"6"];
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||||
[columnsWidth setObject:i2n(121) forKey:@"0"];
|
[columnsWidth setObject:i2n(162) forKey:@"0"];
|
||||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
[columnsWidth setObject:i2n(142) forKey:@"1"];
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||||
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
||||||
[columnsWidth setObject:i2n(58) forKey:@"7"];
|
[columnsWidth setObject:i2n(58) forKey:@"6"];
|
||||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,15 +60,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
PyDupeGuru *_py = (PyDupeGuru *)py;
|
PyDupeGuru *_py = (PyDupeGuru *)py;
|
||||||
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
||||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
[_py setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
||||||
|
[_py setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
||||||
[_py setMatchScaled:[ud objectForKey:@"matchScaled"]];
|
[_py setMatchScaled:[ud objectForKey:@"matchScaled"]];
|
||||||
int r = n2i([py doScan]);
|
int r = n2i([py doScan]);
|
||||||
[matches reloadData];
|
|
||||||
[self refreshStats];
|
|
||||||
if (r != 0)
|
if (r != 0)
|
||||||
[[ProgressController mainProgressController] hide];
|
[[ProgressController mainProgressController] hide];
|
||||||
if (r == 1)
|
|
||||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
|
||||||
if (r == 3)
|
if (r == 3)
|
||||||
{
|
{
|
||||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||||
@@ -93,9 +90,8 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[_resultColumns addObject:sizeCol];
|
[_resultColumns addObject:sizeCol];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Dimensions" width:80 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Dimensions" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Creation" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Modification" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Modification" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Match %" width:58 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Match %" width:58 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@@ -7,8 +7,14 @@
|
|||||||
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||||
from core_pe import app_cocoa as app_pe_cocoa
|
from core_pe import app_cocoa as app_pe_cocoa
|
||||||
|
|
||||||
# Fix py2app imports which chokes on relative imports
|
# Fix py2app imports which chokes on relative imports and other stuff
|
||||||
from core_pe import block, cache, matchbase, data, _block_osx
|
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):
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
def init(self):
|
def init(self):
|
||||||
@@ -21,10 +27,10 @@ class PyDupeGuru(PyDupeGuruBase):
|
|||||||
|
|
||||||
#---Information
|
#---Information
|
||||||
def getSelectedDupePath(self):
|
def getSelectedDupePath(self):
|
||||||
return unicode(self.py.selected_dupe_path())
|
return str(self.py.selected_dupe_path())
|
||||||
|
|
||||||
def getSelectedDupeRefPath(self):
|
def getSelectedDupeRefPath(self):
|
||||||
return unicode(self.py.selected_dupe_ref_path())
|
return str(self.py.selected_dupe_ref_path())
|
||||||
|
|
||||||
#---Properties
|
#---Properties
|
||||||
def setMatchScaled_(self,match_scaled):
|
def setMatchScaled_(self,match_scaled):
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
CE031751109B340A00517EE6 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031750109B340A00517EE6 /* Preferences.xib */; };
|
CE031751109B340A00517EE6 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031750109B340A00517EE6 /* Preferences.xib */; };
|
||||||
CE031754109B345200517EE6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031753109B345200517EE6 /* MainMenu.xib */; };
|
CE031754109B345200517EE6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE031753109B345200517EE6 /* MainMenu.xib */; };
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */; };
|
CE073F6309CAE1A3005C1D2F /* dupeguru_pe_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */; };
|
||||||
|
CE0C2AB61177011000BC749F /* HSTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C2AB51177011000BC749F /* HSTable.m */; };
|
||||||
|
CE0C2ABD1177014200BC749F /* ProblemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0C2ABB1177014200BC749F /* ProblemDialog.m */; };
|
||||||
|
CE0C2AC81177021600BC749F /* ProblemDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE0C2AC71177021600BC749F /* ProblemDialog.xib */; };
|
||||||
CE15C8A80ADEB8B50061D4A5 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
CE15C8A80ADEB8B50061D4A5 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
||||||
CE15C8C00ADEB8D40061D4A5 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
CE15C8C00ADEB8D40061D4A5 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */; };
|
||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
||||||
@@ -24,10 +27,8 @@
|
|||||||
CE77C8A810946CE20078B0DB /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE77C8A710946CE20078B0DB /* DetailsPanel.xib */; };
|
CE77C8A810946CE20078B0DB /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE77C8A710946CE20078B0DB /* DetailsPanel.xib */; };
|
||||||
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */; };
|
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */; };
|
||||||
CE7AC9191119911200D02F6C /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE7AC9161119911200D02F6C /* progress.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 */; };
|
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1C0FC192D60086DCA6 /* Dialogs.m */; };
|
||||||
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */; };
|
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */; };
|
||||||
CE80DB300FC192D60086DCA6 /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB200FC192D60086DCA6 /* Outline.m */; };
|
|
||||||
CE80DB310FC192D60086DCA6 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB220FC192D60086DCA6 /* ProgressController.m */; };
|
CE80DB310FC192D60086DCA6 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB220FC192D60086DCA6 /* ProgressController.m */; };
|
||||||
CE80DB320FC192D60086DCA6 /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB250FC192D60086DCA6 /* RecentDirectories.m */; };
|
CE80DB320FC192D60086DCA6 /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB250FC192D60086DCA6 /* RecentDirectories.m */; };
|
||||||
CE80DB330FC192D60086DCA6 /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB270FC192D60086DCA6 /* RegistrationInterface.m */; };
|
CE80DB330FC192D60086DCA6 /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB270FC192D60086DCA6 /* RegistrationInterface.m */; };
|
||||||
@@ -39,6 +40,8 @@
|
|||||||
CE80DB8B0FC1951C0086DCA6 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */; };
|
CE80DB8B0FC1951C0086DCA6 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */; };
|
||||||
CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB890FC1951C0086DCA6 /* ResultWindow.m */; };
|
CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB890FC1951C0086DCA6 /* ResultWindow.m */; };
|
||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
||||||
|
CE895D7B12144A7800E74705 /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE895D7912144A7800E74705 /* registration.xib */; };
|
||||||
|
CE95865F112C516400F95FD2 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE95865D112C516400F95FD2 /* StatsLabel.m */; };
|
||||||
CE9EA7561122C96C008CD2BC /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7441122C96C008CD2BC /* HSGUIController.m */; };
|
CE9EA7561122C96C008CD2BC /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7441122C96C008CD2BC /* HSGUIController.m */; };
|
||||||
CE9EA7571122C96C008CD2BC /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7461122C96C008CD2BC /* HSOutline.m */; };
|
CE9EA7571122C96C008CD2BC /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7461122C96C008CD2BC /* HSOutline.m */; };
|
||||||
CE9EA7581122C96C008CD2BC /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7481122C96C008CD2BC /* HSWindowController.m */; };
|
CE9EA7581122C96C008CD2BC /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7481122C96C008CD2BC /* HSWindowController.m */; };
|
||||||
@@ -51,6 +54,8 @@
|
|||||||
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
||||||
|
CEF12A7E124DFD400087B51D /* HSTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF12A7D124DFD400087B51D /* HSTableView.m */; };
|
||||||
|
CEF12A84124DFD620087B51D /* ResultTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CEF12A83124DFD620087B51D /* ResultTable.m */; };
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
||||||
@@ -83,6 +88,13 @@
|
|||||||
CE031750109B340A00517EE6 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
CE031750109B340A00517EE6 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
||||||
CE031753109B345200517EE6 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
CE031753109B345200517EE6 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_pe_help; path = ../../help_pe/dupeguru_pe_help; sourceTree = SOURCE_ROOT; };
|
CE073F5409CAE1A3005C1D2F /* dupeguru_pe_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_pe_help; path = ../../help_pe/dupeguru_pe_help; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0C2AAA117700E700BC749F /* PyTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyTable.h; sourceTree = "<group>"; };
|
||||||
|
CE0C2AB41177011000BC749F /* HSTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSTable.h; sourceTree = "<group>"; };
|
||||||
|
CE0C2AB51177011000BC749F /* HSTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSTable.m; sourceTree = "<group>"; };
|
||||||
|
CE0C2ABA1177014200BC749F /* ProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProblemDialog.h; path = ../base/ProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0C2ABB1177014200BC749F /* ProblemDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProblemDialog.m; path = ../base/ProblemDialog.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0C2ABC1177014200BC749F /* PyProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyProblemDialog.h; path = ../base/PyProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE0C2AC71177021600BC749F /* ProblemDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = ProblemDialog.xib; path = ../base/xib/ProblemDialog.xib; sourceTree = SOURCE_ROOT; };
|
||||||
CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
CE15C8A70ADEB8B50061D4A5 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
||||||
CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -99,13 +111,10 @@
|
|||||||
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailsPanel.xib; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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; };
|
CE80DB1B0FC192D60086DCA6 /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = ../../cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
|
CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB1F0FC192D60086DCA6 /* Outline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Outline.h; path = ../../cocoalib/Outline.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE80DB200FC192D60086DCA6 /* Outline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Outline.m; path = ../../cocoalib/Outline.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE80DB210FC192D60086DCA6 /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
|
CE80DB210FC192D60086DCA6 /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB220FC192D60086DCA6 /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
|
CE80DB220FC192D60086DCA6 /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE80DB230FC192D60086DCA6 /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
|
CE80DB230FC192D60086DCA6 /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -130,6 +139,10 @@
|
|||||||
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
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; };
|
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>"; };
|
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; };
|
||||||
|
CE958659112C516400F95FD2 /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE95865C112C516400F95FD2 /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE95865D112C516400F95FD2 /* StatsLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StatsLabel.m; path = ../base/StatsLabel.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE9EA7431122C96C008CD2BC /* HSGUIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSGUIController.h; sourceTree = "<group>"; };
|
CE9EA7431122C96C008CD2BC /* HSGUIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSGUIController.h; sourceTree = "<group>"; };
|
||||||
CE9EA7441122C96C008CD2BC /* HSGUIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSGUIController.m; sourceTree = "<group>"; };
|
CE9EA7441122C96C008CD2BC /* HSGUIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSGUIController.m; sourceTree = "<group>"; };
|
||||||
CE9EA7451122C96C008CD2BC /* HSOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutline.h; sourceTree = "<group>"; };
|
CE9EA7451122C96C008CD2BC /* HSOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutline.h; sourceTree = "<group>"; };
|
||||||
@@ -155,6 +168,11 @@
|
|||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
||||||
|
CEF12A7C124DFD400087B51D /* HSTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSTableView.h; path = ../../cocoalib/views/HSTableView.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CEF12A7D124DFD400087B51D /* HSTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSTableView.m; path = ../../cocoalib/views/HSTableView.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CEF12A81124DFD620087B51D /* PyResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTable.h; path = ../base/PyResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CEF12A82124DFD620087B51D /* ResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultTable.h; path = ../base/ResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CEF12A83124DFD620087B51D /* ResultTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultTable.m; path = ../base/ResultTable.m; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = ../../images/folder32.png; sourceTree = SOURCE_ROOT; };
|
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = ../../images/folder32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = ../../images/details32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = ../../images/details32.png; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = ../../images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = ../../images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -175,7 +193,7 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
080E96DDFE201D6D7F000001 /* Classes */ = {
|
080E96DDFE201D6D7F000001 /* DGPE */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CE381C9509914ACE003581CE /* AppDelegate.h */,
|
CE381C9509914ACE003581CE /* AppDelegate.h */,
|
||||||
@@ -189,7 +207,7 @@
|
|||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */,
|
CE381C9B09914ADF003581CE /* ResultWindow.h */,
|
||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */,
|
CE381C9A09914ADF003581CE /* ResultWindow.m */,
|
||||||
);
|
);
|
||||||
name = Classes;
|
name = DGPE;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
||||||
@@ -222,7 +240,7 @@
|
|||||||
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
|
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
080E96DDFE201D6D7F000001 /* Classes */,
|
080E96DDFE201D6D7F000001 /* DGPE */,
|
||||||
CE80DB1A0FC192AB0086DCA6 /* cocoalib */,
|
CE80DB1A0FC192AB0086DCA6 /* cocoalib */,
|
||||||
CE80DB810FC194BD0086DCA6 /* dgbase */,
|
CE80DB810FC194BD0086DCA6 /* dgbase */,
|
||||||
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
||||||
@@ -271,6 +289,7 @@
|
|||||||
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */,
|
CE77C8A710946CE20078B0DB /* DetailsPanel.xib */,
|
||||||
CE77C89C10946C6D0078B0DB /* DirectoryPanel.xib */,
|
CE77C89C10946C6D0078B0DB /* DirectoryPanel.xib */,
|
||||||
CE031750109B340A00517EE6 /* Preferences.xib */,
|
CE031750109B340A00517EE6 /* Preferences.xib */,
|
||||||
|
CE0C2AC71177021600BC749F /* ProblemDialog.xib */,
|
||||||
);
|
);
|
||||||
path = xib;
|
path = xib;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -278,9 +297,9 @@
|
|||||||
CE7AC9141119911200D02F6C /* xib */ = {
|
CE7AC9141119911200D02F6C /* xib */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE895D7912144A7800E74705 /* registration.xib */,
|
||||||
CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */,
|
CE7AC9151119911200D02F6C /* ErrorReportWindow.xib */,
|
||||||
CE7AC9161119911200D02F6C /* progress.xib */,
|
CE7AC9161119911200D02F6C /* progress.xib */,
|
||||||
CE7AC9171119911200D02F6C /* registration.xib */,
|
|
||||||
);
|
);
|
||||||
name = xib;
|
name = xib;
|
||||||
path = ../../cocoalib/xib;
|
path = ../../cocoalib/xib;
|
||||||
@@ -304,8 +323,6 @@
|
|||||||
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */,
|
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */,
|
||||||
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */,
|
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */,
|
||||||
CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */,
|
CE80DB1E0FC192D60086DCA6 /* HSErrorReportWindow.m */,
|
||||||
CE80DB1F0FC192D60086DCA6 /* Outline.h */,
|
|
||||||
CE80DB200FC192D60086DCA6 /* Outline.m */,
|
|
||||||
CE80DB210FC192D60086DCA6 /* ProgressController.h */,
|
CE80DB210FC192D60086DCA6 /* ProgressController.h */,
|
||||||
CE80DB220FC192D60086DCA6 /* ProgressController.m */,
|
CE80DB220FC192D60086DCA6 /* ProgressController.m */,
|
||||||
CE80DB230FC192D60086DCA6 /* PyApp.h */,
|
CE80DB230FC192D60086DCA6 /* PyApp.h */,
|
||||||
@@ -325,7 +342,9 @@
|
|||||||
CE80DB810FC194BD0086DCA6 /* dgbase */ = {
|
CE80DB810FC194BD0086DCA6 /* dgbase */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CE9EA7711122CA0B008CD2BC /* PyDirectoryOutline.h */,
|
CEF12A81124DFD620087B51D /* PyResultTable.h */,
|
||||||
|
CEF12A82124DFD620087B51D /* ResultTable.h */,
|
||||||
|
CEF12A83124DFD620087B51D /* ResultTable.m */,
|
||||||
CE80DB820FC1951C0086DCA6 /* AppDelegate.h */,
|
CE80DB820FC1951C0086DCA6 /* AppDelegate.h */,
|
||||||
CE80DB830FC1951C0086DCA6 /* AppDelegate.m */,
|
CE80DB830FC1951C0086DCA6 /* AppDelegate.m */,
|
||||||
CE80DB840FC1951C0086DCA6 /* Consts.h */,
|
CE80DB840FC1951C0086DCA6 /* Consts.h */,
|
||||||
@@ -335,10 +354,17 @@
|
|||||||
CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */,
|
CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */,
|
||||||
CE9EA76F1122CA0B008CD2BC /* DirectoryOutline.h */,
|
CE9EA76F1122CA0B008CD2BC /* DirectoryOutline.h */,
|
||||||
CE9EA7701122CA0B008CD2BC /* DirectoryOutline.m */,
|
CE9EA7701122CA0B008CD2BC /* DirectoryOutline.m */,
|
||||||
CE80DB870FC1951C0086DCA6 /* PyDupeGuru.h */,
|
CE0C2ABA1177014200BC749F /* ProblemDialog.h */,
|
||||||
CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */,
|
CE0C2ABB1177014200BC749F /* ProblemDialog.m */,
|
||||||
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */,
|
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */,
|
||||||
CE80DB890FC1951C0086DCA6 /* ResultWindow.m */,
|
CE80DB890FC1951C0086DCA6 /* ResultWindow.m */,
|
||||||
|
CE95865C112C516400F95FD2 /* StatsLabel.h */,
|
||||||
|
CE95865D112C516400F95FD2 /* StatsLabel.m */,
|
||||||
|
CE80DB870FC1951C0086DCA6 /* PyDupeGuru.h */,
|
||||||
|
CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */,
|
||||||
|
CE9EA7711122CA0B008CD2BC /* PyDirectoryOutline.h */,
|
||||||
|
CE0C2ABC1177014200BC749F /* PyProblemDialog.h */,
|
||||||
|
CE958659112C516400F95FD2 /* PyStatsLabel.h */,
|
||||||
);
|
);
|
||||||
name = dgbase;
|
name = dgbase;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -350,6 +376,8 @@
|
|||||||
CE9EA7441122C96C008CD2BC /* HSGUIController.m */,
|
CE9EA7441122C96C008CD2BC /* HSGUIController.m */,
|
||||||
CE9EA7451122C96C008CD2BC /* HSOutline.h */,
|
CE9EA7451122C96C008CD2BC /* HSOutline.h */,
|
||||||
CE9EA7461122C96C008CD2BC /* HSOutline.m */,
|
CE9EA7461122C96C008CD2BC /* HSOutline.m */,
|
||||||
|
CE0C2AB41177011000BC749F /* HSTable.h */,
|
||||||
|
CE0C2AB51177011000BC749F /* HSTable.m */,
|
||||||
CE9EA7471122C96C008CD2BC /* HSWindowController.h */,
|
CE9EA7471122C96C008CD2BC /* HSWindowController.h */,
|
||||||
CE9EA7481122C96C008CD2BC /* HSWindowController.m */,
|
CE9EA7481122C96C008CD2BC /* HSWindowController.m */,
|
||||||
);
|
);
|
||||||
@@ -362,6 +390,7 @@
|
|||||||
children = (
|
children = (
|
||||||
CE9EA74C1122C96C008CD2BC /* PyGUI.h */,
|
CE9EA74C1122C96C008CD2BC /* PyGUI.h */,
|
||||||
CE9EA74D1122C96C008CD2BC /* PyOutline.h */,
|
CE9EA74D1122C96C008CD2BC /* PyOutline.h */,
|
||||||
|
CE0C2AAA117700E700BC749F /* PyTable.h */,
|
||||||
);
|
);
|
||||||
name = proxies;
|
name = proxies;
|
||||||
path = ../../cocoalib/proxies;
|
path = ../../cocoalib/proxies;
|
||||||
@@ -370,6 +399,8 @@
|
|||||||
CE9EA74F1122C96C008CD2BC /* views */ = {
|
CE9EA74F1122C96C008CD2BC /* views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CEF12A7C124DFD400087B51D /* HSTableView.h */,
|
||||||
|
CEF12A7D124DFD400087B51D /* HSTableView.m */,
|
||||||
CE9EA7501122C96C008CD2BC /* HSOutlineView.h */,
|
CE9EA7501122C96C008CD2BC /* HSOutlineView.h */,
|
||||||
CE9EA7511122C96C008CD2BC /* HSOutlineView.m */,
|
CE9EA7511122C96C008CD2BC /* HSOutlineView.m */,
|
||||||
CE9EA7521122C96C008CD2BC /* NSIndexPathAdditions.h */,
|
CE9EA7521122C96C008CD2BC /* NSIndexPathAdditions.h */,
|
||||||
@@ -432,6 +463,13 @@
|
|||||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||||
compatibilityVersion = "Xcode 3.0";
|
compatibilityVersion = "Xcode 3.0";
|
||||||
hasScannedForEncodings = 1;
|
hasScannedForEncodings = 1;
|
||||||
|
knownRegions = (
|
||||||
|
English,
|
||||||
|
Japanese,
|
||||||
|
French,
|
||||||
|
German,
|
||||||
|
en,
|
||||||
|
);
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -460,7 +498,8 @@
|
|||||||
CE031754109B345200517EE6 /* MainMenu.xib in Resources */,
|
CE031754109B345200517EE6 /* MainMenu.xib in Resources */,
|
||||||
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */,
|
CE7AC9181119911200D02F6C /* ErrorReportWindow.xib in Resources */,
|
||||||
CE7AC9191119911200D02F6C /* progress.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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -478,7 +517,6 @@
|
|||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */,
|
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */,
|
||||||
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */,
|
CE80DB2E0FC192D60086DCA6 /* Dialogs.m in Sources */,
|
||||||
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */,
|
CE80DB2F0FC192D60086DCA6 /* HSErrorReportWindow.m in Sources */,
|
||||||
CE80DB300FC192D60086DCA6 /* Outline.m in Sources */,
|
|
||||||
CE80DB310FC192D60086DCA6 /* ProgressController.m in Sources */,
|
CE80DB310FC192D60086DCA6 /* ProgressController.m in Sources */,
|
||||||
CE80DB320FC192D60086DCA6 /* RecentDirectories.m in Sources */,
|
CE80DB320FC192D60086DCA6 /* RecentDirectories.m in Sources */,
|
||||||
CE80DB330FC192D60086DCA6 /* RegistrationInterface.m in Sources */,
|
CE80DB330FC192D60086DCA6 /* RegistrationInterface.m in Sources */,
|
||||||
@@ -499,11 +537,28 @@
|
|||||||
CE9EA75B1122C96C008CD2BC /* NSIndexPathAdditions.m in Sources */,
|
CE9EA75B1122C96C008CD2BC /* NSIndexPathAdditions.m in Sources */,
|
||||||
CE9EA75C1122C96C008CD2BC /* NSTableViewAdditions.m in Sources */,
|
CE9EA75C1122C96C008CD2BC /* NSTableViewAdditions.m in Sources */,
|
||||||
CE9EA7721122CA0B008CD2BC /* DirectoryOutline.m in Sources */,
|
CE9EA7721122CA0B008CD2BC /* DirectoryOutline.m in Sources */,
|
||||||
|
CE95865F112C516400F95FD2 /* StatsLabel.m in Sources */,
|
||||||
|
CE0C2AB61177011000BC749F /* HSTable.m in Sources */,
|
||||||
|
CE0C2ABD1177014200BC749F /* ProblemDialog.m in Sources */,
|
||||||
|
CEF12A7E124DFD400087B51D /* HSTableView.m in Sources */,
|
||||||
|
CEF12A84124DFD620087B51D /* ResultTable.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* 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 */
|
/* Begin XCBuildConfiguration section */
|
||||||
C01FCF4C08A954540054247B /* release */ = {
|
C01FCF4C08A954540054247B /* release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
|
||||||
<data>
|
<data>
|
||||||
<int key="IBDocument.SystemTarget">1050</int>
|
<int key="IBDocument.SystemTarget">1050</int>
|
||||||
<string key="IBDocument.SystemVersion">10C540</string>
|
<string key="IBDocument.SystemVersion">10F569</string>
|
||||||
<string key="IBDocument.InterfaceBuilderVersion">740</string>
|
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||||
<string key="IBDocument.AppKitVersion">1038.25</string>
|
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||||
<string key="IBDocument.HIToolboxVersion">458.00</string>
|
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
<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>
|
||||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||||
@@ -434,6 +434,7 @@
|
|||||||
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
|
||||||
<string key="NSMinSize">{451, 177}</string>
|
<string key="NSMinSize">{451, 177}</string>
|
||||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||||
|
<string key="NSFrameAutosaveName">DetailsPanel</string>
|
||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
@@ -866,6 +867,13 @@
|
|||||||
<string key="NS.key.0">detailsTable</string>
|
<string key="NS.key.0">detailsTable</string>
|
||||||
<string key="NS.object.0">NSTableView</string>
|
<string key="NS.object.0">NSTableView</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBProjectSource</string>
|
<string key="majorKey">IBProjectSource</string>
|
||||||
<string key="minorKey">../base/DetailsPanel.h</string>
|
<string key="minorKey">../base/DetailsPanel.h</string>
|
||||||
@@ -891,6 +899,35 @@
|
|||||||
<string>NSProgressIndicator</string>
|
<string>NSProgressIndicator</string>
|
||||||
</object>
|
</object>
|
||||||
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBProjectSource</string>
|
<string key="majorKey">IBProjectSource</string>
|
||||||
<string key="minorKey">DetailsPanel.h</string>
|
<string key="minorKey">DetailsPanel.h</string>
|
||||||
@@ -903,6 +940,13 @@
|
|||||||
<string key="NS.key.0">detailsTable</string>
|
<string key="NS.key.0">detailsTable</string>
|
||||||
<string key="NS.object.0">NSTableView</string>
|
<string key="NS.object.0">NSTableView</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBUserSource</string>
|
<string key="majorKey">IBUserSource</string>
|
||||||
<string key="minorKey"/>
|
<string key="minorKey"/>
|
||||||
@@ -1423,6 +1467,13 @@
|
|||||||
<string key="NS.key.0">showWindow:</string>
|
<string key="NS.key.0">showWindow:</string>
|
||||||
<string key="NS.object.0">id</string>
|
<string key="NS.object.0">id</string>
|
||||||
</object>
|
</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">
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
<string key="majorKey">IBFrameworkSource</string>
|
<string key="majorKey">IBFrameworkSource</string>
|
||||||
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
<string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
|
||||||
@@ -1431,6 +1482,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<int key="IBDocument.localizationMode">0</int>
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
<integer value="1050" key="NS.object.0"/>
|
<integer value="1050" key="NS.object.0"/>
|
||||||
@@ -1446,5 +1498,9 @@
|
|||||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
<nil key="IBDocument.LastKnownRelativeProjectPath"/>
|
<nil key="IBDocument.LastKnownRelativeProjectPath"/>
|
||||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
<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>
|
</data>
|
||||||
</archive>
|
</archive>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
||||||
[d setObject:b2n(YES) forKey:@"mixFileKind"];
|
[d setObject:b2n(YES) forKey:@"mixFileKind"];
|
||||||
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
|
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"];
|
||||||
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
|
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
|
||||||
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
|
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
|
||||||
[d setObject:b2n(NO) forKey:@"debug"];
|
[d setObject:b2n(NO) forKey:@"debug"];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>hsft</string>
|
<string>hsft</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>2.9.2</string>
|
<string>2.12.0</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../../cocoalib/Outline.h"
|
|
||||||
#import "../base/ResultWindow.h"
|
#import "../base/ResultWindow.h"
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
- (void)awakeFromNib
|
- (void)awakeFromNib
|
||||||
{
|
{
|
||||||
[super awakeFromNib];
|
[super awakeFromNib];
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,4)] retain];
|
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
|
||||||
[_deltaColumns removeIndex:3];
|
[deltaColumns addIndex:4];
|
||||||
|
[table setDeltaColumns:deltaColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -29,12 +30,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[columnsOrder addObject:@"0"];
|
[columnsOrder addObject:@"0"];
|
||||||
[columnsOrder addObject:@"1"];
|
[columnsOrder addObject:@"1"];
|
||||||
[columnsOrder addObject:@"2"];
|
[columnsOrder addObject:@"2"];
|
||||||
[columnsOrder addObject:@"6"];
|
[columnsOrder addObject:@"5"];
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
||||||
[columnsWidth setObject:i2n(195) forKey:@"0"];
|
[columnsWidth setObject:i2n(195) forKey:@"0"];
|
||||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
[columnsWidth setObject:i2n(183) forKey:@"1"];
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
||||||
[columnsWidth setObject:i2n(60) forKey:@"6"];
|
[columnsWidth setObject:i2n(60) forKey:@"5"];
|
||||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,18 +51,15 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[_py setScanType:[ud objectForKey:@"scanType"]];
|
[_py setScanType:[ud objectForKey:@"scanType"]];
|
||||||
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
||||||
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
|
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
|
||||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
[_py setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
||||||
|
[_py setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
||||||
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
||||||
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
|
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
|
||||||
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
|
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
|
||||||
[_py setSizeThreshold:sizeThreshold];
|
[_py setSizeThreshold:sizeThreshold];
|
||||||
int r = n2i([py doScan]);
|
int r = n2i([py doScan]);
|
||||||
[matches reloadData];
|
|
||||||
[self refreshStats];
|
|
||||||
if (r != 0)
|
if (r != 0)
|
||||||
[[ProgressController mainProgressController] hide];
|
[[ProgressController mainProgressController] hide];
|
||||||
if (r == 1)
|
|
||||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
|
||||||
if (r == 3)
|
if (r == 3)
|
||||||
{
|
{
|
||||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
||||||
@@ -81,10 +79,9 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
||||||
[_resultColumns addObject:sizeCol];
|
[_resultColumns addObject:sizeCol];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Creation" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Modification" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Modification" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Match %" width:60 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Match %" width:60 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Words Used" width:120 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Words Used" width:120 refCol:refCol]];
|
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Dupe Count" width:80 refCol:refCol]];
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -4,14 +4,18 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsutil.cocoa import signature
|
from hscommon.cocoa import signature
|
||||||
|
|
||||||
from core import scanner
|
from core.scanner import ScanType
|
||||||
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel
|
||||||
from core_se.app_cocoa import DupeGuru
|
from core_se.app_cocoa import DupeGuru
|
||||||
|
|
||||||
# Fix py2app imports with chokes on relative imports
|
# Fix py2app imports with chokes on relative imports and other stuff
|
||||||
from core_se import fs, data
|
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):
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
def init(self):
|
def init(self):
|
||||||
@@ -26,8 +30,8 @@ class PyDupeGuru(PyDupeGuruBase):
|
|||||||
def setScanType_(self,scan_type):
|
def setScanType_(self,scan_type):
|
||||||
try:
|
try:
|
||||||
self.py.scanner.scan_type = [
|
self.py.scanner.scan_type = [
|
||||||
scanner.SCAN_TYPE_FILENAME,
|
ScanType.Filename,
|
||||||
scanner.SCAN_TYPE_CONTENT
|
ScanType.Contents,
|
||||||
][scan_type]
|
][scan_type]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_help */; };
|
CE073F6309CAE1A3005C1D2F /* dupeguru_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_help */; };
|
||||||
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */; };
|
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */; };
|
||||||
CE19BC6411199231007CCEB0 /* progress.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE19BC6111199231007CCEB0 /* progress.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 */; };
|
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
||||||
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3A46F9109B212E002ABFD5 /* MainMenu.xib */; };
|
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3A46F9109B212E002ABFD5 /* MainMenu.xib */; };
|
||||||
CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
||||||
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
|
||||||
|
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = CE647E551173024A006D28BA /* ProblemDialog.m */; };
|
||||||
|
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE647E581173026F006D28BA /* ProblemDialog.xib */; };
|
||||||
|
CE6DD4E7124CA3070089A48D /* ResultTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6DD4E6124CA3070089A48D /* ResultTable.m */; };
|
||||||
|
CE6DD547124CAF1F0089A48D /* HSTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6DD546124CAF1F0089A48D /* HSTableView.m */; };
|
||||||
CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
|
CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
|
||||||
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
|
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
|
||||||
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */; };
|
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */; };
|
||||||
@@ -27,7 +30,10 @@
|
|||||||
CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */; };
|
CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */; };
|
||||||
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
|
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
|
||||||
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.m */; };
|
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.m */; };
|
||||||
|
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE8C53BB117324CE0011B41F /* HSTable.m */; };
|
||||||
|
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; };
|
||||||
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
|
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
|
||||||
|
CEBC6C3912144A4B007B43AE /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEBC6C3712144A4B007B43AE /* registration.xib */; };
|
||||||
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */; };
|
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */; };
|
||||||
CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */; };
|
CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */; };
|
||||||
CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7EA120FE675C80004E467 /* DetailsPanel.m */; };
|
CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7EA120FE675C80004E467 /* DetailsPanel.m */; };
|
||||||
@@ -39,7 +45,6 @@
|
|||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
||||||
CEFC7F9E0FC9517500CD5728 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F8B0FC9517500CD5728 /* Dialogs.m */; };
|
CEFC7F9E0FC9517500CD5728 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F8B0FC9517500CD5728 /* Dialogs.m */; };
|
||||||
CEFC7F9F0FC9517500CD5728 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */; };
|
CEFC7F9F0FC9517500CD5728 /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */; };
|
||||||
CEFC7FA00FC9517500CD5728 /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F8F0FC9517500CD5728 /* Outline.m */; };
|
|
||||||
CEFC7FA10FC9517500CD5728 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F910FC9517500CD5728 /* ProgressController.m */; };
|
CEFC7FA10FC9517500CD5728 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F910FC9517500CD5728 /* ProgressController.m */; };
|
||||||
CEFC7FA20FC9517500CD5728 /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F950FC9517500CD5728 /* RecentDirectories.m */; };
|
CEFC7FA20FC9517500CD5728 /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F950FC9517500CD5728 /* RecentDirectories.m */; };
|
||||||
CEFC7FA30FC9517500CD5728 /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F970FC9517500CD5728 /* RegistrationInterface.m */; };
|
CEFC7FA30FC9517500CD5728 /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CEFC7F970FC9517500CD5728 /* RegistrationInterface.m */; };
|
||||||
@@ -74,7 +79,6 @@
|
|||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_help; path = ../../help_se/dupeguru_help; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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; };
|
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; };
|
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -82,6 +86,15 @@
|
|||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
||||||
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
CE45579A0AE3BC2B005A9546 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
CE45579A0AE3BC2B005A9546 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
||||||
|
CE647E541173024A006D28BA /* ProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProblemDialog.h; path = ../base/ProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE647E551173024A006D28BA /* ProblemDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProblemDialog.m; path = ../base/ProblemDialog.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE647E561173024A006D28BA /* PyProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyProblemDialog.h; path = ../base/PyProblemDialog.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE647E581173026F006D28BA /* ProblemDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = ProblemDialog.xib; path = ../base/xib/ProblemDialog.xib; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE6DD4E4124CA3070089A48D /* PyResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTable.h; path = ../base/PyResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE6DD4E5124CA3070089A48D /* ResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultTable.h; path = ../base/ResultTable.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE6DD4E6124CA3070089A48D /* ResultTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultTable.m; path = ../base/ResultTable.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE6DD545124CAF1F0089A48D /* HSTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSTableView.h; path = ../../cocoalib/views/HSTableView.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE6DD546124CAF1F0089A48D /* HSTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSTableView.m; path = ../../cocoalib/views/HSTableView.m; sourceTree = SOURCE_ROOT; };
|
||||||
CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
|
CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
|
||||||
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE76FDBE111EE37C006618EA /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
|
CE76FDBE111EE37C006618EA /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
|
||||||
@@ -101,7 +114,13 @@
|
|||||||
CE76FDDE111EE42F006618EA /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
|
CE76FDDE111EE42F006618EA /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
|
||||||
CE76FDF5111EE561006618EA /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
|
CE76FDF5111EE561006618EA /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
|
||||||
CE76FDF6111EE561006618EA /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
|
CE76FDF6111EE561006618EA /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
CE8C53B61173248F0011B41F /* PyTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyTable.h; sourceTree = "<group>"; };
|
||||||
|
CE8C53BB117324CE0011B41F /* HSTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSTable.m; sourceTree = "<group>"; };
|
||||||
|
CE91F210113BC22D0010360B /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
CEDD92D60FDD01640031C7B7 /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -118,8 +137,6 @@
|
|||||||
CEFC7F8B0FC9517500CD5728 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
CEFC7F8B0FC9517500CD5728 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC7F8C0FC9517500CD5728 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
CEFC7F8C0FC9517500CD5728 /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
|
CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC7F8E0FC9517500CD5728 /* Outline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Outline.h; path = ../../cocoalib/Outline.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEFC7F8F0FC9517500CD5728 /* Outline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Outline.m; path = ../../cocoalib/Outline.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEFC7F900FC9517500CD5728 /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
|
CEFC7F900FC9517500CD5728 /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC7F910FC9517500CD5728 /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
|
CEFC7F910FC9517500CD5728 /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
|
||||||
CEFC7F920FC9517500CD5728 /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
|
CEFC7F920FC9517500CD5728 /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -235,9 +252,9 @@
|
|||||||
CE19BC5F11199231007CCEB0 /* xib */ = {
|
CE19BC5F11199231007CCEB0 /* xib */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CEBC6C3712144A4B007B43AE /* registration.xib */,
|
||||||
CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */,
|
CE19BC6011199231007CCEB0 /* ErrorReportWindow.xib */,
|
||||||
CE19BC6111199231007CCEB0 /* progress.xib */,
|
CE19BC6111199231007CCEB0 /* progress.xib */,
|
||||||
CE19BC6211199231007CCEB0 /* registration.xib */,
|
|
||||||
);
|
);
|
||||||
name = xib;
|
name = xib;
|
||||||
path = ../../cocoalib/xib;
|
path = ../../cocoalib/xib;
|
||||||
@@ -246,6 +263,8 @@
|
|||||||
CE76FDBD111EE37C006618EA /* views */ = {
|
CE76FDBD111EE37C006618EA /* views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE6DD545124CAF1F0089A48D /* HSTableView.h */,
|
||||||
|
CE6DD546124CAF1F0089A48D /* HSTableView.m */,
|
||||||
CE76FDBE111EE37C006618EA /* HSOutlineView.h */,
|
CE76FDBE111EE37C006618EA /* HSOutlineView.h */,
|
||||||
CE76FDBF111EE37C006618EA /* HSOutlineView.m */,
|
CE76FDBF111EE37C006618EA /* HSOutlineView.m */,
|
||||||
CE76FDC0111EE37C006618EA /* NSIndexPathAdditions.h */,
|
CE76FDC0111EE37C006618EA /* NSIndexPathAdditions.h */,
|
||||||
@@ -266,6 +285,7 @@
|
|||||||
CE76FDDE111EE42F006618EA /* HSOutline.m */,
|
CE76FDDE111EE42F006618EA /* HSOutline.m */,
|
||||||
CE76FDC8111EE38E006618EA /* HSGUIController.h */,
|
CE76FDC8111EE38E006618EA /* HSGUIController.h */,
|
||||||
CE76FDC9111EE38E006618EA /* HSGUIController.m */,
|
CE76FDC9111EE38E006618EA /* HSGUIController.m */,
|
||||||
|
CE8C53BB117324CE0011B41F /* HSTable.m */,
|
||||||
);
|
);
|
||||||
name = controllers;
|
name = controllers;
|
||||||
path = ../../cocoalib/controllers;
|
path = ../../cocoalib/controllers;
|
||||||
@@ -276,6 +296,7 @@
|
|||||||
children = (
|
children = (
|
||||||
CE76FDCD111EE38E006618EA /* PyGUI.h */,
|
CE76FDCD111EE38E006618EA /* PyGUI.h */,
|
||||||
CE76FDCE111EE38E006618EA /* PyOutline.h */,
|
CE76FDCE111EE38E006618EA /* PyOutline.h */,
|
||||||
|
CE8C53B61173248F0011B41F /* PyTable.h */,
|
||||||
);
|
);
|
||||||
name = proxies;
|
name = proxies;
|
||||||
path = ../../cocoalib/proxies;
|
path = ../../cocoalib/proxies;
|
||||||
@@ -294,6 +315,7 @@
|
|||||||
CEEFC0CA10943849001F3A39 /* xib */ = {
|
CEEFC0CA10943849001F3A39 /* xib */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE647E581173026F006D28BA /* ProblemDialog.xib */,
|
||||||
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */,
|
CE3A46F9109B212E002ABFD5 /* MainMenu.xib */,
|
||||||
CEAC6810109B0B7E00B43C85 /* Preferences.xib */,
|
CEAC6810109B0B7E00B43C85 /* Preferences.xib */,
|
||||||
CEEFC0F710945D9F001F3A39 /* DirectoryPanel.xib */,
|
CEEFC0F710945D9F001F3A39 /* DirectoryPanel.xib */,
|
||||||
@@ -326,8 +348,6 @@
|
|||||||
CEFC7F8B0FC9517500CD5728 /* Dialogs.m */,
|
CEFC7F8B0FC9517500CD5728 /* Dialogs.m */,
|
||||||
CEFC7F8C0FC9517500CD5728 /* HSErrorReportWindow.h */,
|
CEFC7F8C0FC9517500CD5728 /* HSErrorReportWindow.h */,
|
||||||
CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */,
|
CEFC7F8D0FC9517500CD5728 /* HSErrorReportWindow.m */,
|
||||||
CEFC7F8E0FC9517500CD5728 /* Outline.h */,
|
|
||||||
CEFC7F8F0FC9517500CD5728 /* Outline.m */,
|
|
||||||
CEFC7F900FC9517500CD5728 /* ProgressController.h */,
|
CEFC7F900FC9517500CD5728 /* ProgressController.h */,
|
||||||
CEFC7F910FC9517500CD5728 /* ProgressController.m */,
|
CEFC7F910FC9517500CD5728 /* ProgressController.m */,
|
||||||
CEFC7F920FC9517500CD5728 /* PyApp.h */,
|
CEFC7F920FC9517500CD5728 /* PyApp.h */,
|
||||||
@@ -347,6 +367,12 @@
|
|||||||
CEFC7FB00FC9518F00CD5728 /* dgbase */ = {
|
CEFC7FB00FC9518F00CD5728 /* dgbase */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CE6DD4E4124CA3070089A48D /* PyResultTable.h */,
|
||||||
|
CE6DD4E5124CA3070089A48D /* ResultTable.h */,
|
||||||
|
CE6DD4E6124CA3070089A48D /* ResultTable.m */,
|
||||||
|
CE91F210113BC22D0010360B /* PyStatsLabel.h */,
|
||||||
|
CE91F213113BC22D0010360B /* StatsLabel.h */,
|
||||||
|
CE91F214113BC22D0010360B /* StatsLabel.m */,
|
||||||
CE76FDD1111EE3A7006618EA /* DirectoryOutline.h */,
|
CE76FDD1111EE3A7006618EA /* DirectoryOutline.h */,
|
||||||
CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */,
|
CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */,
|
||||||
CE76FDD3111EE3A7006618EA /* PyDirectoryOutline.h */,
|
CE76FDD3111EE3A7006618EA /* PyDirectoryOutline.h */,
|
||||||
@@ -361,6 +387,9 @@
|
|||||||
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */,
|
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */,
|
||||||
CEFC7FB70FC951A700CD5728 /* ResultWindow.h */,
|
CEFC7FB70FC951A700CD5728 /* ResultWindow.h */,
|
||||||
CEFC7FB80FC951A700CD5728 /* ResultWindow.m */,
|
CEFC7FB80FC951A700CD5728 /* ResultWindow.m */,
|
||||||
|
CE647E541173024A006D28BA /* ProblemDialog.h */,
|
||||||
|
CE647E551173024A006D28BA /* ProblemDialog.m */,
|
||||||
|
CE647E561173024A006D28BA /* PyProblemDialog.h */,
|
||||||
);
|
);
|
||||||
name = dgbase;
|
name = dgbase;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -398,6 +427,13 @@
|
|||||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
||||||
compatibilityVersion = "Xcode 3.0";
|
compatibilityVersion = "Xcode 3.0";
|
||||||
hasScannedForEncodings = 1;
|
hasScannedForEncodings = 1;
|
||||||
|
knownRegions = (
|
||||||
|
English,
|
||||||
|
Japanese,
|
||||||
|
French,
|
||||||
|
German,
|
||||||
|
en,
|
||||||
|
);
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -425,7 +461,8 @@
|
|||||||
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */,
|
CE3A46FA109B212E002ABFD5 /* MainMenu.xib in Resources */,
|
||||||
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */,
|
CE19BC6311199231007CCEB0 /* ErrorReportWindow.xib in Resources */,
|
||||||
CE19BC6411199231007CCEB0 /* progress.xib in Resources */,
|
CE19BC6411199231007CCEB0 /* progress.xib in Resources */,
|
||||||
CE19BC6511199231007CCEB0 /* registration.xib in Resources */,
|
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */,
|
||||||
|
CEBC6C3912144A4B007B43AE /* registration.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -441,7 +478,6 @@
|
|||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */,
|
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */,
|
||||||
CEFC7F9E0FC9517500CD5728 /* Dialogs.m in Sources */,
|
CEFC7F9E0FC9517500CD5728 /* Dialogs.m in Sources */,
|
||||||
CEFC7F9F0FC9517500CD5728 /* HSErrorReportWindow.m in Sources */,
|
CEFC7F9F0FC9517500CD5728 /* HSErrorReportWindow.m in Sources */,
|
||||||
CEFC7FA00FC9517500CD5728 /* Outline.m in Sources */,
|
|
||||||
CEFC7FA10FC9517500CD5728 /* ProgressController.m in Sources */,
|
CEFC7FA10FC9517500CD5728 /* ProgressController.m in Sources */,
|
||||||
CEFC7FA20FC9517500CD5728 /* RecentDirectories.m in Sources */,
|
CEFC7FA20FC9517500CD5728 /* RecentDirectories.m in Sources */,
|
||||||
CEFC7FA30FC9517500CD5728 /* RegistrationInterface.m in Sources */,
|
CEFC7FA30FC9517500CD5728 /* RegistrationInterface.m in Sources */,
|
||||||
@@ -460,11 +496,28 @@
|
|||||||
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */,
|
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */,
|
||||||
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */,
|
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */,
|
||||||
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
|
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
|
||||||
|
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */,
|
||||||
|
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */,
|
||||||
|
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */,
|
||||||
|
CE6DD4E7124CA3070089A48D /* ResultTable.m in Sources */,
|
||||||
|
CE6DD547124CAF1F0089A48D /* HSTableView.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* 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 */
|
/* Begin XCBuildConfiguration section */
|
||||||
C01FCF4C08A954540054247B /* release */ = {
|
C01FCF4C08A954540054247B /* release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ def main(edition, ui, dev):
|
|||||||
if ui not in ('cocoa', 'qt'):
|
if ui not in ('cocoa', 'qt'):
|
||||||
ui = 'cocoa' if sys.platform == 'darwin' else 'qt'
|
ui = 'cocoa' if sys.platform == 'darwin' else 'qt'
|
||||||
build_type = 'Dev' if dev else 'Release'
|
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 = {
|
conf = {
|
||||||
'edition': edition,
|
'edition': edition,
|
||||||
'ui': ui,
|
'ui': ui,
|
||||||
|
|||||||
159
core/app.py
159
core/app.py
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Created By: Virgil Dupras
|
# Created By: Virgil Dupras
|
||||||
# Created On: 2006/11/11
|
# Created On: 2006/11/11
|
||||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
@@ -7,17 +6,18 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path as op
|
import os.path as op
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
from send2trash import send2trash
|
||||||
|
from hscommon.reg import RegistrableApplication, RegistrationRequired
|
||||||
|
from hscommon.notify import Broadcaster
|
||||||
from hsutil import io, files
|
from hsutil import io, files
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.reg import RegistrableApplication, RegistrationRequired
|
|
||||||
from hsutil.misc import flatten, first
|
from hsutil.misc import flatten, first
|
||||||
from hsutil.notify import Broadcaster
|
|
||||||
from hsutil.str import escape
|
from hsutil.str import escape
|
||||||
|
|
||||||
from . import directories, results, scanner, export, fs
|
from . import directories, results, scanner, export, fs
|
||||||
@@ -31,9 +31,6 @@ JOB_DELETE = 'job_delete'
|
|||||||
class NoScannableFileError(Exception):
|
class NoScannableFileError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class AllFilesAreRefError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DupeGuru(RegistrableApplication, Broadcaster):
|
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."
|
DEMO_LIMIT_DESC = "In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied."
|
||||||
|
|
||||||
@@ -48,10 +45,10 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self.results = results.Results(data_module)
|
self.results = results.Results(data_module)
|
||||||
self.scanner = scanner.Scanner()
|
self.scanner = scanner.Scanner()
|
||||||
self.action_count = 0
|
self.action_count = 0
|
||||||
self.last_op_error_count = 0
|
|
||||||
self.options = {
|
self.options = {
|
||||||
'escape_filter_regexp': True,
|
'escape_filter_regexp': True,
|
||||||
'clean_empty_dirs': False,
|
'clean_empty_dirs': False,
|
||||||
|
'ignore_hardlink_matches': False,
|
||||||
}
|
}
|
||||||
self.selected_dupes = []
|
self.selected_dupes = []
|
||||||
|
|
||||||
@@ -64,32 +61,28 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
else:
|
else:
|
||||||
self.action_count += count
|
self.action_count += count
|
||||||
|
|
||||||
def _do_delete(self, j):
|
def _do_delete(self, j, replace_with_hardlinks):
|
||||||
def op(dupe):
|
def op(dupe):
|
||||||
j.add_progress()
|
j.add_progress()
|
||||||
return self._do_delete_dupe(dupe)
|
return self._do_delete_dupe(dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
j.start_job(self.results.mark_count)
|
j.start_job(self.results.mark_count)
|
||||||
self.last_op_error_count = self.results.perform_on_marked(op, True)
|
self.results.perform_on_marked(op, True)
|
||||||
|
|
||||||
def _do_delete_dupe(self, dupe):
|
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
|
||||||
if not io.exists(dupe.path):
|
if not io.exists(dupe.path):
|
||||||
return True
|
return
|
||||||
self._recycle_dupe(dupe)
|
send2trash(str(dupe.path)) # Raises OSError when there's a problem
|
||||||
|
if replace_with_hardlinks:
|
||||||
|
group = self.results.get_group_of_duplicate(dupe)
|
||||||
|
ref = group.ref
|
||||||
|
os.link(str(ref.path), str(dupe.path))
|
||||||
self.clean_empty_dirs(dupe.path[:-1])
|
self.clean_empty_dirs(dupe.path[:-1])
|
||||||
if not io.exists(dupe.path):
|
|
||||||
return True
|
|
||||||
logging.warning("Could not send {0} to trash.".format(unicode(dupe.path)))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _do_load(self, j):
|
def _do_load(self, j):
|
||||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
||||||
self.notify('directories_changed')
|
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)
|
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)
|
|
||||||
for file in j.iter_with_progress(files, 'Reading metadata %d/%d'):
|
|
||||||
file._read_all_info(attrnames=self.data.METADATA_TO_READ)
|
|
||||||
|
|
||||||
def _get_display_info(self, dupe, group, delta=False):
|
def _get_display_info(self, dupe, group, delta=False):
|
||||||
if (dupe is None) or (group is None):
|
if (dupe is None) or (group is None):
|
||||||
@@ -97,29 +90,46 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
try:
|
try:
|
||||||
return self.data.GetDisplayInfo(dupe, group, delta)
|
return self.data.GetDisplayInfo(dupe, group, delta)
|
||||||
except Exception as e:
|
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)
|
return ['---'] * len(self.data.COLUMNS)
|
||||||
|
|
||||||
def _get_file(self, str_path):
|
def _get_file(self, str_path):
|
||||||
path = Path(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):
|
def _job_completed(self, jobid):
|
||||||
# Must be called by subclasses when they detect that an async job is completed.
|
# Must be called by subclasses when they detect that an async job is completed.
|
||||||
if jobid in (JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_DELETE):
|
if jobid == JOB_SCAN:
|
||||||
self.notify('results_changed')
|
self.notify('results_changed')
|
||||||
|
elif jobid in (JOB_LOAD, JOB_MOVE, JOB_DELETE):
|
||||||
|
self.notify('results_changed')
|
||||||
|
self.notify('problems_changed')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _open_path(path):
|
def _open_path(path):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _recycle_dupe(dupe):
|
def _reveal_path(path):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _reveal_path(path):
|
def _remove_hardlink_dupes(files):
|
||||||
raise NotImplementedError()
|
seen_inodes = set()
|
||||||
|
result = []
|
||||||
|
for file in files:
|
||||||
|
inode = io.stat(file.path).st_ino
|
||||||
|
if inode not in seen_inodes:
|
||||||
|
seen_inodes.add(inode)
|
||||||
|
result.append(file)
|
||||||
|
return result
|
||||||
|
|
||||||
def _select_dupes(self, dupes):
|
def _select_dupes(self, dupes):
|
||||||
if dupes == self.selected_dupes:
|
if dupes == self.selected_dupes:
|
||||||
@@ -127,8 +137,8 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
self.selected_dupes = dupes
|
self.selected_dupes = dupes
|
||||||
self.notify('dupes_selected')
|
self.notify('dupes_selected')
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
def _start_job(self, jobid, func, *args):
|
||||||
# func(j)
|
# func(j, *args)
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def add_directory(self, d):
|
def add_directory(self, d):
|
||||||
@@ -147,7 +157,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
g = self.results.get_group_of_duplicate(dupe)
|
g = self.results.get_group_of_duplicate(dupe)
|
||||||
for other in g:
|
for other in g:
|
||||||
if other is not dupe:
|
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)
|
self.remove_duplicates(dupes)
|
||||||
|
|
||||||
def apply_filter(self, filter):
|
def apply_filter(self, filter):
|
||||||
@@ -178,40 +188,35 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
dest_path = dest_path + source_path[1:-1] #Remove drive letter and filename
|
dest_path = dest_path + source_path[1:-1] #Remove drive letter and filename
|
||||||
elif dest_type == 1:
|
elif dest_type == 1:
|
||||||
dest_path = dest_path + source_path[location_path:-1]
|
dest_path = dest_path + source_path[location_path:-1]
|
||||||
try:
|
if not io.exists(dest_path):
|
||||||
if not io.exists(dest_path):
|
io.makedirs(dest_path)
|
||||||
io.makedirs(dest_path)
|
# Raises an EnvironmentError if there's a problem
|
||||||
if copy:
|
if copy:
|
||||||
files.copy(source_path, dest_path)
|
files.copy(source_path, dest_path)
|
||||||
else:
|
else:
|
||||||
files.move(source_path, dest_path)
|
files.move(source_path, dest_path)
|
||||||
self.clean_empty_dirs(source_path[:-1])
|
self.clean_empty_dirs(source_path[:-1])
|
||||||
except EnvironmentError as e:
|
|
||||||
operation = 'Copy' if copy else 'Move'
|
|
||||||
logging.warning('%s operation failed on %s. Error: %s' % (operation, unicode(dupe.path), unicode(e)))
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def copy_or_move_marked(self, copy, destination, recreate_path):
|
def copy_or_move_marked(self, copy, destination, recreate_path):
|
||||||
def do(j):
|
def do(j):
|
||||||
def op(dupe):
|
def op(dupe):
|
||||||
j.add_progress()
|
j.add_progress()
|
||||||
return self.copy_or_move(dupe, copy, destination, recreate_path)
|
self.copy_or_move(dupe, copy, destination, recreate_path)
|
||||||
|
|
||||||
j.start_job(self.results.mark_count)
|
j.start_job(self.results.mark_count)
|
||||||
self.last_op_error_count = self.results.perform_on_marked(op, not copy)
|
self.results.perform_on_marked(op, not copy)
|
||||||
|
|
||||||
self._demo_check()
|
self._demo_check()
|
||||||
jobid = JOB_COPY if copy else JOB_MOVE
|
jobid = JOB_COPY if copy else JOB_MOVE
|
||||||
self._start_job(jobid, do)
|
self._start_job(jobid, do)
|
||||||
|
|
||||||
def delete_marked(self):
|
def delete_marked(self, replace_with_hardlinks=False):
|
||||||
self._demo_check()
|
self._demo_check()
|
||||||
self._start_job(JOB_DELETE, self._do_delete)
|
self._start_job(JOB_DELETE, self._do_delete, replace_with_hardlinks)
|
||||||
|
|
||||||
def export_to_xhtml(self, column_ids):
|
def export_to_xhtml(self, column_ids):
|
||||||
column_ids = [colid for colid in column_ids if colid.isdigit()]
|
column_ids = [colid for colid in column_ids if colid.isdigit()]
|
||||||
column_ids = map(int, column_ids)
|
column_ids = list(map(int, column_ids))
|
||||||
column_ids.sort()
|
column_ids.sort()
|
||||||
colnames = [col['display'] for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
|
colnames = [col['display'] for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
|
||||||
rows = []
|
rows = []
|
||||||
@@ -223,10 +228,40 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
rows.append(row)
|
rows.append(row)
|
||||||
return export.export_to_xhtml(colnames, rows)
|
return export.export_to_xhtml(colnames, rows)
|
||||||
|
|
||||||
|
def invoke_command(self, cmd):
|
||||||
|
"""Calls command `cmd` with %d and %r placeholders replaced.
|
||||||
|
|
||||||
|
Using the current selection, %d is replaced with the currently selected dupe and %r is
|
||||||
|
replaced with that dupe's ref file. If there's no selection, the command is not invoked.
|
||||||
|
If the dupe is a ref, %d and %r will be the same.
|
||||||
|
"""
|
||||||
|
if not self.selected_dupes:
|
||||||
|
return
|
||||||
|
dupe = self.selected_dupes[0]
|
||||||
|
group = self.results.get_group_of_duplicate(dupe)
|
||||||
|
ref = group.ref
|
||||||
|
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,
|
||||||
|
# executable paths with spaces in it, *even* when they're enclosed in "". So this is
|
||||||
|
# a workaround to make the damn thing work.
|
||||||
|
exepath, args = match.groups()
|
||||||
|
path, exename = op.split(exepath)
|
||||||
|
subprocess.Popen(exename + args, shell=True, cwd=path)
|
||||||
|
else:
|
||||||
|
subprocess.Popen(cmd, shell=True)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
self._start_job(JOB_LOAD, self._do_load)
|
self._start_job(JOB_LOAD, self._do_load)
|
||||||
self.load_ignore_list()
|
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):
|
def load_ignore_list(self):
|
||||||
p = op.join(self.appdata, 'ignore_list.xml')
|
p = op.join(self.appdata, 'ignore_list.xml')
|
||||||
self.scanner.ignore_list.load_from_xml(p)
|
self.scanner.ignore_list.load_from_xml(p)
|
||||||
@@ -239,7 +274,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
if g not in changed_groups:
|
if g not in changed_groups:
|
||||||
self.results.make_ref(dupe)
|
self.results.make_ref(dupe)
|
||||||
changed_groups.add(g)
|
changed_groups.add(g)
|
||||||
self.notify('results_switched')
|
self.notify('results_changed_but_keep_selection')
|
||||||
|
|
||||||
def mark_all(self):
|
def mark_all(self):
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
@@ -276,10 +311,10 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
|
|
||||||
def remove_duplicates(self, duplicates):
|
def remove_duplicates(self, duplicates):
|
||||||
self.results.remove_duplicates(self.without_ref(duplicates))
|
self.results.remove_duplicates(self.without_ref(duplicates))
|
||||||
self.notify('results_changed')
|
self.notify('results_changed_but_keep_selection')
|
||||||
|
|
||||||
def remove_marked(self):
|
def remove_marked(self):
|
||||||
self.results.perform_on_marked(lambda x:True, True)
|
self.results.perform_on_marked(lambda x:None, True)
|
||||||
self.notify('results_changed')
|
self.notify('results_changed')
|
||||||
|
|
||||||
def remove_selected(self):
|
def remove_selected(self):
|
||||||
@@ -291,7 +326,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
d.rename(newname)
|
d.rename(newname)
|
||||||
return True
|
return True
|
||||||
except (IndexError, fs.FSError) as e:
|
except (IndexError, fs.FSError) as e:
|
||||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
logging.warning("dupeGuru Warning: %s" % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reveal_selected(self):
|
def reveal_selected(self):
|
||||||
@@ -302,7 +337,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
if not op.exists(self.appdata):
|
if not op.exists(self.appdata):
|
||||||
os.makedirs(self.appdata)
|
os.makedirs(self.appdata)
|
||||||
self.directories.save_to_file(op.join(self.appdata, 'last_directories.xml'))
|
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):
|
def save_ignore_list(self):
|
||||||
if not op.exists(self.appdata):
|
if not op.exists(self.appdata):
|
||||||
@@ -314,15 +355,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
|||||||
def do(j):
|
def do(j):
|
||||||
j.set_progress(0, 'Collecting files to scan')
|
j.set_progress(0, 'Collecting files to scan')
|
||||||
files = list(self.directories.get_files())
|
files = list(self.directories.get_files())
|
||||||
|
if self.options['ignore_hardlink_matches']:
|
||||||
|
files = self._remove_hardlink_dupes(files)
|
||||||
logging.info('Scanning %d files' % len(files))
|
logging.info('Scanning %d files' % len(files))
|
||||||
self.results.groups = self.scanner.GetDupeGroups(files, j)
|
self.results.groups = self.scanner.GetDupeGroups(files, j)
|
||||||
|
|
||||||
files = self.directories.get_files()
|
if not self.directories.has_any_file():
|
||||||
first_file = first(files)
|
|
||||||
if first_file is None:
|
|
||||||
raise NoScannableFileError()
|
raise NoScannableFileError()
|
||||||
if first_file.is_ref and all(f.is_ref for f in files):
|
|
||||||
raise AllFilesAreRefError()
|
|
||||||
self.results.groups = []
|
self.results.groups = []
|
||||||
self._start_job(JOB_SCAN, do)
|
self._start_job(JOB_SCAN, do)
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
import logging
|
import logging
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
|
||||||
from hsutil import cocoa, job
|
from hscommon import cocoa, job
|
||||||
from hsutil.cocoa import install_exception_hook
|
from hscommon.cocoa import install_exception_hook
|
||||||
from hsutil.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults,
|
from hscommon.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults,
|
||||||
NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask,
|
NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask,
|
||||||
NSWorkspace, NSWorkspaceRecycleOperation)
|
NSWorkspace)
|
||||||
from hsutil.reg import RegistrationRequired
|
from hscommon.reg import RegistrationRequired
|
||||||
|
|
||||||
from . import app, fs
|
from . import app
|
||||||
|
|
||||||
JOBID2TITLE = {
|
JOBID2TITLE = {
|
||||||
app.JOB_SCAN: "Scanning for duplicates",
|
app.JOB_SCAN: "Scanning for duplicates",
|
||||||
@@ -49,24 +49,17 @@ class DupeGuru(app.DupeGuru):
|
|||||||
#--- Override
|
#--- Override
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _open_path(path):
|
def _open_path(path):
|
||||||
NSWorkspace.sharedWorkspace().openFile_(unicode(path))
|
NSWorkspace.sharedWorkspace().openFile_(str(path))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _recycle_dupe(dupe):
|
|
||||||
# local import because first appkit import takes a lot of memory. we want to avoid it.
|
|
||||||
directory = unicode(dupe.path[:-1])
|
|
||||||
filename = dupe.name
|
|
||||||
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
|
|
||||||
NSWorkspaceRecycleOperation, directory, '', [filename], None)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _reveal_path(path):
|
def _reveal_path(path):
|
||||||
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(unicode(path), '')
|
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(str(path), '')
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
def _start_job(self, jobid, func, *args):
|
||||||
try:
|
try:
|
||||||
j = self.progress.create_job()
|
j = self.progress.create_job()
|
||||||
self.progress.run_threaded(func, args=(j, ))
|
args = tuple([j] + list(args))
|
||||||
|
self.progress.run_threaded(func, args=args)
|
||||||
except job.JobInProgressError:
|
except job.JobInProgressError:
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self)
|
NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self)
|
||||||
else:
|
else:
|
||||||
@@ -84,6 +77,4 @@ class DupeGuru(app.DupeGuru):
|
|||||||
return 0
|
return 0
|
||||||
except app.NoScannableFileError:
|
except app.NoScannableFileError:
|
||||||
return 3
|
return 3
|
||||||
except app.AllFilesAreRefError:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
|
|
||||||
# Common interface for all editions' dg_cocoa unit.
|
# Common interface for all editions' dg_cocoa unit.
|
||||||
|
|
||||||
from hsutil.cocoa.inter import signature, PyOutline, PyGUIObject, PyRegistrable
|
from hscommon.cocoa.inter import signature, PyTable, PyOutline, PyGUIObject, PyRegistrable
|
||||||
|
|
||||||
from .gui.details_panel import DetailsPanel
|
from .gui.details_panel import DetailsPanel
|
||||||
from .gui.directory_tree import DirectoryTree
|
from .gui.directory_tree import DirectoryTree
|
||||||
from .gui.result_tree import ResultTree
|
from .gui.problem_dialog import ProblemDialog
|
||||||
|
from .gui.problem_table import ProblemTable
|
||||||
|
from .gui.result_table import ResultTable
|
||||||
from .gui.stats_label import StatsLabel
|
from .gui.stats_label import StatsLabel
|
||||||
|
|
||||||
# Fix py2app's problems on relative imports
|
# Fix py2app's problems on relative imports
|
||||||
@@ -44,6 +46,9 @@ class PyDupeGuruBase(PyRegistrable):
|
|||||||
def loadResults(self):
|
def loadResults(self):
|
||||||
self.py.load()
|
self.py.load()
|
||||||
|
|
||||||
|
def loadResultsFrom_(self, filename):
|
||||||
|
self.py.load_from(filename)
|
||||||
|
|
||||||
def markAll(self):
|
def markAll(self):
|
||||||
self.py.mark_all()
|
self.py.mark_all()
|
||||||
|
|
||||||
@@ -65,6 +70,9 @@ class PyDupeGuruBase(PyRegistrable):
|
|||||||
def saveResults(self):
|
def saveResults(self):
|
||||||
self.py.save()
|
self.py.save()
|
||||||
|
|
||||||
|
def saveResultsAs_(self, filename):
|
||||||
|
self.py.save_as(filename)
|
||||||
|
|
||||||
#---Actions
|
#---Actions
|
||||||
def addSelectedToIgnoreList(self):
|
def addSelectedToIgnoreList(self):
|
||||||
self.py.add_selected_to_ignore_list()
|
self.py.add_selected_to_ignore_list()
|
||||||
@@ -72,6 +80,9 @@ class PyDupeGuruBase(PyRegistrable):
|
|||||||
def deleteMarked(self):
|
def deleteMarked(self):
|
||||||
self.py.delete_marked()
|
self.py.delete_marked()
|
||||||
|
|
||||||
|
def hardlinkMarked(self):
|
||||||
|
self.py.delete_marked(replace_with_hardlinks=True)
|
||||||
|
|
||||||
def applyFilter_(self, filter):
|
def applyFilter_(self, filter):
|
||||||
self.py.apply_filter(filter)
|
self.py.apply_filter(filter)
|
||||||
|
|
||||||
@@ -93,6 +104,9 @@ class PyDupeGuruBase(PyRegistrable):
|
|||||||
def revealSelected(self):
|
def revealSelected(self):
|
||||||
self.py.reveal_selected()
|
self.py.reveal_selected()
|
||||||
|
|
||||||
|
def invokeCommand_(self, cmd):
|
||||||
|
self.py.invoke_command(cmd)
|
||||||
|
|
||||||
#---Information
|
#---Information
|
||||||
def getIgnoreListCount(self):
|
def getIgnoreListCount(self):
|
||||||
return len(self.py.scanner.ignore_list)
|
return len(self.py.scanner.ignore_list)
|
||||||
@@ -100,22 +114,36 @@ class PyDupeGuruBase(PyRegistrable):
|
|||||||
def getMarkCount(self):
|
def getMarkCount(self):
|
||||||
return self.py.results.mark_count
|
return self.py.results.mark_count
|
||||||
|
|
||||||
def getOperationalErrorCount(self):
|
@signature('i@:')
|
||||||
return self.py.last_op_error_count
|
def scanWasProblematic(self):
|
||||||
|
return bool(self.py.results.problems)
|
||||||
|
|
||||||
#---Properties
|
#---Properties
|
||||||
|
@signature('v@:c')
|
||||||
def setMixFileKind_(self, mix_file_kind):
|
def setMixFileKind_(self, mix_file_kind):
|
||||||
self.py.scanner.mix_file_kind = mix_file_kind
|
self.py.scanner.mix_file_kind = mix_file_kind
|
||||||
|
|
||||||
|
@signature('v@:c')
|
||||||
def setEscapeFilterRegexp_(self, escape_filter_regexp):
|
def setEscapeFilterRegexp_(self, escape_filter_regexp):
|
||||||
self.py.options['escape_filter_regexp'] = escape_filter_regexp
|
self.py.options['escape_filter_regexp'] = escape_filter_regexp
|
||||||
|
|
||||||
|
@signature('v@:c')
|
||||||
def setRemoveEmptyFolders_(self, remove_empty_folders):
|
def setRemoveEmptyFolders_(self, remove_empty_folders):
|
||||||
self.py.options['clean_empty_dirs'] = remove_empty_folders
|
self.py.options['clean_empty_dirs'] = remove_empty_folders
|
||||||
|
|
||||||
|
@signature('v@:c')
|
||||||
|
def setIgnoreHardlinkMatches_(self, ignore_hardlink_matches):
|
||||||
|
self.py.options['ignore_hardlink_matches'] = ignore_hardlink_matches
|
||||||
|
|
||||||
#---Worker
|
#---Worker
|
||||||
def getJobProgress(self):
|
def getJobProgress(self):
|
||||||
return self.py.progress.last_progress
|
try:
|
||||||
|
return self.py.progress.last_progress
|
||||||
|
except AttributeError:
|
||||||
|
# I have *no idea* why this can possible happen (last_progress is always set by
|
||||||
|
# create_job() *before* any threaded job notification, which shows the progress panel,
|
||||||
|
# is sent), but it happens anyway, so there we go.
|
||||||
|
return -1
|
||||||
|
|
||||||
def getJobDesc(self):
|
def getJobDesc(self):
|
||||||
return self.py.progress.last_desc
|
return self.py.progress.last_desc
|
||||||
@@ -145,8 +173,8 @@ class PyDirectoryOutline(PyOutline):
|
|||||||
self.py.add_directory(path)
|
self.py.add_directory(path)
|
||||||
|
|
||||||
|
|
||||||
class PyResultOutline(PyOutline):
|
class PyResultTable(PyTable):
|
||||||
py_class = ResultTree
|
py_class = ResultTable
|
||||||
|
|
||||||
@signature('c@:')
|
@signature('c@:')
|
||||||
def powerMarkerMode(self):
|
def powerMarkerMode(self):
|
||||||
@@ -164,9 +192,9 @@ class PyResultOutline(PyOutline):
|
|||||||
def setDeltaValuesMode_(self, value):
|
def setDeltaValuesMode_(self, value):
|
||||||
self.py.delta_values = value
|
self.py.delta_values = value
|
||||||
|
|
||||||
@signature('@@:@i')
|
@signature('@@:ii')
|
||||||
def valueForPath_column_(self, path, column):
|
def valueForRow_column_(self, row_index, column):
|
||||||
return self.py.get_node_value(path, column)
|
return self.py.get_row_value(row_index, column)
|
||||||
|
|
||||||
@signature('c@:@')
|
@signature('c@:@')
|
||||||
def renameSelected_(self, newname):
|
def renameSelected_(self, newname):
|
||||||
@@ -182,8 +210,9 @@ class PyResultOutline(PyOutline):
|
|||||||
def removeSelected(self):
|
def removeSelected(self):
|
||||||
self.py.app.remove_selected()
|
self.py.app.remove_selected()
|
||||||
|
|
||||||
def rootChildrenCounts(self):
|
@signature('i@:')
|
||||||
return self.py.root_children_counts()
|
def selectedDupeCount(self):
|
||||||
|
return self.py.selected_dupe_count
|
||||||
|
|
||||||
# python --> cocoa
|
# python --> cocoa
|
||||||
def invalidate_markings(self):
|
def invalidate_markings(self):
|
||||||
@@ -196,3 +225,13 @@ class PyStatsLabel(PyGUIObject):
|
|||||||
def display(self):
|
def display(self):
|
||||||
return self.py.display
|
return self.py.display
|
||||||
|
|
||||||
|
|
||||||
|
class PyProblemDialog(PyGUIObject):
|
||||||
|
py_class = ProblemDialog
|
||||||
|
|
||||||
|
def revealSelected(self):
|
||||||
|
self.py.reveal_selected_dupe()
|
||||||
|
|
||||||
|
|
||||||
|
class PyProblemTable(PyTable):
|
||||||
|
py_class = ProblemTable
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from hsutil.str import format_time, FT_DECIMAL, format_size
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
def format_path(p):
|
def format_path(p):
|
||||||
return unicode(p[:-1])
|
return str(p[:-1])
|
||||||
|
|
||||||
def format_timestamp(t, delta):
|
def format_timestamp(t, delta):
|
||||||
if delta:
|
if delta:
|
||||||
@@ -38,4 +38,4 @@ def format_dupe_count(c):
|
|||||||
return str(c) if c else '---'
|
return str(c) if c else '---'
|
||||||
|
|
||||||
def cmp_value(value):
|
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
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import xml.dom.minidom
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from hsutil import io
|
from hsutil import io
|
||||||
from hsutil.files import FileOrPath
|
from hsutil.files import FileOrPath
|
||||||
@@ -124,40 +124,47 @@ class Directories(object):
|
|||||||
else:
|
else:
|
||||||
return STATE_NORMAL
|
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):
|
def load_from_file(self, infile):
|
||||||
try:
|
try:
|
||||||
doc = xml.dom.minidom.parse(infile)
|
root = ET.parse(infile).getroot()
|
||||||
except:
|
except Exception:
|
||||||
return
|
return
|
||||||
root_path_nodes = doc.getElementsByTagName('root_directory')
|
for rdn in root.getiterator('root_directory'):
|
||||||
for rdn in root_path_nodes:
|
attrib = rdn.attrib
|
||||||
if not rdn.getAttributeNode('path'):
|
if 'path' not in attrib:
|
||||||
continue
|
continue
|
||||||
path = rdn.getAttributeNode('path').nodeValue
|
path = attrib['path']
|
||||||
try:
|
try:
|
||||||
self.add_path(Path(path))
|
self.add_path(Path(path))
|
||||||
except (AlreadyThereError, InvalidPathError):
|
except (AlreadyThereError, InvalidPathError):
|
||||||
pass
|
pass
|
||||||
state_nodes = doc.getElementsByTagName('state')
|
for sn in root.getiterator('state'):
|
||||||
for sn in state_nodes:
|
attrib = sn.attrib
|
||||||
if not (sn.getAttributeNode('path') and sn.getAttributeNode('value')):
|
if not ('path' in attrib and 'value' in attrib):
|
||||||
continue
|
continue
|
||||||
path = sn.getAttributeNode('path').nodeValue
|
path = attrib['path']
|
||||||
state = sn.getAttributeNode('value').nodeValue
|
state = attrib['value']
|
||||||
self.set_state(Path(path), int(state))
|
self.set_state(Path(path), int(state))
|
||||||
|
|
||||||
def save_to_file(self,outfile):
|
def save_to_file(self, outfile):
|
||||||
with FileOrPath(outfile, 'wb') as fp:
|
with FileOrPath(outfile, 'wb') as fp:
|
||||||
doc = xml.dom.minidom.Document()
|
root = ET.Element('directories')
|
||||||
root = doc.appendChild(doc.createElement('directories'))
|
|
||||||
for root_path in self:
|
for root_path in self:
|
||||||
root_path_node = root.appendChild(doc.createElement('root_directory'))
|
root_path_node = ET.SubElement(root, 'root_directory')
|
||||||
root_path_node.setAttribute('path', unicode(root_path).encode('utf-8'))
|
root_path_node.set('path', str(root_path))
|
||||||
for path, state in self.states.iteritems():
|
for path, state in self.states.items():
|
||||||
state_node = root.appendChild(doc.createElement('state'))
|
state_node = ET.SubElement(root, 'state')
|
||||||
state_node.setAttribute('path', unicode(path).encode('utf-8'))
|
state_node.set('path', str(path))
|
||||||
state_node.setAttribute('value', str(state))
|
state_node.set('value', str(state))
|
||||||
doc.writexml(fp, '\t', '\t', '\n', encoding='utf-8')
|
tree = ET.ElementTree(root)
|
||||||
|
tree.write(fp, encoding='utf-8')
|
||||||
|
|
||||||
def set_state(self, path, state):
|
def set_state(self, path, state):
|
||||||
if self.get_state(path) == state:
|
if self.get_state(path) == state:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
import difflib
|
import difflib
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
@@ -16,7 +16,7 @@ from unicodedata import normalize
|
|||||||
|
|
||||||
from hsutil.misc import flatten
|
from hsutil.misc import flatten
|
||||||
from hsutil.str import multi_replace
|
from hsutil.str import multi_replace
|
||||||
from hsutil import job
|
from hscommon import job
|
||||||
|
|
||||||
(WEIGHT_WORDS,
|
(WEIGHT_WORDS,
|
||||||
MATCH_SIMILAR_WORDS,
|
MATCH_SIMILAR_WORDS,
|
||||||
@@ -25,15 +25,15 @@ NO_FIELD_ORDER) = range(3)
|
|||||||
JOB_REFRESH_RATE = 100
|
JOB_REFRESH_RATE = 100
|
||||||
|
|
||||||
def getwords(s):
|
def getwords(s):
|
||||||
if isinstance(s, unicode):
|
if isinstance(s, str):
|
||||||
s = normalize('NFD', s)
|
s = normalize('NFD', s)
|
||||||
s = multi_replace(s, "-_&+():;\\[]{}.,<>/?~!@#$*", ' ').lower()
|
s = multi_replace(s, "-_&+():;\\[]{}.,<>/?~!@#$*", ' ').lower()
|
||||||
s = ''.join(c for c in s if c in string.ascii_letters + string.digits + string.whitespace)
|
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):
|
def getfields(s):
|
||||||
fields = [getwords(field) for field in s.split(' - ')]
|
fields = [getwords(field) for field in s.split(' - ')]
|
||||||
return filter(None, fields)
|
return [_f for _f in fields if _f]
|
||||||
|
|
||||||
def unpack_fields(fields):
|
def unpack_fields(fields):
|
||||||
result = []
|
result = []
|
||||||
@@ -118,7 +118,7 @@ def build_word_dict(objects, j=job.nulljob):
|
|||||||
def merge_similar_words(word_dict):
|
def merge_similar_words(word_dict):
|
||||||
"""Take all keys in word_dict that are similar, and merge them together.
|
"""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
|
keys.sort(key=len)# we want the shortest word to stay
|
||||||
while keys:
|
while keys:
|
||||||
key = keys.pop(0)
|
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!
|
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)
|
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:
|
if len(objects) < threshold:
|
||||||
continue
|
continue
|
||||||
reduced = set()
|
reduced = set()
|
||||||
|
|||||||
@@ -6,14 +6,13 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import tempfile
|
|
||||||
import os.path as op
|
import os.path as op
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
# Yes, this is a very low-tech solution, but at least it doesn't have all these annoying dependency
|
# Yes, this is a very low-tech solution, but at least it doesn't have all these annoying dependency
|
||||||
# and resource problems.
|
# and resource problems.
|
||||||
|
|
||||||
MAIN_TEMPLATE = u"""
|
MAIN_TEMPLATE = """
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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'>
|
<!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">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
@@ -104,34 +103,34 @@ $rows
|
|||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
COLHEADERS_TEMPLATE = u"<th>{name}</th>"
|
COLHEADERS_TEMPLATE = "<th>{name}</th>"
|
||||||
|
|
||||||
ROW_TEMPLATE = u"""
|
ROW_TEMPLATE = """
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{indented}">{filename}</td>{cells}
|
<td class="{indented}">{filename}</td>{cells}
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CELL_TEMPLATE = u"""<td>{value}</td>"""
|
CELL_TEMPLATE = """<td>{value}</td>"""
|
||||||
|
|
||||||
def export_to_xhtml(colnames, rows):
|
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
|
# a row is a list of values with the first value being a flag indicating if the row should be indented
|
||||||
if rows:
|
if rows:
|
||||||
assert len(rows[0]) == len(colnames) + 1 # + 1 is for the "indented" flag
|
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 = []
|
rendered_rows = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
# [2:] is to remove the indented flag + filename
|
# [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]
|
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.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 {}
|
# The main template can't use format because the css code uses {}
|
||||||
content = MAIN_TEMPLATE.replace('$colheaders', colheaders).replace('$rows', rendered_rows)
|
content = MAIN_TEMPLATE.replace('$colheaders', colheaders).replace('$rows', rendered_rows)
|
||||||
folder = mkdtemp()
|
folder = mkdtemp()
|
||||||
destpath = op.join(folder, u'export.htm')
|
destpath = op.join(folder, 'export.htm')
|
||||||
fp = open(destpath, 'w')
|
fp = open(destpath, 'wt', encoding='utf-8')
|
||||||
fp.write(content.encode('utf-8'))
|
fp.write(content)
|
||||||
fp.close()
|
fp.close()
|
||||||
return destpath
|
return destpath
|
||||||
|
|||||||
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,
|
# resulting needless complexity and memory usage. It's been a while since I wanted to do that fork,
|
||||||
# and I'm doing it now.
|
# and I'm doing it now.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@@ -25,13 +25,13 @@ class FSError(Exception):
|
|||||||
cls_message = "An error has occured on '{name}' in '{parent}'"
|
cls_message = "An error has occured on '{name}' in '{parent}'"
|
||||||
def __init__(self, fsobject, parent=None):
|
def __init__(self, fsobject, parent=None):
|
||||||
message = self.cls_message
|
message = self.cls_message
|
||||||
if isinstance(fsobject, basestring):
|
if isinstance(fsobject, str):
|
||||||
name = fsobject
|
name = fsobject
|
||||||
elif isinstance(fsobject, File):
|
elif isinstance(fsobject, File):
|
||||||
name = fsobject.name
|
name = fsobject.name
|
||||||
else:
|
else:
|
||||||
name = ''
|
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))
|
Exception.__init__(self, message.format(name=name, parent=parentname))
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +55,6 @@ class OperationError(FSError):
|
|||||||
class File(object):
|
class File(object):
|
||||||
INITIAL_INFO = {
|
INITIAL_INFO = {
|
||||||
'size': 0,
|
'size': 0,
|
||||||
'ctime': 0,
|
|
||||||
'mtime': 0,
|
'mtime': 0,
|
||||||
'md5': '',
|
'md5': '',
|
||||||
'md5partial': '',
|
'md5partial': '',
|
||||||
@@ -82,10 +81,9 @@ class File(object):
|
|||||||
raise AttributeError()
|
raise AttributeError()
|
||||||
|
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field in ('size', 'ctime', 'mtime'):
|
if field in ('size', 'mtime'):
|
||||||
stats = io.stat(self.path)
|
stats = io.stat(self.path)
|
||||||
self.size = nonone(stats.st_size, 0)
|
self.size = nonone(stats.st_size, 0)
|
||||||
self.ctime = nonone(stats.st_ctime, 0)
|
|
||||||
self.mtime = nonone(stats.st_mtime, 0)
|
self.mtime = nonone(stats.st_mtime, 0)
|
||||||
elif field == 'md5partial':
|
elif field == 'md5partial':
|
||||||
try:
|
try:
|
||||||
@@ -119,7 +117,7 @@ class File(object):
|
|||||||
If `attrnames` is not None, caches only attrnames.
|
If `attrnames` is not None, caches only attrnames.
|
||||||
"""
|
"""
|
||||||
if attrnames is None:
|
if attrnames is None:
|
||||||
attrnames = self.INITIAL_INFO.keys()
|
attrnames = list(self.INITIAL_INFO.keys())
|
||||||
for attrname in attrnames:
|
for attrname in attrnames:
|
||||||
if attrname not in self.__dict__:
|
if attrname not in self.__dict__:
|
||||||
self._read_info(attrname)
|
self._read_info(attrname)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsutil.notify import Listener
|
from hscommon.notify import Listener
|
||||||
|
|
||||||
class GUIObject(Listener):
|
class GUIObject(Listener):
|
||||||
def __init__(self, view, app):
|
def __init__(self, view, app):
|
||||||
@@ -24,9 +24,12 @@ class GUIObject(Listener):
|
|||||||
def marking_changed(self):
|
def marking_changed(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def problems_changed(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def results_changed(self):
|
def results_changed(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def results_switched(self):
|
def results_changed_but_keep_selection(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class DetailsPanel(GUIObject):
|
|||||||
ref = group.ref if group is not None and group.ref is not dupe else None
|
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)
|
l2 = self.app._get_display_info(ref, group, False)
|
||||||
names = [c['display'] for c in self.app.data.COLUMNS]
|
names = [c['display'] for c in self.app.data.COLUMNS]
|
||||||
self._table = zip(names, l1, l2)
|
self._table = list(zip(names, l1, l2))
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def row_count(self):
|
def row_count(self):
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class DirectoryTree(GUIObject, Tree):
|
|||||||
def _refresh(self):
|
def _refresh(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
for path in self.app.directories:
|
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):
|
def add_directory(self, path):
|
||||||
self.app.add_directory(path)
|
self.app.add_directory(path)
|
||||||
|
|||||||
31
core/gui/problem_dialog.py
Normal file
31
core/gui/problem_dialog.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-04-12
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
|
from hscommon.notify import Broadcaster
|
||||||
|
|
||||||
|
from .base import GUIObject
|
||||||
|
|
||||||
|
class ProblemDialog(GUIObject, Broadcaster):
|
||||||
|
def __init__(self, view, app):
|
||||||
|
GUIObject.__init__(self, view, app)
|
||||||
|
Broadcaster.__init__(self)
|
||||||
|
self._selected_dupe = None
|
||||||
|
|
||||||
|
def reveal_selected_dupe(self):
|
||||||
|
if self._selected_dupe is not None:
|
||||||
|
self.app._reveal_path(self._selected_dupe.path)
|
||||||
|
|
||||||
|
def select_dupe(self, dupe):
|
||||||
|
self._selected_dupe = dupe
|
||||||
|
|
||||||
|
#--- Event Handlers
|
||||||
|
def problems_changed(self):
|
||||||
|
self._selected_dupe = None
|
||||||
|
self.notify('problems_changed')
|
||||||
|
|
||||||
43
core/gui/problem_table.py
Normal file
43
core/gui/problem_table.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-04-12
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
|
from hscommon.notify import Listener
|
||||||
|
from hsgui.table import GUITable, Row
|
||||||
|
|
||||||
|
class ProblemTable(GUITable, Listener):
|
||||||
|
def __init__(self, view, problem_dialog):
|
||||||
|
GUITable.__init__(self)
|
||||||
|
Listener.__init__(self, problem_dialog)
|
||||||
|
self.view = view
|
||||||
|
self.dialog = problem_dialog
|
||||||
|
|
||||||
|
#--- Override
|
||||||
|
def _update_selection(self):
|
||||||
|
row = self.selected_row
|
||||||
|
dupe = row.dupe if row is not None else None
|
||||||
|
self.dialog.select_dupe(dupe)
|
||||||
|
|
||||||
|
def _fill(self):
|
||||||
|
problems = self.dialog.app.results.problems
|
||||||
|
for dupe, msg in problems:
|
||||||
|
self.append(ProblemRow(self, dupe, msg))
|
||||||
|
|
||||||
|
#--- Event handlers
|
||||||
|
def problems_changed(self):
|
||||||
|
self.refresh()
|
||||||
|
self.view.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
class ProblemRow(Row):
|
||||||
|
def __init__(self, table, dupe, msg):
|
||||||
|
Row.__init__(self, table)
|
||||||
|
self.dupe = dupe
|
||||||
|
self.msg = msg
|
||||||
|
self.path = str(dupe.path)
|
||||||
|
|
||||||
@@ -9,14 +9,14 @@
|
|||||||
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from hsgui.tree import Tree, Node
|
from hsgui.table import GUITable, Row
|
||||||
|
|
||||||
from .base import GUIObject
|
from .base import GUIObject
|
||||||
|
|
||||||
class DupeNode(Node):
|
class DupeRow(Row):
|
||||||
def __init__(self, app, group, dupe):
|
def __init__(self, table, group, dupe):
|
||||||
Node.__init__(self, '')
|
Row.__init__(self, table)
|
||||||
self._app = app
|
self._app = table.app
|
||||||
self._group = group
|
self._group = group
|
||||||
self._dupe = dupe
|
self._dupe = dupe
|
||||||
self._data = None
|
self._data = None
|
||||||
@@ -34,6 +34,10 @@ class DupeNode(Node):
|
|||||||
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
|
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
|
||||||
return self._data_delta
|
return self._data_delta
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isref(self):
|
||||||
|
return self._dupe is self._group.ref
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def markable(self):
|
def markable(self):
|
||||||
return self._app.results.is_markable(self._dupe)
|
return self._app.results.is_markable(self._dupe)
|
||||||
@@ -47,10 +51,10 @@ class DupeNode(Node):
|
|||||||
self._app.mark_dupe(self._dupe, value)
|
self._app.mark_dupe(self._dupe, value)
|
||||||
|
|
||||||
|
|
||||||
class ResultTree(GUIObject, Tree):
|
class ResultTable(GUIObject, GUITable):
|
||||||
def __init__(self, view, app):
|
def __init__(self, view, app):
|
||||||
GUIObject.__init__(self, view, app)
|
GUIObject.__init__(self, view, app)
|
||||||
Tree.__init__(self)
|
GUITable.__init__(self)
|
||||||
self._power_marker = False
|
self._power_marker = False
|
||||||
self._delta_values = False
|
self._delta_values = False
|
||||||
self._sort_descriptors = (0, True)
|
self._sort_descriptors = (0, True)
|
||||||
@@ -58,61 +62,58 @@ class ResultTree(GUIObject, Tree):
|
|||||||
#--- Override
|
#--- Override
|
||||||
def connect(self):
|
def connect(self):
|
||||||
GUIObject.connect(self)
|
GUIObject.connect(self)
|
||||||
self._refresh()
|
self._refresh_with_view()
|
||||||
self.view.refresh()
|
|
||||||
|
|
||||||
def _select_nodes(self, nodes):
|
def _restore_selection(self, previous_selection):
|
||||||
Tree._select_nodes(self, nodes)
|
if self.app.selected_dupes:
|
||||||
self.app._select_dupes(map(attrgetter('_dupe'), nodes))
|
to_find = set(self.app.selected_dupes)
|
||||||
|
indexes = [i for i, r in enumerate(self) if r._dupe in to_find]
|
||||||
|
self.selected_indexes = indexes
|
||||||
|
|
||||||
#--- Private
|
def _update_selection(self):
|
||||||
def _refresh(self):
|
rows = self.selected_rows
|
||||||
self.clear()
|
self.app._select_dupes(list(map(attrgetter('_dupe'), rows)))
|
||||||
|
|
||||||
|
def _fill(self):
|
||||||
if not self.power_marker:
|
if not self.power_marker:
|
||||||
for group in self.app.results.groups:
|
for group in self.app.results.groups:
|
||||||
group_node = DupeNode(self.app, group, group.ref)
|
self.append(DupeRow(self, group, group.ref))
|
||||||
self.append(group_node)
|
|
||||||
for dupe in group.dupes:
|
for dupe in group.dupes:
|
||||||
group_node.append(DupeNode(self.app, group, dupe))
|
self.append(DupeRow(self, group, dupe))
|
||||||
else:
|
else:
|
||||||
for dupe in self.app.results.dupes:
|
for dupe in self.app.results.dupes:
|
||||||
group = self.app.results.get_group_of_duplicate(dupe)
|
group = self.app.results.get_group_of_duplicate(dupe)
|
||||||
self.append(DupeNode(self.app, group, dupe))
|
self.append(DupeRow(self, group, dupe))
|
||||||
if self.app.selected_dupes:
|
|
||||||
to_find = set(self.app.selected_dupes)
|
def _refresh_with_view(self):
|
||||||
nodes = list(self.findall(lambda n: n is not self and n._dupe in to_find))
|
self.refresh()
|
||||||
self.selected_nodes = nodes
|
self.view.refresh()
|
||||||
|
self.view.show_selected_row()
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def get_node_value(self, path, column):
|
def get_row_value(self, index, column):
|
||||||
try:
|
try:
|
||||||
node = self.get_node(path)
|
row = self[index]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return '---'
|
return '---'
|
||||||
if self.delta_values:
|
if self.delta_values:
|
||||||
return node.data_delta[column]
|
return row.data_delta[column]
|
||||||
else:
|
else:
|
||||||
return node.data[column]
|
return row.data[column]
|
||||||
|
|
||||||
def rename_selected(self, newname):
|
def rename_selected(self, newname):
|
||||||
node = self.selected_node
|
row = self.selected_row
|
||||||
node._data = None
|
row._data = None
|
||||||
node._data_delta = None
|
row._data_delta = None
|
||||||
return self.app.rename_selected(newname)
|
return self.app.rename_selected(newname)
|
||||||
|
|
||||||
def root_children_counts(self):
|
|
||||||
# This is a speed optimization for cases where there's a lot of results so that there is
|
|
||||||
# not thousands of children_count queries when expandAll is called.
|
|
||||||
return [len(node) for node in self]
|
|
||||||
|
|
||||||
def sort(self, key, asc):
|
def sort(self, key, asc):
|
||||||
if self.power_marker:
|
if self.power_marker:
|
||||||
self.app.results.sort_dupes(key, asc, self.delta_values)
|
self.app.results.sort_dupes(key, asc, self.delta_values)
|
||||||
else:
|
else:
|
||||||
self.app.results.sort_groups(key, asc)
|
self.app.results.sort_groups(key, asc)
|
||||||
self._sort_descriptors = (key, asc)
|
self._sort_descriptors = (key, asc)
|
||||||
self._refresh()
|
self._refresh_with_view()
|
||||||
self.view.refresh()
|
|
||||||
|
|
||||||
#--- Properties
|
#--- Properties
|
||||||
@property
|
@property
|
||||||
@@ -126,8 +127,7 @@ class ResultTree(GUIObject, Tree):
|
|||||||
self._power_marker = value
|
self._power_marker = value
|
||||||
key, asc = self._sort_descriptors
|
key, asc = self._sort_descriptors
|
||||||
self.sort(key, asc)
|
self.sort(key, asc)
|
||||||
self._refresh()
|
# no need to refresh, it has happened in sort()
|
||||||
self.view.refresh()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def delta_values(self):
|
def delta_values(self):
|
||||||
@@ -138,22 +138,25 @@ class ResultTree(GUIObject, Tree):
|
|||||||
if value == self._delta_values:
|
if value == self._delta_values:
|
||||||
return
|
return
|
||||||
self._delta_values = value
|
self._delta_values = value
|
||||||
self._refresh()
|
self.refresh()
|
||||||
self.view.refresh()
|
self.view.refresh()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_dupe_count(self):
|
||||||
|
return sum(1 for row in self.selected_rows if not row.isref)
|
||||||
|
|
||||||
#--- Event Handlers
|
#--- Event Handlers
|
||||||
def marking_changed(self):
|
def marking_changed(self):
|
||||||
self.view.invalidate_markings()
|
self.view.invalidate_markings()
|
||||||
|
|
||||||
def results_changed(self):
|
def results_changed(self):
|
||||||
self._refresh()
|
self._refresh_with_view()
|
||||||
self.view.refresh()
|
|
||||||
|
|
||||||
def results_switched(self):
|
def results_changed_but_keep_selection(self):
|
||||||
# What we want to to here is that instead of restoring selected *dupes* after refresh, we
|
# What we want to to here is that instead of restoring selected *dupes* after refresh, we
|
||||||
# restore selected *paths*.
|
# restore selected *paths*.
|
||||||
paths = self.selected_paths
|
indexes = self.selected_indexes
|
||||||
self._refresh()
|
self.refresh()
|
||||||
self.selected_paths = paths
|
self.select(indexes)
|
||||||
self.view.refresh()
|
self.view.refresh()
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsutil.files import FileOrPath
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
import xml.dom.minidom
|
from hsutil.files import FileOrPath
|
||||||
|
|
||||||
class IgnoreList(object):
|
class IgnoreList(object):
|
||||||
"""An ignore list implementation that is iterable, filterable and exportable to XML.
|
"""An ignore list implementation that is iterable, filterable and exportable to XML.
|
||||||
@@ -22,7 +22,7 @@ class IgnoreList(object):
|
|||||||
self._count = 0
|
self._count = 0
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for first,seconds in self._ignored.iteritems():
|
for first,seconds in self._ignored.items():
|
||||||
for second in seconds:
|
for second in seconds:
|
||||||
yield (first,second)
|
yield (first,second)
|
||||||
|
|
||||||
@@ -71,45 +71,40 @@ class IgnoreList(object):
|
|||||||
self._ignored[first] = matches
|
self._ignored[first] = matches
|
||||||
self._count += 1
|
self._count += 1
|
||||||
|
|
||||||
def load_from_xml(self,infile):
|
def load_from_xml(self, infile):
|
||||||
"""Loads the ignore list from a XML created with save_to_xml.
|
"""Loads the ignore list from a XML created with save_to_xml.
|
||||||
|
|
||||||
infile can be a file object or a filename.
|
infile can be a file object or a filename.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
doc = xml.dom.minidom.parse(infile)
|
root = ET.parse(infile).getroot()
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
file_nodes = doc.getElementsByTagName('file')
|
file_elems = (e for e in root if e.tag == 'file')
|
||||||
for fn in file_nodes:
|
for fn in file_elems:
|
||||||
if not fn.getAttributeNode('path'):
|
file_path = fn.get('path')
|
||||||
|
if not file_path:
|
||||||
continue
|
continue
|
||||||
file_path = fn.getAttributeNode('path').nodeValue
|
subfile_elems = (e for e in fn if e.tag == 'file')
|
||||||
subfile_nodes = fn.getElementsByTagName('file')
|
for sfn in subfile_elems:
|
||||||
for sfn in subfile_nodes:
|
subfile_path = sfn.get('path')
|
||||||
if not sfn.getAttributeNode('path'):
|
if subfile_path:
|
||||||
continue
|
self.Ignore(file_path, subfile_path)
|
||||||
subfile_path = sfn.getAttributeNode('path').nodeValue
|
|
||||||
self.Ignore(file_path,subfile_path)
|
|
||||||
|
|
||||||
def save_to_xml(self,outfile):
|
def save_to_xml(self, outfile):
|
||||||
"""Create a XML file that can be used by load_from_xml.
|
"""Create a XML file that can be used by load_from_xml.
|
||||||
|
|
||||||
outfile can be a file object or a filename.
|
outfile can be a file object or a filename.
|
||||||
"""
|
"""
|
||||||
doc = xml.dom.minidom.Document()
|
root = ET.Element('ignore_list')
|
||||||
root = doc.appendChild(doc.createElement('ignore_list'))
|
for filename, subfiles in self._ignored.items():
|
||||||
for file,subfiles in self._ignored.items():
|
file_node = ET.SubElement(root, 'file')
|
||||||
file_node = root.appendChild(doc.createElement('file'))
|
file_node.set('path', filename)
|
||||||
if isinstance(file,unicode):
|
for subfilename in subfiles:
|
||||||
file = file.encode('utf-8')
|
subfile_node = ET.SubElement(file_node, 'file')
|
||||||
file_node.setAttribute('path',file)
|
subfile_node.set('path', subfilename)
|
||||||
for subfile in subfiles:
|
tree = ET.ElementTree(root)
|
||||||
subfile_node = file_node.appendChild(doc.createElement('file'))
|
|
||||||
if isinstance(subfile,unicode):
|
|
||||||
subfile = subfile.encode('utf-8')
|
|
||||||
subfile_node.setAttribute('path',subfile)
|
|
||||||
with FileOrPath(outfile, 'wb') as fp:
|
with FileOrPath(outfile, 'wb') as fp:
|
||||||
doc.writexml(fp,'\t','\t','\n',encoding='utf-8')
|
tree.write(fp, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
221
core/results.py
221
core/results.py
@@ -8,16 +8,14 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from xml.sax import handler, make_parser, SAXException
|
from xml.etree import ElementTree as ET
|
||||||
from xml.sax.saxutils import XMLGenerator
|
|
||||||
from xml.sax.xmlreader import AttributesImpl
|
|
||||||
|
|
||||||
from . import engine
|
from . import engine
|
||||||
from hsutil.job import nulljob
|
from hscommon.job import nulljob
|
||||||
from hsutil.markable import Markable
|
from hscommon.markable import Markable
|
||||||
from hsutil.misc import flatten, cond, nonone
|
from hsutil.misc import flatten, nonone
|
||||||
from hsutil.str import format_size
|
from hsutil.str import format_size
|
||||||
from hsutil.files import open_if_filename
|
from hsutil.files import FileOrPath
|
||||||
|
|
||||||
class Results(Markable):
|
class Results(Markable):
|
||||||
#---Override
|
#---Override
|
||||||
@@ -34,6 +32,8 @@ class Results(Markable):
|
|||||||
self.__recalculate_stats()
|
self.__recalculate_stats()
|
||||||
self.__marked_size = 0
|
self.__marked_size = 0
|
||||||
self.data = data_module
|
self.data = data_module
|
||||||
|
self.problems = [] # (dupe, error_msg)
|
||||||
|
self.is_modified = False
|
||||||
|
|
||||||
def _did_mark(self, dupe):
|
def _did_mark(self, dupe):
|
||||||
self.__marked_size += dupe.size
|
self.__marked_size += dupe.size
|
||||||
@@ -116,6 +116,7 @@ class Results(Markable):
|
|||||||
self.__group_of_duplicate[dupe] = g
|
self.__group_of_duplicate[dupe] = g
|
||||||
if not hasattr(dupe, 'is_ref'):
|
if not hasattr(dupe, 'is_ref'):
|
||||||
dupe.is_ref = False
|
dupe.is_ref = False
|
||||||
|
self.is_modified = True
|
||||||
old_filters = nonone(self.__filters, [])
|
old_filters = nonone(self.__filters, [])
|
||||||
self.apply_filter(None)
|
self.apply_filter(None)
|
||||||
for filter_str in old_filters:
|
for filter_str in old_filters:
|
||||||
@@ -148,7 +149,7 @@ class Results(Markable):
|
|||||||
self.__filters.append(filter_str)
|
self.__filters.append(filter_str)
|
||||||
if self.__filtered_dupes is None:
|
if self.__filtered_dupes is None:
|
||||||
self.__filtered_dupes = flatten(g[:] for g in self.groups)
|
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(dupe.name))
|
self.__filtered_dupes = set(dupe for dupe in self.__filtered_dupes if filter_re.search(str(dupe.path)))
|
||||||
filtered_groups = set()
|
filtered_groups = set()
|
||||||
for dupe in self.__filtered_dupes:
|
for dupe in self.__filtered_dupes:
|
||||||
filtered_groups.add(self.get_group_of_duplicate(dupe))
|
filtered_groups.add(self.get_group_of_duplicate(dupe))
|
||||||
@@ -168,43 +169,56 @@ class Results(Markable):
|
|||||||
is_markable = _is_markable
|
is_markable = _is_markable
|
||||||
|
|
||||||
def load_from_xml(self, infile, get_file, j=nulljob):
|
def load_from_xml(self, infile, get_file, j=nulljob):
|
||||||
|
def do_match(ref_file, other_files, group):
|
||||||
|
if not other_files:
|
||||||
|
return
|
||||||
|
for other_file in other_files:
|
||||||
|
group.add_match(engine.get_match(ref_file, other_file))
|
||||||
|
do_match(other_files[0], other_files[1:], group)
|
||||||
|
|
||||||
self.apply_filter(None)
|
self.apply_filter(None)
|
||||||
handler = _ResultsHandler(get_file)
|
|
||||||
try:
|
try:
|
||||||
parser = make_parser()
|
root = ET.parse(infile).getroot()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# This special handling is to try to figure out the cause of #47
|
|
||||||
# We don't silently return, because we want the user to send error report.
|
|
||||||
logging.exception(e)
|
|
||||||
try:
|
|
||||||
import xml.parsers.expat
|
|
||||||
logging.warning('importing xml.parsers.expat went ok, WTF?')
|
|
||||||
except Exception as e:
|
|
||||||
# This log should give a little more details about the cause of this all
|
|
||||||
logging.exception(e)
|
|
||||||
raise
|
|
||||||
raise
|
|
||||||
parser.setContentHandler(handler)
|
|
||||||
try:
|
|
||||||
infile, must_close = open_if_filename(infile)
|
|
||||||
except IOError:
|
|
||||||
return
|
return
|
||||||
BUFSIZE = 1024 * 1024 # 1mb buffer
|
group_elems = list(root.getiterator('group'))
|
||||||
infile.seek(0, 2)
|
groups = []
|
||||||
j.start_job(infile.tell() // BUFSIZE)
|
marked = set()
|
||||||
infile.seek(0, 0)
|
for group_elem in j.iter_with_progress(group_elems, every=100):
|
||||||
try:
|
group = engine.Group()
|
||||||
while True:
|
dupes = []
|
||||||
data = infile.read(BUFSIZE)
|
for file_elem in group_elem.getiterator('file'):
|
||||||
if not data:
|
path = file_elem.get('path')
|
||||||
break
|
words = file_elem.get('words', '')
|
||||||
parser.feed(data)
|
if not path:
|
||||||
j.add_progress()
|
continue
|
||||||
except SAXException:
|
file = get_file(path)
|
||||||
return
|
if file is None:
|
||||||
self.groups = handler.groups
|
continue
|
||||||
for dupe_file in handler.marked:
|
file.words = words.split(',')
|
||||||
|
file.is_ref = file_elem.get('is_ref') == 'y'
|
||||||
|
dupes.append(file)
|
||||||
|
if file_elem.get('marked') == 'y':
|
||||||
|
marked.add(file)
|
||||||
|
for match_elem in group_elem.getiterator('match'):
|
||||||
|
try:
|
||||||
|
attrs = match_elem.attrib
|
||||||
|
first_file = dupes[int(attrs['first'])]
|
||||||
|
second_file = dupes[int(attrs['second'])]
|
||||||
|
percentage = int(attrs['percentage'])
|
||||||
|
group.add_match(engine.Match(first_file, second_file, percentage))
|
||||||
|
except (IndexError, KeyError, ValueError): # Covers missing attr, non-int values and indexes out of bounds
|
||||||
|
pass
|
||||||
|
if (not group.matches) and (len(dupes) >= 2):
|
||||||
|
do_match(dupes[0], dupes[1:], group)
|
||||||
|
group.prioritize(lambda x: dupes.index(x))
|
||||||
|
if len(group):
|
||||||
|
groups.append(group)
|
||||||
|
j.add_progress()
|
||||||
|
self.groups = groups
|
||||||
|
for dupe_file in marked:
|
||||||
self.mark(dupe_file)
|
self.mark(dupe_file)
|
||||||
|
self.is_modified = False
|
||||||
|
|
||||||
def make_ref(self, dupe):
|
def make_ref(self, dupe):
|
||||||
g = self.get_group_of_duplicate(dupe)
|
g = self.get_group_of_duplicate(dupe)
|
||||||
@@ -218,19 +232,25 @@ class Results(Markable):
|
|||||||
self.__total_count -= 1
|
self.__total_count -= 1
|
||||||
self.__total_size -= dupe.size
|
self.__total_size -= dupe.size
|
||||||
self.__dupes = None
|
self.__dupes = None
|
||||||
|
self.is_modified = True
|
||||||
|
|
||||||
def perform_on_marked(self, func, remove_from_results):
|
def perform_on_marked(self, func, remove_from_results):
|
||||||
problems = []
|
# Performs `func` on all marked dupes. If an EnvironmentError is raised during the call,
|
||||||
for d in self.dupes:
|
# the problematic dupe is added to self.problems.
|
||||||
if self.is_marked(d) and (not func(d)):
|
self.problems = []
|
||||||
problems.append(d)
|
to_remove = []
|
||||||
|
marked = (dupe for dupe in self.dupes if self.is_marked(dupe))
|
||||||
|
for dupe in marked:
|
||||||
|
try:
|
||||||
|
func(dupe)
|
||||||
|
to_remove.append(dupe)
|
||||||
|
except EnvironmentError as e:
|
||||||
|
self.problems.append((dupe, str(e)))
|
||||||
if remove_from_results:
|
if remove_from_results:
|
||||||
to_remove = [d for d in self.dupes if self.is_marked(d) and (d not in problems)]
|
|
||||||
self.remove_duplicates(to_remove)
|
self.remove_duplicates(to_remove)
|
||||||
self.mark_none()
|
self.mark_none()
|
||||||
for d in problems:
|
for dupe, _ in self.problems:
|
||||||
self.mark(d)
|
self.mark(dupe)
|
||||||
return len(problems)
|
|
||||||
|
|
||||||
def remove_duplicates(self, dupes):
|
def remove_duplicates(self, dupes):
|
||||||
'''Remove 'dupes' from their respective group, and remove the group is it ends up empty.
|
'''Remove 'dupes' from their respective group, and remove the group is it ends up empty.
|
||||||
@@ -253,16 +273,14 @@ class Results(Markable):
|
|||||||
for group in affected_groups:
|
for group in affected_groups:
|
||||||
group.discard_matches()
|
group.discard_matches()
|
||||||
self.__dupes = None
|
self.__dupes = None
|
||||||
|
self.is_modified = True
|
||||||
|
|
||||||
def save_to_xml(self, outfile):
|
def save_to_xml(self, outfile):
|
||||||
self.apply_filter(None)
|
self.apply_filter(None)
|
||||||
outfile, must_close = open_if_filename(outfile, 'wb')
|
root = ET.Element('results')
|
||||||
writer = XMLGenerator(outfile, 'utf-8')
|
# writer = XMLGenerator(outfile, 'utf-8')
|
||||||
writer.startDocument()
|
|
||||||
empty_attrs = AttributesImpl({})
|
|
||||||
writer.startElement('results', empty_attrs)
|
|
||||||
for g in self.groups:
|
for g in self.groups:
|
||||||
writer.startElement('group', empty_attrs)
|
group_elem = ET.SubElement(root, 'group')
|
||||||
dupe2index = {}
|
dupe2index = {}
|
||||||
for index, d in enumerate(g):
|
for index, d in enumerate(g):
|
||||||
dupe2index[d] = index
|
dupe2index[d] = index
|
||||||
@@ -270,27 +288,23 @@ class Results(Markable):
|
|||||||
words = engine.unpack_fields(d.words)
|
words = engine.unpack_fields(d.words)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
words = ()
|
words = ()
|
||||||
attrs = AttributesImpl({
|
file_elem = ET.SubElement(group_elem, 'file')
|
||||||
'path': unicode(d.path),
|
try:
|
||||||
'is_ref': cond(d.is_ref, 'y', 'n'),
|
file_elem.set('path', str(d.path))
|
||||||
'words': ','.join(words),
|
file_elem.set('words', ','.join(words))
|
||||||
'marked': cond(self.is_marked(d), 'y', 'n')
|
except ValueError: # If there's an invalid character, just skip the file
|
||||||
})
|
file_elem.set('path', '')
|
||||||
writer.startElement('file', attrs)
|
file_elem.set('is_ref', ('y' if d.is_ref else 'n'))
|
||||||
writer.endElement('file')
|
file_elem.set('marked', ('y' if self.is_marked(d) else 'n'))
|
||||||
for match in g.matches:
|
for match in g.matches:
|
||||||
attrs = AttributesImpl({
|
match_elem = ET.SubElement(group_elem, 'match')
|
||||||
'first': str(dupe2index[match.first]),
|
match_elem.set('first', str(dupe2index[match.first]))
|
||||||
'second': str(dupe2index[match.second]),
|
match_elem.set('second', str(dupe2index[match.second]))
|
||||||
'percentage': str(int(match.percentage)),
|
match_elem.set('percentage', str(int(match.percentage)))
|
||||||
})
|
tree = ET.ElementTree(root)
|
||||||
writer.startElement('match', attrs)
|
with FileOrPath(outfile, 'wb') as fp:
|
||||||
writer.endElement('match')
|
tree.write(fp, encoding='utf-8')
|
||||||
writer.endElement('group')
|
self.is_modified = False
|
||||||
writer.endElement('results')
|
|
||||||
writer.endDocument()
|
|
||||||
if must_close:
|
|
||||||
outfile.close()
|
|
||||||
|
|
||||||
def sort_dupes(self, key, asc=True, delta=False):
|
def sort_dupes(self, key, asc=True, delta=False):
|
||||||
if not self.__dupes:
|
if not self.__dupes:
|
||||||
@@ -310,60 +324,3 @@ class Results(Markable):
|
|||||||
dupes = property(__get_dupe_list)
|
dupes = property(__get_dupe_list)
|
||||||
groups = property(__get_groups, __set_groups)
|
groups = property(__get_groups, __set_groups)
|
||||||
stat_line = property(__get_stat_line)
|
stat_line = property(__get_stat_line)
|
||||||
|
|
||||||
class _ResultsHandler(handler.ContentHandler):
|
|
||||||
def __init__(self, get_file):
|
|
||||||
self.group = None
|
|
||||||
self.dupes = None
|
|
||||||
self.marked = set()
|
|
||||||
self.groups = []
|
|
||||||
self.get_file = get_file
|
|
||||||
|
|
||||||
def startElement(self, name, attrs):
|
|
||||||
if name == 'group':
|
|
||||||
self.group = engine.Group()
|
|
||||||
self.dupes = []
|
|
||||||
return
|
|
||||||
if (name == 'file') and (self.group is not None):
|
|
||||||
if not (('path' in attrs) and ('words' in attrs)):
|
|
||||||
return
|
|
||||||
path = attrs['path']
|
|
||||||
file = self.get_file(path)
|
|
||||||
if file is None:
|
|
||||||
return
|
|
||||||
file.words = attrs['words'].split(',')
|
|
||||||
file.is_ref = attrs.get('is_ref') == 'y'
|
|
||||||
self.dupes.append(file)
|
|
||||||
if attrs.get('marked') == 'y':
|
|
||||||
self.marked.add(file)
|
|
||||||
if (name == 'match') and (self.group is not None):
|
|
||||||
try:
|
|
||||||
first_file = self.dupes[int(attrs['first'])]
|
|
||||||
second_file = self.dupes[int(attrs['second'])]
|
|
||||||
percentage = int(attrs['percentage'])
|
|
||||||
self.group.add_match(engine.Match(first_file, second_file, percentage))
|
|
||||||
except (IndexError, KeyError, ValueError): # Covers missing attr, non-int values and indexes out of bounds
|
|
||||||
pass
|
|
||||||
|
|
||||||
def endElement(self, name):
|
|
||||||
def do_match(ref_file, other_files, group):
|
|
||||||
if not other_files:
|
|
||||||
return
|
|
||||||
for other_file in other_files:
|
|
||||||
group.add_match(engine.get_match(ref_file, other_file))
|
|
||||||
do_match(other_files[0], other_files[1:], group)
|
|
||||||
|
|
||||||
if name == 'group':
|
|
||||||
group = self.group
|
|
||||||
self.group = None
|
|
||||||
dupes = self.dupes
|
|
||||||
self.dupes = []
|
|
||||||
if group is None:
|
|
||||||
return
|
|
||||||
if len(dupes) < 2:
|
|
||||||
return
|
|
||||||
if not group.matches: # <match> elements not present, do it manually, without %
|
|
||||||
do_match(dupes[0], dupes[1:], group)
|
|
||||||
group.prioritize(lambda x: dupes.index(x))
|
|
||||||
self.groups.append(group)
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,25 +7,36 @@
|
|||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from hscommon import job
|
||||||
from hsutil import job, io
|
from hsutil import io
|
||||||
from hsutil.misc import dedupe
|
from hsutil.misc import dedupe
|
||||||
from hsutil.str import get_file_ext, rem_file_ext
|
from hsutil.str import get_file_ext, rem_file_ext
|
||||||
|
|
||||||
from . import engine
|
from . import engine
|
||||||
from .ignore import IgnoreList
|
from .ignore import IgnoreList
|
||||||
|
|
||||||
(SCAN_TYPE_FILENAME,
|
class ScanType:
|
||||||
SCAN_TYPE_FIELDS,
|
Filename = 0
|
||||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
Fields = 1
|
||||||
SCAN_TYPE_TAG,
|
FieldsNoOrder = 2
|
||||||
UNUSED, # Must not be removed. Constants here are what scan_type in the prefs are.
|
Tag = 3
|
||||||
SCAN_TYPE_CONTENT,
|
# number 4 is obsolete
|
||||||
SCAN_TYPE_CONTENT_AUDIO) = range(7)
|
Contents = 5
|
||||||
|
ContentsAudio = 6
|
||||||
|
|
||||||
SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year']
|
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):
|
class Scanner(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ignore_list = IgnoreList()
|
self.ignore_list = IgnoreList()
|
||||||
@@ -37,22 +48,22 @@ class Scanner(object):
|
|||||||
for f in j.iter_with_progress(files, 'Read size of %d/%d files'):
|
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)
|
f.size # pre-read, makes a smoother progress if read here (especially for bundles)
|
||||||
files = [f for f in files if f.size >= self.size_threshold]
|
files = [f for f in files if f.size >= self.size_threshold]
|
||||||
if self.scan_type in (SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO):
|
if self.scan_type in (ScanType.Contents, ScanType.ContentsAudio):
|
||||||
sizeattr = 'size' if self.scan_type == SCAN_TYPE_CONTENT else 'audiosize'
|
sizeattr = 'size' if self.scan_type == ScanType.Contents else 'audiosize'
|
||||||
return engine.getmatches_by_contents(files, sizeattr, partial=self.scan_type==SCAN_TYPE_CONTENT_AUDIO, j=j)
|
return engine.getmatches_by_contents(files, sizeattr, partial=self.scan_type==ScanType.ContentsAudio, j=j)
|
||||||
else:
|
else:
|
||||||
j = j.start_subjob([2, 8])
|
j = j.start_subjob([2, 8])
|
||||||
kw = {}
|
kw = {}
|
||||||
kw['match_similar_words'] = self.match_similar_words
|
kw['match_similar_words'] = self.match_similar_words
|
||||||
kw['weight_words'] = self.word_weighting
|
kw['weight_words'] = self.word_weighting
|
||||||
kw['min_match_percentage'] = self.min_match_percentage
|
kw['min_match_percentage'] = self.min_match_percentage
|
||||||
if self.scan_type == SCAN_TYPE_FIELDS_NO_ORDER:
|
if self.scan_type == ScanType.FieldsNoOrder:
|
||||||
self.scan_type = SCAN_TYPE_FIELDS
|
self.scan_type = ScanType.Fields
|
||||||
kw['no_field_order'] = True
|
kw['no_field_order'] = True
|
||||||
func = {
|
func = {
|
||||||
SCAN_TYPE_FILENAME: lambda f: engine.getwords(rem_file_ext(f.name)),
|
ScanType.Filename: lambda f: engine.getwords(rem_file_ext(f.name)),
|
||||||
SCAN_TYPE_FIELDS: lambda f: engine.getfields(rem_file_ext(f.name)),
|
ScanType.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],
|
ScanType.Tag: lambda f: [engine.getwords(str(getattr(f, attrname))) for attrname in SCANNABLE_TAGS if attrname in self.scanned_tags],
|
||||||
}[self.scan_type]
|
}[self.scan_type]
|
||||||
for f in j.iter_with_progress(files, 'Read metadata of %d/%d files'):
|
for f in j.iter_with_progress(files, 'Read metadata of %d/%d files'):
|
||||||
f.words = func(f)
|
f.words = func(f)
|
||||||
@@ -66,9 +77,13 @@ class Scanner(object):
|
|||||||
def _tie_breaker(ref, dupe):
|
def _tie_breaker(ref, dupe):
|
||||||
refname = rem_file_ext(ref.name).lower()
|
refname = rem_file_ext(ref.name).lower()
|
||||||
dupename = rem_file_ext(dupe.name).lower()
|
dupename = rem_file_ext(dupe.name).lower()
|
||||||
if 'copy' in refname and 'copy' not in dupename:
|
if 'copy' in dupename:
|
||||||
|
return False
|
||||||
|
if 'copy' in refname:
|
||||||
return True
|
return True
|
||||||
if refname.startswith(dupename) and (refname[len(dupename):].strip().isdigit()):
|
if is_same_with_digit(dupename, refname):
|
||||||
|
return False
|
||||||
|
if is_same_with_digit(refname, dupename):
|
||||||
return True
|
return True
|
||||||
return len(dupe.path) > len(ref.path)
|
return len(dupe.path) > len(ref.path)
|
||||||
|
|
||||||
@@ -87,7 +102,7 @@ class Scanner(object):
|
|||||||
j = j.start_subjob(2)
|
j = j.start_subjob(2)
|
||||||
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
||||||
matches = [m for m in iter_matches
|
matches = [m for m in iter_matches
|
||||||
if not self.ignore_list.AreIgnored(unicode(m.first.path), unicode(m.second.path))]
|
if not self.ignore_list.AreIgnored(str(m.first.path), str(m.second.path))]
|
||||||
logging.info('Grouping matches')
|
logging.info('Grouping matches')
|
||||||
groups = engine.get_groups(matches, j)
|
groups = engine.get_groups(matches, j)
|
||||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
||||||
@@ -102,7 +117,7 @@ class Scanner(object):
|
|||||||
match_similar_words = False
|
match_similar_words = False
|
||||||
min_match_percentage = 80
|
min_match_percentage = 80
|
||||||
mix_file_kind = True
|
mix_file_kind = True
|
||||||
scan_type = SCAN_TYPE_FILENAME
|
scan_type = ScanType.Filename
|
||||||
scanned_tags = set(['artist', 'title'])
|
scanned_tags = set(['artist', 'title'])
|
||||||
size_threshold = 0
|
size_threshold = 0
|
||||||
word_weighting = False
|
word_weighting = False
|
||||||
|
|||||||
@@ -9,14 +9,13 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from nose.tools import eq_
|
from hsutil.testutil import eq_
|
||||||
|
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
from hsutil import io
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.decorators import log_calls
|
from hsutil.decorators import log_calls
|
||||||
import hsutil.files
|
import hsutil.files
|
||||||
from hsutil.job import nulljob
|
from hscommon.job import nulljob
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
from .results_test import GetTestGroups
|
from .results_test import GetTestGroups
|
||||||
@@ -24,14 +23,15 @@ from .. import app, fs, engine
|
|||||||
from ..app import DupeGuru as DupeGuruBase
|
from ..app import DupeGuru as DupeGuruBase
|
||||||
from ..gui.details_panel import DetailsPanel
|
from ..gui.details_panel import DetailsPanel
|
||||||
from ..gui.directory_tree import DirectoryTree
|
from ..gui.directory_tree import DirectoryTree
|
||||||
from ..gui.result_tree import ResultTree
|
from ..gui.result_table import ResultTable
|
||||||
|
from ..scanner import ScanType
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
class DupeGuru(DupeGuruBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DupeGuruBase.__init__(self, data, '/tmp', appid=4)
|
DupeGuruBase.__init__(self, data, '/tmp', appid=4)
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
def _start_job(self, jobid, func, *args):
|
||||||
func(nulljob)
|
func(nulljob, *args)
|
||||||
|
|
||||||
|
|
||||||
class CallLogger(object):
|
class CallLogger(object):
|
||||||
@@ -110,7 +110,7 @@ class TCDupeGuru(TestCase):
|
|||||||
|
|
||||||
def test_Scan_with_objects_evaluating_to_false(self):
|
def test_Scan_with_objects_evaluating_to_false(self):
|
||||||
class FakeFile(fs.File):
|
class FakeFile(fs.File):
|
||||||
def __nonzero__(self):
|
def __bool__(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -119,10 +119,23 @@ class TCDupeGuru(TestCase):
|
|||||||
f1, f2 = [FakeFile('foo') for i in range(2)]
|
f1, f2 = [FakeFile('foo') for i in range(2)]
|
||||||
f1.is_ref, f2.is_ref = (False, False)
|
f1.is_ref, f2.is_ref = (False, False)
|
||||||
assert not (bool(f1) and bool(f2))
|
assert not (bool(f1) and bool(f2))
|
||||||
app.directories.get_files = lambda: [f1, f2]
|
app.directories.get_files = lambda: iter([f1, f2])
|
||||||
app.directories._dirs.append('this is just so Scan() doesnt return 3')
|
app.directories._dirs.append('this is just so Scan() doesnt return 3')
|
||||||
app.start_scanning() # no exception
|
app.start_scanning() # no exception
|
||||||
|
|
||||||
|
def test_ignore_hardlink_matches(self):
|
||||||
|
# If the ignore_hardlink_matches option is set, don't match files hardlinking to the same
|
||||||
|
# inode.
|
||||||
|
tmppath = Path(self.tmpdir())
|
||||||
|
io.open(tmppath + 'myfile', 'w').write('foo')
|
||||||
|
os.link(str(tmppath + 'myfile'), str(tmppath + 'hardlink'))
|
||||||
|
app = DupeGuru()
|
||||||
|
app.directories.add_path(tmppath)
|
||||||
|
app.scanner.scan_type = ScanType.Contents
|
||||||
|
app.options['ignore_hardlink_matches'] = True
|
||||||
|
app.start_scanning()
|
||||||
|
eq_(len(app.results.groups), 0)
|
||||||
|
|
||||||
|
|
||||||
class TCDupeGuru_clean_empty_dirs(TestCase):
|
class TCDupeGuru_clean_empty_dirs(TestCase):
|
||||||
cls_tested_module = app
|
cls_tested_module = app
|
||||||
@@ -167,11 +180,11 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
self.dpanel = DetailsPanel(self.dpanel_gui, self.app)
|
self.dpanel = DetailsPanel(self.dpanel_gui, self.app)
|
||||||
self.dtree_gui = CallLogger()
|
self.dtree_gui = CallLogger()
|
||||||
self.dtree = DirectoryTree(self.dtree_gui, self.app)
|
self.dtree = DirectoryTree(self.dtree_gui, self.app)
|
||||||
self.rtree_gui = CallLogger()
|
self.rtable_gui = CallLogger()
|
||||||
self.rtree = ResultTree(self.rtree_gui, self.app)
|
self.rtable = ResultTable(self.rtable_gui, self.app)
|
||||||
self.dpanel.connect()
|
self.dpanel.connect()
|
||||||
self.dtree.connect()
|
self.dtree.connect()
|
||||||
self.rtree.connect()
|
self.rtable.connect()
|
||||||
tmppath = self.tmppath()
|
tmppath = self.tmppath()
|
||||||
io.mkdir(tmppath + 'foo')
|
io.mkdir(tmppath + 'foo')
|
||||||
io.mkdir(tmppath + 'bar')
|
io.mkdir(tmppath + 'bar')
|
||||||
@@ -201,11 +214,11 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
if expected is not None:
|
if expected is not None:
|
||||||
expected = set(expected)
|
expected = set(expected)
|
||||||
not_called = expected - calls
|
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:
|
if not_expected is not None:
|
||||||
not_expected = set(not_expected)
|
not_expected = set(not_expected)
|
||||||
called = not_expected & calls
|
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()
|
gui.clear_calls()
|
||||||
|
|
||||||
def clear_gui_calls(self):
|
def clear_gui_calls(self):
|
||||||
@@ -218,42 +231,35 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
def test_GetObjects(self):
|
def test_GetObjects(self):
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
groups = self.groups
|
groups = self.groups
|
||||||
n = self.rtree.get_node([0])
|
r = self.rtable[0]
|
||||||
assert n._group is groups[0]
|
assert r._group is groups[0]
|
||||||
assert n._dupe is objects[0]
|
assert r._dupe is objects[0]
|
||||||
n = self.rtree.get_node([0, 0])
|
r = self.rtable[1]
|
||||||
assert n._group is groups[0]
|
assert r._group is groups[0]
|
||||||
assert n._dupe is objects[1]
|
assert r._dupe is objects[1]
|
||||||
n = self.rtree.get_node([1, 0])
|
r = self.rtable[4]
|
||||||
assert n._group is groups[1]
|
assert r._group is groups[1]
|
||||||
assert n._dupe is objects[4]
|
assert r._dupe is objects[4]
|
||||||
|
|
||||||
def test_GetObjects_after_sort(self):
|
def test_GetObjects_after_sort(self):
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
groups = self.groups[:] # we need an un-sorted reference
|
groups = self.groups[:] # we need an un-sorted reference
|
||||||
self.rtree.sort(0, False) #0 = Filename
|
self.rtable.sort(0, False) #0 = Filename
|
||||||
n = self.rtree.get_node([0, 0])
|
r = self.rtable[1]
|
||||||
assert n._group is groups[1]
|
assert r._group is groups[1]
|
||||||
assert n._dupe is objects[4]
|
assert r._dupe is objects[4]
|
||||||
|
|
||||||
def test_selected_result_node_paths(self):
|
|
||||||
# app.selected_dupes is correctly converted into node paths
|
|
||||||
paths = [[0, 0], [0, 1], [1]]
|
|
||||||
self.rtree.selected_paths = paths
|
|
||||||
eq_(self.rtree.selected_paths, paths)
|
|
||||||
|
|
||||||
def test_selected_result_node_paths_after_deletion(self):
|
def test_selected_result_node_paths_after_deletion(self):
|
||||||
# cases where the selected dupes aren't there are correctly handled
|
# cases where the selected dupes aren't there are correctly handled
|
||||||
paths = [[0, 0], [0, 1], [1]]
|
self.rtable.select([1, 2, 3])
|
||||||
self.rtree.selected_paths = paths
|
|
||||||
self.app.remove_selected()
|
self.app.remove_selected()
|
||||||
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
|
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
|
||||||
eq_(self.rtree.selected_paths, [[0]]) # no exception
|
eq_(self.rtable.selected_indexes, [1]) # no exception
|
||||||
|
|
||||||
def test_selectResultNodePaths(self):
|
def test_selectResultNodePaths(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
self.rtree.selected_paths = [[0, 0], [0, 1]]
|
self.rtable.select([1, 2])
|
||||||
eq_(len(app.selected_dupes), 2)
|
eq_(len(app.selected_dupes), 2)
|
||||||
assert app.selected_dupes[0] is objects[1]
|
assert app.selected_dupes[0] is objects[1]
|
||||||
assert app.selected_dupes[1] is objects[2]
|
assert app.selected_dupes[1] is objects[2]
|
||||||
@@ -261,7 +267,7 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
def test_selectResultNodePaths_with_ref(self):
|
def test_selectResultNodePaths_with_ref(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1]]
|
self.rtable.select([1, 2, 3])
|
||||||
eq_(len(app.selected_dupes), 3)
|
eq_(len(app.selected_dupes), 3)
|
||||||
assert app.selected_dupes[0] is objects[1]
|
assert app.selected_dupes[0] is objects[1]
|
||||||
assert app.selected_dupes[1] is objects[2]
|
assert app.selected_dupes[1] is objects[2]
|
||||||
@@ -271,9 +277,9 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
groups = self.groups[:] #To keep the old order in memory
|
groups = self.groups[:] #To keep the old order in memory
|
||||||
self.rtree.sort(0, False) #0 = Filename
|
self.rtable.sort(0, False) #0 = Filename
|
||||||
#Now, the group order is supposed to be reversed
|
#Now, the group order is supposed to be reversed
|
||||||
self.rtree.selected_paths = [[0, 0], [1], [1, 0]]
|
self.rtable.select([1, 2, 3])
|
||||||
eq_(len(app.selected_dupes), 3)
|
eq_(len(app.selected_dupes), 3)
|
||||||
assert app.selected_dupes[0] is objects[4]
|
assert app.selected_dupes[0] is objects[4]
|
||||||
assert app.selected_dupes[1] is groups[0].ref
|
assert app.selected_dupes[1] is groups[0].ref
|
||||||
@@ -283,39 +289,26 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
# app.selected_dupes is correctly converted into paths
|
# app.selected_dupes is correctly converted into paths
|
||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
self.rtree.power_marker = True
|
self.rtable.power_marker = True
|
||||||
self.rtree.selected_paths = [[0], [1], [2]]
|
self.rtable.select([0, 1, 2])
|
||||||
self.rtree.power_marker = False
|
self.rtable.power_marker = False
|
||||||
eq_(self.rtree.selected_paths, [[0, 0], [0, 1], [1, 0]])
|
eq_(self.rtable.selected_indexes, [1, 2, 4])
|
||||||
|
|
||||||
def test_selected_powermarker_node_paths_after_deletion(self):
|
def test_selected_powermarker_node_paths_after_deletion(self):
|
||||||
# cases where the selected dupes aren't there are correctly handled
|
# cases where the selected dupes aren't there are correctly handled
|
||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
self.rtree.power_marker = True
|
self.rtable.power_marker = True
|
||||||
self.rtree.selected_paths = [[0], [1], [2]]
|
self.rtable.select([0, 1, 2])
|
||||||
app.remove_selected()
|
app.remove_selected()
|
||||||
eq_(self.rtree.selected_paths, []) # no exception
|
eq_(self.rtable.selected_indexes, []) # no exception
|
||||||
|
|
||||||
def test_selectPowerMarkerRows(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
|
|
||||||
eq_(len(app.selected_dupes), 3)
|
|
||||||
assert app.selected_dupes[0] is objects[1]
|
|
||||||
assert app.selected_dupes[1] is objects[2]
|
|
||||||
assert app.selected_dupes[2] is objects[4]
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows_empty(self):
|
|
||||||
self.rtree.selected_paths = []
|
|
||||||
eq_(len(self.app.selected_dupes), 0)
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows_after_sort(self):
|
def test_selectPowerMarkerRows_after_sort(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
self.rtree.power_marker = True
|
self.rtable.power_marker = True
|
||||||
self.rtree.sort(0, False) #0 = Filename
|
self.rtable.sort(0, False) #0 = Filename
|
||||||
self.rtree.selected_paths = [[0], [1], [2]]
|
self.rtable.select([0, 1, 2])
|
||||||
eq_(len(app.selected_dupes), 3)
|
eq_(len(app.selected_dupes), 3)
|
||||||
assert app.selected_dupes[0] is objects[4]
|
assert app.selected_dupes[0] is objects[4]
|
||||||
assert app.selected_dupes[1] is objects[2]
|
assert app.selected_dupes[1] is objects[2]
|
||||||
@@ -326,7 +319,7 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
objects = self.objects
|
objects = self.objects
|
||||||
app.toggle_selected_mark_state()
|
app.toggle_selected_mark_state()
|
||||||
eq_(app.results.mark_count, 0)
|
eq_(app.results.mark_count, 0)
|
||||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
self.rtable.select([1, 4])
|
||||||
app.toggle_selected_mark_state()
|
app.toggle_selected_mark_state()
|
||||||
eq_(app.results.mark_count, 2)
|
eq_(app.results.mark_count, 2)
|
||||||
assert not app.results.is_marked(objects[0])
|
assert not app.results.is_marked(objects[0])
|
||||||
@@ -336,10 +329,10 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
assert app.results.is_marked(objects[4])
|
assert app.results.is_marked(objects[4])
|
||||||
|
|
||||||
def test_refreshDetailsWithSelected(self):
|
def test_refreshDetailsWithSelected(self):
|
||||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
self.rtable.select([1, 4])
|
||||||
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
|
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
|
||||||
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
||||||
self.rtree.selected_paths = []
|
self.rtable.select([])
|
||||||
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
|
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
|
||||||
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
self.check_gui_calls(self.dpanel_gui, ['refresh'])
|
||||||
|
|
||||||
@@ -347,7 +340,7 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
groups = self.groups
|
groups = self.groups
|
||||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
self.rtable.select([1, 4])
|
||||||
app.make_selected_reference()
|
app.make_selected_reference()
|
||||||
assert groups[0].ref is objects[1]
|
assert groups[0].ref is objects[1]
|
||||||
assert groups[1].ref is objects[4]
|
assert groups[1].ref is objects[4]
|
||||||
@@ -356,7 +349,7 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
app = self.app
|
app = self.app
|
||||||
objects = self.objects
|
objects = self.objects
|
||||||
groups = self.groups
|
groups = self.groups
|
||||||
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
|
self.rtable.select([1, 2, 4])
|
||||||
#Only [0, 0] and [1, 0] must go ref, not [0, 1] because it is a part of the same group
|
#Only [0, 0] and [1, 0] must go ref, not [0, 1] because it is a part of the same group
|
||||||
app.make_selected_reference()
|
app.make_selected_reference()
|
||||||
assert groups[0].ref is objects[1]
|
assert groups[0].ref is objects[1]
|
||||||
@@ -364,12 +357,9 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
|
|
||||||
def test_removeSelected(self):
|
def test_removeSelected(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
self.rtree.selected_paths = [[0, 0], [1, 0]]
|
self.rtable.select([1, 4])
|
||||||
app.remove_selected()
|
app.remove_selected()
|
||||||
eq_(len(app.results.dupes), 1)
|
eq_(len(app.results.dupes), 1) # the first path is now selected
|
||||||
app.remove_selected()
|
|
||||||
eq_(len(app.results.dupes), 1)
|
|
||||||
self.rtree.selected_path = [0, 0]
|
|
||||||
app.remove_selected()
|
app.remove_selected()
|
||||||
eq_(len(app.results.dupes), 0)
|
eq_(len(app.results.dupes), 0)
|
||||||
|
|
||||||
@@ -390,10 +380,10 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
|
|
||||||
def test_ignore(self):
|
def test_ignore(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
self.rtree.selected_path = [1, 0] #The dupe of the second, 2 sized group
|
self.rtable.select([4]) #The dupe of the second, 2 sized group
|
||||||
app.add_selected_to_ignore_list()
|
app.add_selected_to_ignore_list()
|
||||||
eq_(len(app.scanner.ignore_list), 1)
|
eq_(len(app.scanner.ignore_list), 1)
|
||||||
self.rtree.selected_path = [0, 0] #first dupe of the 3 dupes group
|
self.rtable.select([1]) #first dupe of the 3 dupes group
|
||||||
app.add_selected_to_ignore_list()
|
app.add_selected_to_ignore_list()
|
||||||
#BOTH the ref and the other dupe should have been added
|
#BOTH the ref and the other dupe should have been added
|
||||||
eq_(len(app.scanner.ignore_list), 3)
|
eq_(len(app.scanner.ignore_list), 3)
|
||||||
@@ -413,25 +403,25 @@ class TCDupeGuruWithResults(TestCase):
|
|||||||
|
|
||||||
def test_only_unicode_is_added_to_ignore_list(self):
|
def test_only_unicode_is_added_to_ignore_list(self):
|
||||||
def FakeIgnore(first,second):
|
def FakeIgnore(first,second):
|
||||||
if not isinstance(first,unicode):
|
if not isinstance(first,str):
|
||||||
self.fail()
|
self.fail()
|
||||||
if not isinstance(second,unicode):
|
if not isinstance(second,str):
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
app = self.app
|
app = self.app
|
||||||
app.scanner.ignore_list.Ignore = FakeIgnore
|
app.scanner.ignore_list.Ignore = FakeIgnore
|
||||||
self.rtree.selected_path = [1, 0]
|
self.rtable.select([4])
|
||||||
app.add_selected_to_ignore_list()
|
app.add_selected_to_ignore_list()
|
||||||
|
|
||||||
|
|
||||||
class TCDupeGuru_renameSelected(TestCase):
|
class TCDupeGuru_renameSelected(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
p = self.tmppath()
|
p = self.tmppath()
|
||||||
fp = open(unicode(p + 'foo bar 1'),mode='w')
|
fp = open(str(p + 'foo bar 1'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open(unicode(p + 'foo bar 2'),mode='w')
|
fp = open(str(p + 'foo bar 2'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open(unicode(p + 'foo bar 3'),mode='w')
|
fp = open(str(p + 'foo bar 3'),mode='w')
|
||||||
fp.close()
|
fp.close()
|
||||||
files = fs.get_files(p)
|
files = fs.get_files(p)
|
||||||
matches = engine.getmatches(files)
|
matches = engine.getmatches(files)
|
||||||
@@ -444,14 +434,14 @@ class TCDupeGuru_renameSelected(TestCase):
|
|||||||
self.groups = groups
|
self.groups = groups
|
||||||
self.p = p
|
self.p = p
|
||||||
self.files = files
|
self.files = files
|
||||||
self.rtree_gui = CallLogger()
|
self.rtable_gui = CallLogger()
|
||||||
self.rtree = ResultTree(self.rtree_gui, self.app)
|
self.rtable = ResultTable(self.rtable_gui, self.app)
|
||||||
self.rtree.connect()
|
self.rtable.connect()
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
self.rtree.selected_path = [0, 0]
|
self.rtable.select([1])
|
||||||
assert app.rename_selected('renamed')
|
assert app.rename_selected('renamed')
|
||||||
names = io.listdir(self.p)
|
names = io.listdir(self.p)
|
||||||
assert 'renamed' in names
|
assert 'renamed' in names
|
||||||
@@ -461,7 +451,7 @@ class TCDupeGuru_renameSelected(TestCase):
|
|||||||
def test_none_selected(self):
|
def test_none_selected(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
self.rtree.selected_paths = []
|
self.rtable.select([])
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||||
assert not app.rename_selected('renamed')
|
assert not app.rename_selected('renamed')
|
||||||
msg = logging.warning.calls[0]['msg']
|
msg = logging.warning.calls[0]['msg']
|
||||||
@@ -474,7 +464,7 @@ class TCDupeGuru_renameSelected(TestCase):
|
|||||||
def test_name_already_exists(self):
|
def test_name_already_exists(self):
|
||||||
app = self.app
|
app = self.app
|
||||||
g = self.groups[0]
|
g = self.groups[0]
|
||||||
self.rtree.selected_path = [0, 0]
|
self.rtable.select([1])
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
||||||
assert not app.rename_selected('foo bar 1')
|
assert not app.rename_selected('foo bar 1')
|
||||||
msg = logging.warning.calls[0]['msg']
|
msg = logging.warning.calls[0]['msg']
|
||||||
|
|||||||
28
core/tests/conftest.py
Normal file
28
core/tests/conftest.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-07-11
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" 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/bsd_license
|
||||||
|
|
||||||
|
# This unit is required to make tests work with py.test. When running
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def get_testunit(item):
|
||||||
|
if hasattr(item, 'obj'):
|
||||||
|
testunit = py.builtin._getimself(item.obj)
|
||||||
|
if hasattr(testunit, 'global_setup'):
|
||||||
|
return testunit
|
||||||
|
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_setup()
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_teardown()
|
||||||
@@ -10,10 +10,9 @@ import os.path as op
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from hsutil import io
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
|
from hsutil.testutil import eq_
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
from ..directories import *
|
from ..directories import *
|
||||||
@@ -83,8 +82,8 @@ class TCDirectories(TestCase):
|
|||||||
|
|
||||||
def test_AddPath_non_latin(self):
|
def test_AddPath_non_latin(self):
|
||||||
p = Path(self.tmpdir())
|
p = Path(self.tmpdir())
|
||||||
to_add = p + u'unicode\u201a'
|
to_add = p + 'unicode\u201a'
|
||||||
os.mkdir(unicode(to_add))
|
os.mkdir(str(to_add))
|
||||||
d = Directories()
|
d = Directories()
|
||||||
try:
|
try:
|
||||||
d.add_path(to_add)
|
d.add_path(to_add)
|
||||||
@@ -112,7 +111,7 @@ class TCDirectories(TestCase):
|
|||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p))
|
self.assertEqual(STATE_REFERENCE,d.get_state(p))
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
||||||
self.assertEqual(1,len(d.states))
|
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])
|
self.assertEqual(STATE_REFERENCE,d.states[p])
|
||||||
|
|
||||||
def test_get_state_with_path_not_there(self):
|
def test_get_state_with_path_not_there(self):
|
||||||
@@ -214,11 +213,11 @@ class TCDirectories(TestCase):
|
|||||||
|
|
||||||
def test_unicode_save(self):
|
def test_unicode_save(self):
|
||||||
d = Directories()
|
d = Directories()
|
||||||
p1 = self.tmppath() + u'hello\xe9'
|
p1 = self.tmppath() + 'hello\xe9'
|
||||||
io.mkdir(p1)
|
io.mkdir(p1)
|
||||||
io.mkdir(p1 + u'foo\xe9')
|
io.mkdir(p1 + 'foo\xe9')
|
||||||
d.add_path(p1)
|
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')
|
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
||||||
try:
|
try:
|
||||||
d.save_to_file(tmpxml)
|
d.save_to_file(tmpxml)
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from nose.tools import eq_
|
from hscommon import job
|
||||||
|
|
||||||
from hsutil import job
|
|
||||||
from hsutil.decorators import log_calls
|
from hsutil.decorators import log_calls
|
||||||
|
from hsutil.misc import first
|
||||||
|
from hsutil.testutil import eq_
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
from .. import engine, fs
|
from .. import engine
|
||||||
from ..engine import *
|
from ..engine import *
|
||||||
|
|
||||||
class NamedObject(object):
|
class NamedObject(object):
|
||||||
@@ -46,6 +46,15 @@ def get_test_group():
|
|||||||
result.add_match(m3)
|
result.add_match(m3)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def assert_match(m, name1, name2):
|
||||||
|
# When testing matches, whether objects are in first or second position very often doesn't
|
||||||
|
# matter. This function makes this test more convenient.
|
||||||
|
if m.first.name == name1:
|
||||||
|
eq_(m.second.name, name2)
|
||||||
|
else:
|
||||||
|
eq_(m.first.name, name2)
|
||||||
|
eq_(m.second.name, name1)
|
||||||
|
|
||||||
class TCgetwords(TestCase):
|
class TCgetwords(TestCase):
|
||||||
def test_spaces(self):
|
def test_spaces(self):
|
||||||
self.assertEqual(['a', 'b', 'c', 'd'], getwords("a b c d"))
|
self.assertEqual(['a', 'b', 'c', 'd'], getwords("a b c d"))
|
||||||
@@ -53,12 +62,12 @@ class TCgetwords(TestCase):
|
|||||||
|
|
||||||
def test_splitter_chars(self):
|
def test_splitter_chars(self):
|
||||||
self.assertEqual(
|
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")
|
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):
|
def test_joiner_chars(self):
|
||||||
self.assertEqual(["aec"], getwords(u"a'e\u0301c"))
|
self.assertEqual(["aec"], getwords("a'e\u0301c"))
|
||||||
|
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
self.assertEqual([], getwords(''))
|
self.assertEqual([], getwords(''))
|
||||||
@@ -67,7 +76,7 @@ class TCgetwords(TestCase):
|
|||||||
self.assertEqual(['foo', 'bar'], getwords('FOO BAR'))
|
self.assertEqual(['foo', 'bar'], getwords('FOO BAR'))
|
||||||
|
|
||||||
def test_decompose_unicode(self):
|
def test_decompose_unicode(self):
|
||||||
self.assertEqual(getwords(u'foo\xe9bar'), ['fooebar'])
|
self.assertEqual(getwords('foo\xe9bar'), ['fooebar'])
|
||||||
|
|
||||||
|
|
||||||
class TCgetfields(TestCase):
|
class TCgetfields(TestCase):
|
||||||
@@ -229,10 +238,9 @@ class TCbuild_word_dict(TestCase):
|
|||||||
self.log = []
|
self.log = []
|
||||||
s = "foo bar"
|
s = "foo bar"
|
||||||
build_word_dict([NamedObject(s, True), NamedObject(s, True), NamedObject(s, True)], j)
|
build_word_dict([NamedObject(s, True), NamedObject(s, True), NamedObject(s, True)], j)
|
||||||
|
# We don't have intermediate log because iter_with_progress is called with every > 1
|
||||||
self.assertEqual(0,self.log[0])
|
self.assertEqual(0,self.log[0])
|
||||||
self.assertEqual(33,self.log[1])
|
self.assertEqual(100,self.log[1])
|
||||||
self.assertEqual(66,self.log[2])
|
|
||||||
self.assertEqual(100,self.log[3])
|
|
||||||
|
|
||||||
|
|
||||||
class TCmerge_similar_words(TestCase):
|
class TCmerge_similar_words(TestCase):
|
||||||
@@ -352,23 +360,18 @@ class GetMatches(TestCase):
|
|||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
|
||||||
r = getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(2,len(r))
|
self.assertEqual(2,len(r))
|
||||||
seek = [m for m in r if m.percentage == 50] #"foo bar" and "bar bleh"
|
m = first(m for m in r if m.percentage == 50) #"foo bar" and "bar bleh"
|
||||||
m = seek[0]
|
assert_match(m, 'foo bar', 'bar bleh')
|
||||||
self.assertEqual(['foo','bar'],m.first.words)
|
m = first(m for m in r if m.percentage == 33) #"foo bar" and "a b c foo"
|
||||||
self.assertEqual(['bar','bleh'],m.second.words)
|
assert_match(m, 'foo bar', 'a b c foo')
|
||||||
seek = [m for m in r if m.percentage == 33] #"foo bar" and "a b c foo"
|
|
||||||
m = seek[0]
|
|
||||||
self.assertEqual(['foo','bar'],m.first.words)
|
|
||||||
self.assertEqual(['a','b','c','foo'],m.second.words)
|
|
||||||
|
|
||||||
def test_null_and_unrelated_objects(self):
|
def test_null_and_unrelated_objects(self):
|
||||||
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject(""),NamedObject("unrelated object")]
|
l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject(""),NamedObject("unrelated object")]
|
||||||
r = getmatches(l)
|
r = getmatches(l)
|
||||||
self.assertEqual(1,len(r))
|
eq_(len(r), 1)
|
||||||
m = r[0]
|
m = r[0]
|
||||||
self.assertEqual(50,m.percentage)
|
eq_(m.percentage, 50)
|
||||||
self.assertEqual(['foo','bar'],m.first.words)
|
assert_match(m, 'foo bar', 'bar bleh')
|
||||||
self.assertEqual(['bar','bleh'],m.second.words)
|
|
||||||
|
|
||||||
def test_twice_the_same_word(self):
|
def test_twice_the_same_word(self):
|
||||||
l = [NamedObject("foo foo bar"),NamedObject("bar bleh")]
|
l = [NamedObject("foo foo bar"),NamedObject("bar bleh")]
|
||||||
@@ -765,7 +768,7 @@ class TCget_groups(TestCase):
|
|||||||
self.assert_(o3 in g)
|
self.assert_(o3 in g)
|
||||||
|
|
||||||
def test_four_sized_group(self):
|
def test_four_sized_group(self):
|
||||||
l = [NamedObject("foobar") for i in xrange(4)]
|
l = [NamedObject("foobar") for i in range(4)]
|
||||||
m = getmatches(l)
|
m = getmatches(l)
|
||||||
r = get_groups(m)
|
r = get_groups(m)
|
||||||
self.assertEqual(1,len(r))
|
self.assertEqual(1,len(r))
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import cStringIO
|
import io
|
||||||
import xml.dom.minidom
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from nose.tools import eq_
|
from hsutil.testutil import eq_
|
||||||
|
|
||||||
from ..ignore import *
|
from ..ignore import *
|
||||||
|
|
||||||
@@ -59,37 +59,35 @@ def test_save_to_xml():
|
|||||||
il.Ignore('foo','bar')
|
il.Ignore('foo','bar')
|
||||||
il.Ignore('foo','bleh')
|
il.Ignore('foo','bleh')
|
||||||
il.Ignore('bleh','bar')
|
il.Ignore('bleh','bar')
|
||||||
f = cStringIO.StringIO()
|
f = io.BytesIO()
|
||||||
il.save_to_xml(f)
|
il.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
doc = xml.dom.minidom.parse(f)
|
doc = ET.parse(f)
|
||||||
root = doc.documentElement
|
root = doc.getroot()
|
||||||
eq_('ignore_list',root.nodeName)
|
eq_(root.tag, 'ignore_list')
|
||||||
children = [c for c in root.childNodes if c.localName]
|
eq_(len(root), 2)
|
||||||
eq_(2,len(children))
|
eq_(len([c for c in root if c.tag == 'file']), 2)
|
||||||
eq_(2,len([c for c in children if c.nodeName == 'file']))
|
f1, f2 = root[:]
|
||||||
f1,f2 = children
|
subchildren = [c for c in f1 if c.tag == 'file'] + [c for c in f2 if c.tag == 'file']
|
||||||
subchildren = [c for c in f1.childNodes if c.localName == 'file'] +\
|
eq_(len(subchildren), 3)
|
||||||
[c for c in f2.childNodes if c.localName == 'file']
|
|
||||||
eq_(3,len(subchildren))
|
|
||||||
|
|
||||||
def test_SaveThenLoad():
|
def test_SaveThenLoad():
|
||||||
il = IgnoreList()
|
il = IgnoreList()
|
||||||
il.Ignore('foo','bar')
|
il.Ignore('foo', 'bar')
|
||||||
il.Ignore('foo','bleh')
|
il.Ignore('foo', 'bleh')
|
||||||
il.Ignore('bleh','bar')
|
il.Ignore('bleh', 'bar')
|
||||||
il.Ignore(u'\u00e9','bar')
|
il.Ignore('\u00e9', 'bar')
|
||||||
f = cStringIO.StringIO()
|
f = io.BytesIO()
|
||||||
il.save_to_xml(f)
|
il.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
il = IgnoreList()
|
il = IgnoreList()
|
||||||
il.load_from_xml(f)
|
il.load_from_xml(f)
|
||||||
eq_(4,len(il))
|
eq_(4,len(il))
|
||||||
assert il.AreIgnored(u'\u00e9','bar')
|
assert il.AreIgnored('\u00e9','bar')
|
||||||
|
|
||||||
def test_LoadXML_with_empty_file_tags():
|
def test_LoadXML_with_empty_file_tags():
|
||||||
f = cStringIO.StringIO()
|
f = io.BytesIO()
|
||||||
f.write('<?xml version="1.0" encoding="utf-8"?><ignore_list><file><file/></file></ignore_list>')
|
f.write(b'<?xml version="1.0" encoding="utf-8"?><ignore_list><file><file/></file></ignore_list>')
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
il = IgnoreList()
|
il = IgnoreList()
|
||||||
il.load_from_xml(f)
|
il.load_from_xml(f)
|
||||||
@@ -129,14 +127,14 @@ def test_filter():
|
|||||||
assert not il.AreIgnored('foo','bar')
|
assert not il.AreIgnored('foo','bar')
|
||||||
assert il.AreIgnored('bar','baz')
|
assert il.AreIgnored('bar','baz')
|
||||||
|
|
||||||
def test_save_with_non_ascii_non_unicode_items():
|
def test_save_with_non_ascii_items():
|
||||||
il = IgnoreList()
|
il = IgnoreList()
|
||||||
il.Ignore('\xac','\xbf')
|
il.Ignore('\xac', '\xbf')
|
||||||
f = cStringIO.StringIO()
|
f = io.BytesIO()
|
||||||
try:
|
try:
|
||||||
il.save_to_xml(f)
|
il.save_to_xml(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AssertionError(unicode(e))
|
raise AssertionError(str(e))
|
||||||
|
|
||||||
def test_len():
|
def test_len():
|
||||||
il = IgnoreList()
|
il = IgnoreList()
|
||||||
|
|||||||
@@ -7,24 +7,25 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import unittest
|
import io
|
||||||
import StringIO
|
|
||||||
import xml.dom.minidom
|
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
|
from hsutil.testutil import eq_
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
from hsutil.misc import first
|
from hsutil.misc import first
|
||||||
|
|
||||||
from . import engine_test, data
|
from . import engine_test, data
|
||||||
from .. import engine
|
from .. import engine
|
||||||
from ..results import *
|
from ..results import Results
|
||||||
|
|
||||||
class NamedObject(engine_test.NamedObject):
|
class NamedObject(engine_test.NamedObject):
|
||||||
path = property(lambda x:Path('basepath') + x.name)
|
path = property(lambda x:Path('basepath') + x.name)
|
||||||
is_ref = False
|
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.
|
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:
|
# Returns a group set that looks like that:
|
||||||
@@ -53,21 +54,24 @@ class TCResultsEmpty(TestCase):
|
|||||||
self.test_stat_line() # make sure that the stats line isn't saying we applied a '[' filter
|
self.test_stat_line() # make sure that the stats line isn't saying we applied a '[' filter
|
||||||
|
|
||||||
def test_stat_line(self):
|
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):
|
def test_groups(self):
|
||||||
self.assertEqual(0,len(self.results.groups))
|
eq_(0,len(self.results.groups))
|
||||||
|
|
||||||
def test_get_group_of_duplicate(self):
|
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):
|
def test_save_to_xml(self):
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
self.results.save_to_xml(f)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
doc = xml.dom.minidom.parse(f)
|
doc = ET.parse(f)
|
||||||
root = doc.documentElement
|
root = doc.getroot()
|
||||||
self.assertEqual('results',root.nodeName)
|
eq_('results', root.tag)
|
||||||
|
|
||||||
|
def test_is_modified(self):
|
||||||
|
assert not self.results.is_modified
|
||||||
|
|
||||||
|
|
||||||
class TCResultsWithSomeGroups(TestCase):
|
class TCResultsWithSomeGroups(TestCase):
|
||||||
@@ -77,57 +81,57 @@ class TCResultsWithSomeGroups(TestCase):
|
|||||||
self.results.groups = self.groups
|
self.results.groups = self.groups
|
||||||
|
|
||||||
def test_stat_line(self):
|
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):
|
def test_groups(self):
|
||||||
self.assertEqual(2,len(self.results.groups))
|
eq_(2,len(self.results.groups))
|
||||||
|
|
||||||
def test_get_group_of_duplicate(self):
|
def test_get_group_of_duplicate(self):
|
||||||
for o in self.objects:
|
for o in self.objects:
|
||||||
g = self.results.get_group_of_duplicate(o)
|
g = self.results.get_group_of_duplicate(o)
|
||||||
self.assert_(isinstance(g, engine.Group))
|
assert isinstance(g, engine.Group)
|
||||||
self.assert_(o in g)
|
assert o in g
|
||||||
self.assert_(self.results.get_group_of_duplicate(self.groups[0]) is None)
|
assert self.results.get_group_of_duplicate(self.groups[0]) is None
|
||||||
|
|
||||||
def test_remove_duplicates(self):
|
def test_remove_duplicates(self):
|
||||||
g1,g2 = self.results.groups
|
g1,g2 = self.results.groups
|
||||||
self.results.remove_duplicates([g1.dupes[0]])
|
self.results.remove_duplicates([g1.dupes[0]])
|
||||||
self.assertEqual(2,len(g1))
|
eq_(2,len(g1))
|
||||||
self.assert_(g1 in self.results.groups)
|
assert g1 in self.results.groups
|
||||||
self.results.remove_duplicates([g1.ref])
|
self.results.remove_duplicates([g1.ref])
|
||||||
self.assertEqual(2,len(g1))
|
eq_(2,len(g1))
|
||||||
self.assert_(g1 in self.results.groups)
|
assert g1 in self.results.groups
|
||||||
self.results.remove_duplicates([g1.dupes[0]])
|
self.results.remove_duplicates([g1.dupes[0]])
|
||||||
self.assertEqual(0,len(g1))
|
eq_(0,len(g1))
|
||||||
self.assert_(g1 not in self.results.groups)
|
assert g1 not in self.results.groups
|
||||||
self.results.remove_duplicates([g2.dupes[0]])
|
self.results.remove_duplicates([g2.dupes[0]])
|
||||||
self.assertEqual(0,len(g2))
|
eq_(0,len(g2))
|
||||||
self.assert_(g2 not in self.results.groups)
|
assert g2 not in self.results.groups
|
||||||
self.assertEqual(0,len(self.results.groups))
|
eq_(0,len(self.results.groups))
|
||||||
|
|
||||||
def test_remove_duplicates_with_ref_files(self):
|
def test_remove_duplicates_with_ref_files(self):
|
||||||
g1,g2 = self.results.groups
|
g1,g2 = self.results.groups
|
||||||
self.objects[0].is_ref = True
|
self.objects[0].is_ref = True
|
||||||
self.objects[1].is_ref = True
|
self.objects[1].is_ref = True
|
||||||
self.results.remove_duplicates([self.objects[2]])
|
self.results.remove_duplicates([self.objects[2]])
|
||||||
self.assertEqual(0,len(g1))
|
eq_(0,len(g1))
|
||||||
self.assert_(g1 not in self.results.groups)
|
assert g1 not in self.results.groups
|
||||||
|
|
||||||
def test_make_ref(self):
|
def test_make_ref(self):
|
||||||
g = self.results.groups[0]
|
g = self.results.groups[0]
|
||||||
d = g.dupes[0]
|
d = g.dupes[0]
|
||||||
self.results.make_ref(d)
|
self.results.make_ref(d)
|
||||||
self.assert_(d is g.ref)
|
assert d is g.ref
|
||||||
|
|
||||||
def test_sort_groups(self):
|
def test_sort_groups(self):
|
||||||
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
||||||
g1,g2 = self.groups
|
g1,g2 = self.groups
|
||||||
self.results.sort_groups(2) #2 is the key for size
|
self.results.sort_groups(2) #2 is the key for size
|
||||||
self.assert_(self.results.groups[0] is g2)
|
assert self.results.groups[0] is g2
|
||||||
self.assert_(self.results.groups[1] is g1)
|
assert self.results.groups[1] is g1
|
||||||
self.results.sort_groups(2,False)
|
self.results.sort_groups(2,False)
|
||||||
self.assert_(self.results.groups[0] is g1)
|
assert self.results.groups[0] is g1
|
||||||
self.assert_(self.results.groups[1] is g2)
|
assert self.results.groups[1] is g2
|
||||||
|
|
||||||
def test_set_groups_when_sorted(self):
|
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.
|
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
||||||
@@ -136,24 +140,24 @@ class TCResultsWithSomeGroups(TestCase):
|
|||||||
g1,g2 = groups
|
g1,g2 = groups
|
||||||
g1.switch_ref(objects[1])
|
g1.switch_ref(objects[1])
|
||||||
self.results.groups = groups
|
self.results.groups = groups
|
||||||
self.assert_(self.results.groups[0] is g2)
|
assert self.results.groups[0] is g2
|
||||||
self.assert_(self.results.groups[1] is g1)
|
assert self.results.groups[1] is g1
|
||||||
|
|
||||||
def test_get_dupe_list(self):
|
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):
|
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):
|
def test_dupe_list_cache_is_invalidated_when_needed(self):
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
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.results.make_ref(o2)
|
||||||
self.assertEqual([o1,o3,o5],self.results.dupes)
|
eq_([o1,o3,o5],self.results.dupes)
|
||||||
objects,matches,groups = GetTestGroups()
|
objects,matches,groups = GetTestGroups()
|
||||||
o1,o2,o3,o4,o5 = objects
|
o1,o2,o3,o4,o5 = objects
|
||||||
self.results.groups = groups
|
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):
|
def test_dupe_list_sort(self):
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
o1,o2,o3,o4,o5 = self.objects
|
||||||
@@ -163,9 +167,9 @@ class TCResultsWithSomeGroups(TestCase):
|
|||||||
o4.size = 2
|
o4.size = 2
|
||||||
o5.size = 1
|
o5.size = 1
|
||||||
self.results.sort_dupes(2)
|
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.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):
|
def test_dupe_list_remember_sort(self):
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
o1,o2,o3,o4,o5 = self.objects
|
||||||
@@ -176,7 +180,7 @@ class TCResultsWithSomeGroups(TestCase):
|
|||||||
o5.size = 1
|
o5.size = 1
|
||||||
self.results.sort_dupes(2)
|
self.results.sort_dupes(2)
|
||||||
self.results.make_ref(o2)
|
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):
|
def test_dupe_list_sort_delta_values(self):
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
o1,o2,o3,o4,o5 = self.objects
|
||||||
@@ -186,19 +190,69 @@ class TCResultsWithSomeGroups(TestCase):
|
|||||||
o4.size = 20
|
o4.size = 20
|
||||||
o5.size = 1 #-19
|
o5.size = 1 #-19
|
||||||
self.results.sort_dupes(2,delta=True)
|
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):
|
def test_sort_empty_list(self):
|
||||||
#There was an infinite loop when sorting an empty list.
|
#There was an infinite loop when sorting an empty list.
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.sort_dupes(0)
|
r.sort_dupes(0)
|
||||||
self.assertEqual([],r.dupes)
|
eq_([],r.dupes)
|
||||||
|
|
||||||
def test_dupe_list_update_on_remove_duplicates(self):
|
def test_dupe_list_update_on_remove_duplicates(self):
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
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.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):
|
class TCResultsMarkings(TestCase):
|
||||||
@@ -208,27 +262,27 @@ class TCResultsMarkings(TestCase):
|
|||||||
self.results.groups = self.groups
|
self.results.groups = self.groups
|
||||||
|
|
||||||
def test_stat_line(self):
|
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.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.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.mark_invert()
|
||||||
self.results.unmark(self.objects[1])
|
self.results.unmark(self.objects[1])
|
||||||
self.results.mark(self.objects[2])
|
self.results.mark(self.objects[2])
|
||||||
self.results.mark(self.objects[4])
|
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.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.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):
|
def test_with_ref_duplicate(self):
|
||||||
self.objects[1].is_ref = True
|
self.objects[1].is_ref = True
|
||||||
self.results.groups = self.groups
|
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.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 test_perform_on_marked(self):
|
||||||
def log_object(o):
|
def log_object(o):
|
||||||
@@ -238,33 +292,38 @@ class TCResultsMarkings(TestCase):
|
|||||||
log = []
|
log = []
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
self.results.perform_on_marked(log_object,False)
|
self.results.perform_on_marked(log_object,False)
|
||||||
self.assert_(self.objects[1] in log)
|
assert self.objects[1] in log
|
||||||
self.assert_(self.objects[2] in log)
|
assert self.objects[2] in log
|
||||||
self.assert_(self.objects[4] in log)
|
assert self.objects[4] in log
|
||||||
self.assertEqual(3,len(log))
|
eq_(3,len(log))
|
||||||
log = []
|
log = []
|
||||||
self.results.mark_none()
|
self.results.mark_none()
|
||||||
self.results.mark(self.objects[4])
|
self.results.mark(self.objects[4])
|
||||||
self.results.perform_on_marked(log_object,True)
|
self.results.perform_on_marked(log_object,True)
|
||||||
self.assertEqual(1,len(log))
|
eq_(1,len(log))
|
||||||
self.assert_(self.objects[4] in log)
|
assert self.objects[4] in log
|
||||||
self.assertEqual(1,len(self.results.groups))
|
eq_(1,len(self.results.groups))
|
||||||
|
|
||||||
def test_perform_on_marked_with_problems(self):
|
def test_perform_on_marked_with_problems(self):
|
||||||
def log_object(o):
|
def log_object(o):
|
||||||
log.append(o)
|
log.append(o)
|
||||||
return o is not self.objects[1]
|
if o is self.objects[1]:
|
||||||
|
raise EnvironmentError('foobar')
|
||||||
|
|
||||||
log = []
|
log = []
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
self.assert_(self.results.is_marked(self.objects[1]))
|
assert self.results.is_marked(self.objects[1])
|
||||||
self.assertEqual(1,self.results.perform_on_marked(log_object, True))
|
self.results.perform_on_marked(log_object, True)
|
||||||
self.assertEqual(3,len(log))
|
eq_(len(log), 3)
|
||||||
self.assertEqual(1,len(self.results.groups))
|
eq_(len(self.results.groups), 1)
|
||||||
self.assertEqual(2,len(self.results.groups[0]))
|
eq_(len(self.results.groups[0]), 2)
|
||||||
self.assert_(self.objects[1] in self.results.groups[0])
|
assert self.objects[1] in self.results.groups[0]
|
||||||
self.assert_(not self.results.is_marked(self.objects[2]))
|
assert not self.results.is_marked(self.objects[2])
|
||||||
self.assert_(self.results.is_marked(self.objects[1]))
|
assert self.results.is_marked(self.objects[1])
|
||||||
|
eq_(len(self.results.problems), 1)
|
||||||
|
dupe, msg = self.results.problems[0]
|
||||||
|
assert dupe is self.objects[1]
|
||||||
|
eq_(msg, 'foobar')
|
||||||
|
|
||||||
def test_perform_on_marked_with_ref(self):
|
def test_perform_on_marked_with_ref(self):
|
||||||
def log_object(o):
|
def log_object(o):
|
||||||
@@ -276,61 +335,61 @@ class TCResultsMarkings(TestCase):
|
|||||||
self.objects[1].is_ref = True
|
self.objects[1].is_ref = True
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
self.results.perform_on_marked(log_object,True)
|
self.results.perform_on_marked(log_object,True)
|
||||||
self.assert_(self.objects[1] not in log)
|
assert self.objects[1] not in log
|
||||||
self.assert_(self.objects[2] in log)
|
assert self.objects[2] in log
|
||||||
self.assert_(self.objects[4] in log)
|
assert self.objects[4] in log
|
||||||
self.assertEqual(2,len(log))
|
eq_(2,len(log))
|
||||||
self.assertEqual(0,len(self.results.groups))
|
eq_(0,len(self.results.groups))
|
||||||
|
|
||||||
def test_perform_on_marked_remove_objects_only_at_the_end(self):
|
def test_perform_on_marked_remove_objects_only_at_the_end(self):
|
||||||
def check_groups(o):
|
def check_groups(o):
|
||||||
self.assertEqual(3,len(g1))
|
eq_(3,len(g1))
|
||||||
self.assertEqual(2,len(g2))
|
eq_(2,len(g2))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
g1,g2 = self.results.groups
|
g1,g2 = self.results.groups
|
||||||
self.results.mark_all()
|
self.results.mark_all()
|
||||||
self.results.perform_on_marked(check_groups,True)
|
self.results.perform_on_marked(check_groups,True)
|
||||||
self.assertEqual(0,len(g1))
|
eq_(0,len(g1))
|
||||||
self.assertEqual(0,len(g2))
|
eq_(0,len(g2))
|
||||||
self.assertEqual(0,len(self.results.groups))
|
eq_(0,len(self.results.groups))
|
||||||
|
|
||||||
def test_remove_duplicates(self):
|
def test_remove_duplicates(self):
|
||||||
g1 = self.results.groups[0]
|
g1 = self.results.groups[0]
|
||||||
g2 = self.results.groups[1]
|
g2 = self.results.groups[1]
|
||||||
self.results.mark(g1.dupes[0])
|
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.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.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):
|
def test_make_ref(self):
|
||||||
g = self.results.groups[0]
|
g = self.results.groups[0]
|
||||||
d = g.dupes[0]
|
d = g.dupes[0]
|
||||||
self.results.mark(d)
|
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.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.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):
|
def test_SaveXML(self):
|
||||||
self.results.mark(self.objects[1])
|
self.results.mark(self.objects[1])
|
||||||
self.results.mark_invert()
|
self.results.mark_invert()
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
self.results.save_to_xml(f)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
doc = xml.dom.minidom.parse(f)
|
doc = ET.parse(f)
|
||||||
root = doc.documentElement
|
root = doc.getroot()
|
||||||
g1,g2 = root.getElementsByTagName('group')
|
g1, g2 = root.getiterator('group')
|
||||||
d1,d2,d3 = g1.getElementsByTagName('file')
|
d1, d2, d3 = g1.getiterator('file')
|
||||||
self.assertEqual('n',d1.getAttributeNode('marked').nodeValue)
|
eq_('n', d1.get('marked'))
|
||||||
self.assertEqual('n',d2.getAttributeNode('marked').nodeValue)
|
eq_('n', d2.get('marked'))
|
||||||
self.assertEqual('y',d3.getAttributeNode('marked').nodeValue)
|
eq_('y', d3.get('marked'))
|
||||||
d1,d2 = g2.getElementsByTagName('file')
|
d1, d2 = g2.getiterator('file')
|
||||||
self.assertEqual('n',d1.getAttributeNode('marked').nodeValue)
|
eq_('n', d1.get('marked'))
|
||||||
self.assertEqual('y',d2.getAttributeNode('marked').nodeValue)
|
eq_('y', d2.get('marked'))
|
||||||
|
|
||||||
def test_LoadXML(self):
|
def test_LoadXML(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
@@ -339,16 +398,16 @@ class TCResultsMarkings(TestCase):
|
|||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
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(self.objects[1])
|
||||||
self.results.mark_invert()
|
self.results.mark_invert()
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
self.results.save_to_xml(f)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,get_file)
|
r.load_from_xml(f,get_file)
|
||||||
self.assert_(not r.is_marked(self.objects[0]))
|
assert not r.is_marked(self.objects[0])
|
||||||
self.assert_(not r.is_marked(self.objects[1]))
|
assert not r.is_marked(self.objects[1])
|
||||||
self.assert_(r.is_marked(self.objects[2]))
|
assert r.is_marked(self.objects[2])
|
||||||
self.assert_(not r.is_marked(self.objects[3]))
|
assert not r.is_marked(self.objects[3])
|
||||||
self.assert_(r.is_marked(self.objects[4]))
|
assert r.is_marked(self.objects[4])
|
||||||
|
|
||||||
|
|
||||||
class TCResultsXML(TestCase):
|
class TCResultsXML(TestCase):
|
||||||
@@ -363,41 +422,38 @@ class TCResultsXML(TestCase):
|
|||||||
def test_save_to_xml(self):
|
def test_save_to_xml(self):
|
||||||
self.objects[0].is_ref = True
|
self.objects[0].is_ref = True
|
||||||
self.objects[0].words = [['foo','bar']]
|
self.objects[0].words = [['foo','bar']]
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
self.results.save_to_xml(f)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
doc = xml.dom.minidom.parse(f)
|
doc = ET.parse(f)
|
||||||
root = doc.documentElement
|
root = doc.getroot()
|
||||||
self.assertEqual('results',root.nodeName)
|
eq_('results', root.tag)
|
||||||
children = [c for c in root.childNodes if c.localName]
|
eq_(2, len(root))
|
||||||
self.assertEqual(2,len(children))
|
eq_(2, len([c for c in root if c.tag == 'group']))
|
||||||
self.assertEqual(2,len([c for c in children if c.nodeName == 'group']))
|
g1, g2 = root
|
||||||
g1,g2 = children
|
eq_(6,len(g1))
|
||||||
children = [c for c in g1.childNodes if c.localName]
|
eq_(3,len([c for c in g1 if c.tag == 'file']))
|
||||||
self.assertEqual(6,len(children))
|
eq_(3,len([c for c in g1 if c.tag == 'match']))
|
||||||
self.assertEqual(3,len([c for c in children if c.nodeName == 'file']))
|
d1, d2, d3 = [c for c in g1 if c.tag == 'file']
|
||||||
self.assertEqual(3,len([c for c in children if c.nodeName == 'match']))
|
eq_(op.join('basepath','foo bar'),d1.get('path'))
|
||||||
d1,d2,d3 = [c for c in children if c.nodeName == 'file']
|
eq_(op.join('basepath','bar bleh'),d2.get('path'))
|
||||||
self.assertEqual(op.join('basepath','foo bar'),d1.getAttributeNode('path').nodeValue)
|
eq_(op.join('basepath','foo bleh'),d3.get('path'))
|
||||||
self.assertEqual(op.join('basepath','bar bleh'),d2.getAttributeNode('path').nodeValue)
|
eq_('y',d1.get('is_ref'))
|
||||||
self.assertEqual(op.join('basepath','foo bleh'),d3.getAttributeNode('path').nodeValue)
|
eq_('n',d2.get('is_ref'))
|
||||||
self.assertEqual('y',d1.getAttributeNode('is_ref').nodeValue)
|
eq_('n',d3.get('is_ref'))
|
||||||
self.assertEqual('n',d2.getAttributeNode('is_ref').nodeValue)
|
eq_('foo,bar',d1.get('words'))
|
||||||
self.assertEqual('n',d3.getAttributeNode('is_ref').nodeValue)
|
eq_('bar,bleh',d2.get('words'))
|
||||||
self.assertEqual('foo,bar',d1.getAttributeNode('words').nodeValue)
|
eq_('foo,bleh',d3.get('words'))
|
||||||
self.assertEqual('bar,bleh',d2.getAttributeNode('words').nodeValue)
|
eq_(3,len(g2))
|
||||||
self.assertEqual('foo,bleh',d3.getAttributeNode('words').nodeValue)
|
eq_(2,len([c for c in g2 if c.tag == 'file']))
|
||||||
children = [c for c in g2.childNodes if c.localName]
|
eq_(1,len([c for c in g2 if c.tag == 'match']))
|
||||||
self.assertEqual(3,len(children))
|
d1, d2 = [c for c in g2 if c.tag == 'file']
|
||||||
self.assertEqual(2,len([c for c in children if c.nodeName == 'file']))
|
eq_(op.join('basepath','ibabtu'),d1.get('path'))
|
||||||
self.assertEqual(1,len([c for c in children if c.nodeName == 'match']))
|
eq_(op.join('basepath','ibabtu'),d2.get('path'))
|
||||||
d1,d2 = [c for c in children if c.nodeName == 'file']
|
eq_('n',d1.get('is_ref'))
|
||||||
self.assertEqual(op.join('basepath','ibabtu'),d1.getAttributeNode('path').nodeValue)
|
eq_('n',d2.get('is_ref'))
|
||||||
self.assertEqual(op.join('basepath','ibabtu'),d2.getAttributeNode('path').nodeValue)
|
eq_('ibabtu',d1.get('words'))
|
||||||
self.assertEqual('n',d1.getAttributeNode('is_ref').nodeValue)
|
eq_('ibabtu',d2.get('words'))
|
||||||
self.assertEqual('n',d2.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('ibabtu',d1.getAttributeNode('words').nodeValue)
|
|
||||||
self.assertEqual('ibabtu',d2.getAttributeNode('words').nodeValue)
|
|
||||||
|
|
||||||
def test_LoadXML(self):
|
def test_LoadXML(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
@@ -405,30 +461,30 @@ class TCResultsXML(TestCase):
|
|||||||
|
|
||||||
self.objects[0].is_ref = True
|
self.objects[0].is_ref = True
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
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)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,get_file)
|
r.load_from_xml(f,get_file)
|
||||||
self.assertEqual(2,len(r.groups))
|
eq_(2,len(r.groups))
|
||||||
g1,g2 = r.groups
|
g1,g2 = r.groups
|
||||||
self.assertEqual(3,len(g1))
|
eq_(3,len(g1))
|
||||||
self.assert_(g1[0].is_ref)
|
assert g1[0].is_ref
|
||||||
self.assert_(not g1[1].is_ref)
|
assert not g1[1].is_ref
|
||||||
self.assert_(not g1[2].is_ref)
|
assert not g1[2].is_ref
|
||||||
self.assert_(g1[0] is self.objects[0])
|
assert g1[0] is self.objects[0]
|
||||||
self.assert_(g1[1] is self.objects[1])
|
assert g1[1] is self.objects[1]
|
||||||
self.assert_(g1[2] is self.objects[2])
|
assert g1[2] is self.objects[2]
|
||||||
self.assertEqual(['foo','bar'],g1[0].words)
|
eq_(['foo','bar'],g1[0].words)
|
||||||
self.assertEqual(['bar','bleh'],g1[1].words)
|
eq_(['bar','bleh'],g1[1].words)
|
||||||
self.assertEqual(['foo','bleh'],g1[2].words)
|
eq_(['foo','bleh'],g1[2].words)
|
||||||
self.assertEqual(2,len(g2))
|
eq_(2,len(g2))
|
||||||
self.assert_(not g2[0].is_ref)
|
assert not g2[0].is_ref
|
||||||
self.assert_(not g2[1].is_ref)
|
assert not g2[1].is_ref
|
||||||
self.assert_(g2[0] is self.objects[3])
|
assert g2[0] is self.objects[3]
|
||||||
self.assert_(g2[1] is self.objects[4])
|
assert g2[1] is self.objects[4]
|
||||||
self.assertEqual(['ibabtu'],g2[0].words)
|
eq_(['ibabtu'],g2[0].words)
|
||||||
self.assertEqual(['ibabtu'],g2[1].words)
|
eq_(['ibabtu'],g2[1].words)
|
||||||
|
|
||||||
def test_LoadXML_with_filename(self):
|
def test_LoadXML_with_filename(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
@@ -439,7 +495,7 @@ class TCResultsXML(TestCase):
|
|||||||
self.results.save_to_xml(filename)
|
self.results.save_to_xml(filename)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(filename,get_file)
|
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 test_LoadXML_with_some_files_that_dont_exist_anymore(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
@@ -448,84 +504,84 @@ class TCResultsXML(TestCase):
|
|||||||
return [f for f in self.objects if str(f.path) == path][0]
|
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
|
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)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,get_file)
|
r.load_from_xml(f,get_file)
|
||||||
self.assertEqual(1,len(r.groups))
|
eq_(1,len(r.groups))
|
||||||
self.assertEqual(3,len(r.groups[0]))
|
eq_(3,len(r.groups[0]))
|
||||||
|
|
||||||
def test_LoadXML_missing_attributes_and_bogus_elements(self):
|
def test_LoadXML_missing_attributes_and_bogus_elements(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
return [f for f in self.objects if str(f.path) == path][0]
|
||||||
|
|
||||||
doc = xml.dom.minidom.Document()
|
root = ET.Element('foobar') #The root element shouldn't matter, really.
|
||||||
root = doc.appendChild(doc.createElement('foobar')) #The root element shouldn't matter, really.
|
group_node = ET.SubElement(root, 'group')
|
||||||
group_node = root.appendChild(doc.createElement('group'))
|
dupe_node = ET.SubElement(group_node, 'file') #Perfectly correct file
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #Perfectly correct file
|
dupe_node.set('path', op.join('basepath','foo bar'))
|
||||||
dupe_node.setAttribute('path',op.join('basepath','foo bar'))
|
dupe_node.set('is_ref', 'y')
|
||||||
dupe_node.setAttribute('is_ref','y')
|
dupe_node.set('words', 'foo,bar')
|
||||||
dupe_node.setAttribute('words','foo,bar')
|
dupe_node = ET.SubElement(group_node, 'file') #is_ref missing, default to 'n'
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #is_ref missing, default to 'n'
|
dupe_node.set('path',op.join('basepath','foo bleh'))
|
||||||
dupe_node.setAttribute('path',op.join('basepath','foo bleh'))
|
dupe_node.set('words','foo,bleh')
|
||||||
dupe_node.setAttribute('words','foo,bleh')
|
dupe_node = ET.SubElement(group_node, 'file') #words are missing, valid.
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #words are missing, invalid.
|
dupe_node.set('path',op.join('basepath','bar bleh'))
|
||||||
dupe_node.setAttribute('path',op.join('basepath','bar bleh'))
|
dupe_node = ET.SubElement(group_node, 'file') #path is missing, invalid.
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #path is missing, invalid.
|
dupe_node.set('words','foo,bleh')
|
||||||
dupe_node.setAttribute('words','foo,bleh')
|
dupe_node = ET.SubElement(group_node, 'foobar') #Invalid element name
|
||||||
dupe_node = group_node.appendChild(doc.createElement('foobar')) #Invalid element name
|
dupe_node.set('path',op.join('basepath','bar bleh'))
|
||||||
dupe_node.setAttribute('path',op.join('basepath','bar bleh'))
|
dupe_node.set('is_ref','y')
|
||||||
dupe_node.setAttribute('is_ref','y')
|
dupe_node.set('words','bar,bleh')
|
||||||
dupe_node.setAttribute('words','bar,bleh')
|
match_node = ET.SubElement(group_node, 'match') # match pointing to a bad index
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match pointing to a bad index
|
match_node.set('first', '42')
|
||||||
match_node.setAttribute('first', '42')
|
match_node.set('second', '45')
|
||||||
match_node.setAttribute('second', '45')
|
match_node = ET.SubElement(group_node, 'match') # match with missing attrs
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match with missing attrs
|
match_node = ET.SubElement(group_node, 'match') # match with non-int values
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match with non-int values
|
match_node.set('first', 'foo')
|
||||||
match_node.setAttribute('first', 'foo')
|
match_node.set('second', 'bar')
|
||||||
match_node.setAttribute('second', 'bar')
|
match_node.set('percentage', 'baz')
|
||||||
match_node.setAttribute('percentage', 'baz')
|
group_node = ET.SubElement(root, 'foobar') #invalid group
|
||||||
group_node = root.appendChild(doc.createElement('foobar')) #invalid group
|
group_node = ET.SubElement(root, 'group') #empty group
|
||||||
group_node = root.appendChild(doc.createElement('group')) #empty group
|
f = io.BytesIO()
|
||||||
f = StringIO.StringIO()
|
tree = ET.ElementTree(root)
|
||||||
doc.writexml(f,'\t','\t','\n',encoding='utf-8')
|
tree.write(f, encoding='utf-8')
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,get_file)
|
r.load_from_xml(f, get_file)
|
||||||
self.assertEqual(1,len(r.groups))
|
eq_(1,len(r.groups))
|
||||||
self.assertEqual(2,len(r.groups[0]))
|
eq_(3,len(r.groups[0]))
|
||||||
|
|
||||||
def test_xml_non_ascii(self):
|
def test_xml_non_ascii(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
if path == op.join('basepath',u'\xe9foo bar'):
|
if path == op.join('basepath','\xe9foo bar'):
|
||||||
return objects[0]
|
return objects[0]
|
||||||
if path == op.join('basepath',u'bar bleh'):
|
if path == op.join('basepath','bar bleh'):
|
||||||
return objects[1]
|
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
|
matches = engine.getmatches(objects) #we should have 5 matches
|
||||||
groups = engine.get_groups(matches) #We should have 2 groups
|
groups = engine.get_groups(matches) #We should have 2 groups
|
||||||
for g in groups:
|
for g in groups:
|
||||||
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
||||||
results = Results(data)
|
results = Results(data)
|
||||||
results.groups = groups
|
results.groups = groups
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
results.save_to_xml(f)
|
results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,get_file)
|
r.load_from_xml(f,get_file)
|
||||||
g = r.groups[0]
|
g = r.groups[0]
|
||||||
self.assertEqual(u"\xe9foo bar",g[0].name)
|
eq_("\xe9foo bar",g[0].name)
|
||||||
self.assertEqual(['efoo','bar'],g[0].words)
|
eq_(['efoo','bar'],g[0].words)
|
||||||
|
|
||||||
def test_load_invalid_xml(self):
|
def test_load_invalid_xml(self):
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
f.write('<this is invalid')
|
f.write(b'<this is invalid')
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.load_from_xml(f,None)
|
r.load_from_xml(f,None)
|
||||||
self.assertEqual(0,len(r.groups))
|
eq_(0,len(r.groups))
|
||||||
|
|
||||||
def test_load_non_existant_xml(self):
|
def test_load_non_existant_xml(self):
|
||||||
r = Results(data)
|
r = Results(data)
|
||||||
@@ -533,7 +589,7 @@ class TCResultsXML(TestCase):
|
|||||||
r.load_from_xml('does_not_exist.xml', None)
|
r.load_from_xml('does_not_exist.xml', None)
|
||||||
except IOError:
|
except IOError:
|
||||||
self.fail()
|
self.fail()
|
||||||
self.assertEqual(0,len(r.groups))
|
eq_(0,len(r.groups))
|
||||||
|
|
||||||
def test_remember_match_percentage(self):
|
def test_remember_match_percentage(self):
|
||||||
group = self.groups[0]
|
group = self.groups[0]
|
||||||
@@ -543,7 +599,7 @@ class TCResultsXML(TestCase):
|
|||||||
fake_matches.add(engine.Match(d1, d3, 43))
|
fake_matches.add(engine.Match(d1, d3, 43))
|
||||||
fake_matches.add(engine.Match(d2, d3, 46))
|
fake_matches.add(engine.Match(d2, d3, 46))
|
||||||
group.matches = fake_matches
|
group.matches = fake_matches
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
results = self.results
|
results = self.results
|
||||||
results.save_to_xml(f)
|
results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
@@ -552,21 +608,31 @@ class TCResultsXML(TestCase):
|
|||||||
group = results.groups[0]
|
group = results.groups[0]
|
||||||
d1, d2, d3 = group
|
d1, d2, d3 = group
|
||||||
match = group.get_match_of(d2) #d1 - d2
|
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
|
match = group.get_match_of(d3) #d1 - d3
|
||||||
self.assertEqual(43, match[2])
|
eq_(43, match[2])
|
||||||
group.switch_ref(d2)
|
group.switch_ref(d2)
|
||||||
match = group.get_match_of(d3) #d2 - d3
|
match = group.get_match_of(d3) #d2 - d3
|
||||||
self.assertEqual(46, match[2])
|
eq_(46, match[2])
|
||||||
|
|
||||||
def test_save_and_load(self):
|
def test_save_and_load(self):
|
||||||
# previously, when reloading matches, they wouldn't be reloaded as namedtuples
|
# previously, when reloading matches, they wouldn't be reloaded as namedtuples
|
||||||
f = StringIO.StringIO()
|
f = io.BytesIO()
|
||||||
self.results.save_to_xml(f)
|
self.results.save_to_xml(f)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
self.results.load_from_xml(f, self.get_file)
|
self.results.load_from_xml(f, self.get_file)
|
||||||
first(self.results.groups[0].matches).percentage
|
first(self.results.groups[0].matches).percentage
|
||||||
|
|
||||||
|
def test_apply_filter_works_on_paths(self):
|
||||||
|
# apply_filter() searches on the whole path, not just on the filename.
|
||||||
|
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 = 'foo\x19'
|
||||||
|
self.results.save_to_xml(io.BytesIO()) # don't crash
|
||||||
|
|
||||||
|
|
||||||
class TCResultsFilter(TestCase):
|
class TCResultsFilter(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -576,47 +642,47 @@ class TCResultsFilter(TestCase):
|
|||||||
self.results.apply_filter(r'foo')
|
self.results.apply_filter(r'foo')
|
||||||
|
|
||||||
def test_groups(self):
|
def test_groups(self):
|
||||||
self.assertEqual(1, len(self.results.groups))
|
eq_(1, len(self.results.groups))
|
||||||
self.assert_(self.results.groups[0] is self.groups[0])
|
assert self.results.groups[0] is self.groups[0]
|
||||||
|
|
||||||
def test_dupes(self):
|
def test_dupes(self):
|
||||||
# There are 2 objects matching. The first one is ref. Only the 3rd one is supposed to be in dupes.
|
# 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))
|
eq_(1, len(self.results.dupes))
|
||||||
self.assert_(self.results.dupes[0] is self.objects[2])
|
assert self.results.dupes[0] is self.objects[2]
|
||||||
|
|
||||||
def test_cancel_filter(self):
|
def test_cancel_filter(self):
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.assertEqual(3, len(self.results.dupes))
|
eq_(3, len(self.results.dupes))
|
||||||
self.assertEqual(2, len(self.results.groups))
|
eq_(2, len(self.results.groups))
|
||||||
|
|
||||||
def test_dupes_reconstructed_filtered(self):
|
def test_dupes_reconstructed_filtered(self):
|
||||||
# make_ref resets self.__dupes to None. When it's reconstructed, we want it filtered
|
# make_ref resets self.__dupes to None. When it's reconstructed, we want it filtered
|
||||||
dupe = self.results.dupes[0] #3rd object
|
dupe = self.results.dupes[0] #3rd object
|
||||||
self.results.make_ref(dupe)
|
self.results.make_ref(dupe)
|
||||||
self.assertEqual(1, len(self.results.dupes))
|
eq_(1, len(self.results.dupes))
|
||||||
self.assert_(self.results.dupes[0] is self.objects[0])
|
assert self.results.dupes[0] is self.objects[0]
|
||||||
|
|
||||||
def test_include_ref_dupes_in_filter(self):
|
def test_include_ref_dupes_in_filter(self):
|
||||||
# When only the ref of a group match the filter, include it in the group
|
# When only the ref of a group match the filter, include it in the group
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.results.apply_filter(r'foo bar')
|
self.results.apply_filter(r'foo bar')
|
||||||
self.assertEqual(1, len(self.results.groups))
|
eq_(1, len(self.results.groups))
|
||||||
self.assertEqual(0, len(self.results.dupes))
|
eq_(0, len(self.results.dupes))
|
||||||
|
|
||||||
def test_filters_build_on_one_another(self):
|
def test_filters_build_on_one_another(self):
|
||||||
self.results.apply_filter(r'bar')
|
self.results.apply_filter(r'bar')
|
||||||
self.assertEqual(1, len(self.results.groups))
|
eq_(1, len(self.results.groups))
|
||||||
self.assertEqual(0, len(self.results.dupes))
|
eq_(0, len(self.results.dupes))
|
||||||
|
|
||||||
def test_stat_line(self):
|
def test_stat_line(self):
|
||||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked. filter: foo'
|
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')
|
self.results.apply_filter(r'bar')
|
||||||
expected = '0 / 0 (0.00 B / 0.00 B) duplicates marked. filter: foo --> 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)
|
self.results.apply_filter(None)
|
||||||
expected = '0 / 3 (0.00 B / 1.01 KB) duplicates marked.'
|
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):
|
def test_mark_count_is_filtered_as_well(self):
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
@@ -625,7 +691,7 @@ class TCResultsFilter(TestCase):
|
|||||||
self.results.mark(dupe)
|
self.results.mark(dupe)
|
||||||
self.results.apply_filter(r'foo')
|
self.results.apply_filter(r'foo')
|
||||||
expected = '1 / 1 (1.00 B / 1.00 B) duplicates marked. filter: 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):
|
def test_sort_groups(self):
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
@@ -633,22 +699,22 @@ class TCResultsFilter(TestCase):
|
|||||||
g1,g2 = self.groups
|
g1,g2 = self.groups
|
||||||
self.results.apply_filter('a') # Matches both group
|
self.results.apply_filter('a') # Matches both group
|
||||||
self.results.sort_groups(2) #2 is the key for size
|
self.results.sort_groups(2) #2 is the key for size
|
||||||
self.assert_(self.results.groups[0] is g2)
|
assert self.results.groups[0] is g2
|
||||||
self.assert_(self.results.groups[1] is g1)
|
assert self.results.groups[1] is g1
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.assert_(self.results.groups[0] is g2)
|
assert self.results.groups[0] is g2
|
||||||
self.assert_(self.results.groups[1] is g1)
|
assert self.results.groups[1] is g1
|
||||||
self.results.sort_groups(2, False)
|
self.results.sort_groups(2, False)
|
||||||
self.results.apply_filter('a')
|
self.results.apply_filter('a')
|
||||||
self.assert_(self.results.groups[1] is g2)
|
assert self.results.groups[1] is g2
|
||||||
self.assert_(self.results.groups[0] is g1)
|
assert self.results.groups[0] is g1
|
||||||
|
|
||||||
def test_set_group(self):
|
def test_set_group(self):
|
||||||
#We want the new group to be filtered
|
#We want the new group to be filtered
|
||||||
self.objects, self.matches, self.groups = GetTestGroups()
|
self.objects, self.matches, self.groups = GetTestGroups()
|
||||||
self.results.groups = self.groups
|
self.results.groups = self.groups
|
||||||
self.assertEqual(1, len(self.results.groups))
|
eq_(1, len(self.results.groups))
|
||||||
self.assert_(self.results.groups[0] is self.groups[0])
|
assert self.results.groups[0] is self.groups[0]
|
||||||
|
|
||||||
def test_load_cancels_filter(self):
|
def test_load_cancels_filter(self):
|
||||||
def get_file(path):
|
def get_file(path):
|
||||||
@@ -660,23 +726,23 @@ class TCResultsFilter(TestCase):
|
|||||||
r = Results(data)
|
r = Results(data)
|
||||||
r.apply_filter('foo')
|
r.apply_filter('foo')
|
||||||
r.load_from_xml(filename,get_file)
|
r.load_from_xml(filename,get_file)
|
||||||
self.assertEqual(2,len(r.groups))
|
eq_(2,len(r.groups))
|
||||||
|
|
||||||
def test_remove_dupe(self):
|
def test_remove_dupe(self):
|
||||||
self.results.remove_duplicates([self.results.dupes[0]])
|
self.results.remove_duplicates([self.results.dupes[0]])
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.assertEqual(2,len(self.results.groups))
|
eq_(2,len(self.results.groups))
|
||||||
self.assertEqual(2,len(self.results.dupes))
|
eq_(2,len(self.results.dupes))
|
||||||
self.results.apply_filter('ibabtu')
|
self.results.apply_filter('ibabtu')
|
||||||
self.results.remove_duplicates([self.results.dupes[0]])
|
self.results.remove_duplicates([self.results.dupes[0]])
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.assertEqual(1,len(self.results.groups))
|
eq_(1,len(self.results.groups))
|
||||||
self.assertEqual(1,len(self.results.dupes))
|
eq_(1,len(self.results.dupes))
|
||||||
|
|
||||||
def test_filter_is_case_insensitive(self):
|
def test_filter_is_case_insensitive(self):
|
||||||
self.results.apply_filter(None)
|
self.results.apply_filter(None)
|
||||||
self.results.apply_filter('FOO')
|
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):
|
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.
|
# When filtered, a group containing filtered out dupes will display them as being reference.
|
||||||
@@ -687,10 +753,10 @@ class TCResultsFilter(TestCase):
|
|||||||
self.results.make_ref(bar_bleh)
|
self.results.make_ref(bar_bleh)
|
||||||
# Now the stats should display *2* markable dupes (instead of 1)
|
# Now the stats should display *2* markable dupes (instead of 1)
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked. filter: foo'
|
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
|
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.'
|
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):
|
class TCResultsRefFile(TestCase):
|
||||||
@@ -703,15 +769,15 @@ class TCResultsRefFile(TestCase):
|
|||||||
|
|
||||||
def test_stat_line(self):
|
def test_stat_line(self):
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
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):
|
def test_make_ref(self):
|
||||||
d = self.results.groups[0].dupes[1] #non-ref
|
d = self.results.groups[0].dupes[1] #non-ref
|
||||||
r = self.results.groups[0].ref
|
r = self.results.groups[0].ref
|
||||||
self.results.make_ref(d)
|
self.results.make_ref(d)
|
||||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked.'
|
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)
|
self.results.make_ref(r)
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
eq_(expected, self.results.stat_line)
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from hsutil import job, io
|
from hscommon import job
|
||||||
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
|
from hsutil.testutil import eq_
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
from .. import fs
|
from .. import fs
|
||||||
@@ -24,6 +25,9 @@ class NamedObject(object):
|
|||||||
self.path = Path('')
|
self.path = Path('')
|
||||||
self.words = getwords(name)
|
self.words = getwords(name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<NamedObject %r>' % self.name
|
||||||
|
|
||||||
|
|
||||||
no = NamedObject
|
no = NamedObject
|
||||||
|
|
||||||
@@ -42,7 +46,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
def test_default_settings(self):
|
def test_default_settings(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
eq_(s.min_match_percentage, 80)
|
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.mix_file_kind, True)
|
||||||
eq_(s.word_weighting, False)
|
eq_(s.word_weighting, False)
|
||||||
eq_(s.match_similar_words, False)
|
eq_(s.match_similar_words, False)
|
||||||
@@ -94,7 +98,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_content_scan(self):
|
def test_content_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = ScanType.Contents
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
f[0].md5 = f[0].md5partial = 'foobar'
|
f[0].md5 = f[0].md5partial = 'foobar'
|
||||||
f[1].md5 = f[1].md5partial = 'foobar'
|
f[1].md5 = f[1].md5partial = 'foobar'
|
||||||
@@ -111,13 +115,13 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
raise AssertionError()
|
raise AssertionError()
|
||||||
|
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = ScanType.Contents
|
||||||
f = [MyFile('foo', 1), MyFile('bar', 2)]
|
f = [MyFile('foo', 1), MyFile('bar', 2)]
|
||||||
eq_(len(s.GetDupeGroups(f)), 0)
|
eq_(len(s.GetDupeGroups(f)), 0)
|
||||||
|
|
||||||
def test_min_match_perc_doesnt_matter_for_content_scan(self):
|
def test_min_match_perc_doesnt_matter_for_content_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = ScanType.Contents
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
f[0].md5 = f[0].md5partial = 'foobar'
|
f[0].md5 = f[0].md5partial = 'foobar'
|
||||||
f[1].md5 = f[1].md5partial = 'foobar'
|
f[1].md5 = f[1].md5partial = 'foobar'
|
||||||
@@ -133,7 +137,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_content_scan_doesnt_put_md5_in_words_at_the_end(self):
|
def test_content_scan_doesnt_put_md5_in_words_at_the_end(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = ScanType.Contents
|
||||||
f = [no('foo'),no('bar')]
|
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[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'
|
f[1].md5 = f[1].md5partial = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
|
||||||
@@ -187,21 +191,21 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_fields(self):
|
def test_fields(self):
|
||||||
s = Scanner()
|
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')]
|
f = [no('The White Stripes - Little Ghost'), no('The White Stripes - Little Acorn')]
|
||||||
r = s.GetDupeGroups(f)
|
r = s.GetDupeGroups(f)
|
||||||
eq_(len(r), 0)
|
eq_(len(r), 0)
|
||||||
|
|
||||||
def test_fields_no_order(self):
|
def test_fields_no_order(self):
|
||||||
s = Scanner()
|
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')]
|
f = [no('The White Stripes - Little Ghost'), no('Little Ghost - The White Stripes')]
|
||||||
r = s.GetDupeGroups(f)
|
r = s.GetDupeGroups(f)
|
||||||
eq_(len(r), 1)
|
eq_(len(r), 1)
|
||||||
|
|
||||||
def test_tag_scan(self):
|
def test_tag_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
o1.artist = 'The White Stripes'
|
o1.artist = 'The White Stripes'
|
||||||
@@ -213,7 +217,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_tag_with_album_scan(self):
|
def test_tag_with_album_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
@@ -232,7 +236,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_that_dash_in_tags_dont_create_new_fields(self):
|
def test_that_dash_in_tags_dont_create_new_fields(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['artist', 'album', 'title'])
|
s.scanned_tags = set(['artist', 'album', 'title'])
|
||||||
s.min_match_percentage = 50
|
s.min_match_percentage = 50
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
@@ -248,7 +252,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_tag_scan_with_different_scanned(self):
|
def test_tag_scan_with_different_scanned(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['track', 'year'])
|
s.scanned_tags = set(['track', 'year'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
@@ -265,7 +269,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_tag_scan_only_scans_existing_tags(self):
|
def test_tag_scan_only_scans_existing_tags(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['artist', 'foo'])
|
s.scanned_tags = set(['artist', 'foo'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
@@ -278,7 +282,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_tag_scan_converts_to_str(self):
|
def test_tag_scan_converts_to_str(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['track'])
|
s.scanned_tags = set(['track'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
@@ -292,12 +296,12 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_tag_scan_non_ascii(self):
|
def test_tag_scan_non_ascii(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_TAG
|
s.scan_type = ScanType.Tag
|
||||||
s.scanned_tags = set(['title'])
|
s.scanned_tags = set(['title'])
|
||||||
o1 = no('foo')
|
o1 = no('foo')
|
||||||
o2 = no('bar')
|
o2 = no('bar')
|
||||||
o1.title = u'foobar\u00e9'
|
o1.title = 'foobar\u00e9'
|
||||||
o2.title = u'foobar\u00e9'
|
o2.title = 'foobar\u00e9'
|
||||||
try:
|
try:
|
||||||
r = s.GetDupeGroups([o1, o2])
|
r = s.GetDupeGroups([o1, o2])
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
@@ -306,7 +310,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
|
|
||||||
def test_audio_content_scan(self):
|
def test_audio_content_scan(self):
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
s.scan_type = ScanType.ContentsAudio
|
||||||
f = [no('foo'), no('bar'), no('bleh')]
|
f = [no('foo'), no('bar'), no('bleh')]
|
||||||
f[0].md5 = 'foo'
|
f[0].md5 = 'foo'
|
||||||
f[1].md5 = 'bar'
|
f[1].md5 = 'bar'
|
||||||
@@ -328,7 +332,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
raise AssertionError()
|
raise AssertionError()
|
||||||
|
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT_AUDIO
|
s.scan_type = ScanType.ContentsAudio
|
||||||
f = [MyFile('foo'), MyFile('bar')]
|
f = [MyFile('foo'), MyFile('bar')]
|
||||||
f[0].audiosize = 1
|
f[0].audiosize = 1
|
||||||
f[1].audiosize = 2
|
f[1].audiosize = 2
|
||||||
@@ -361,11 +365,11 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
f1 = no('foobar')
|
f1 = no('foobar')
|
||||||
f2 = no('foobar')
|
f2 = no('foobar')
|
||||||
f3 = no('foobar')
|
f3 = no('foobar')
|
||||||
f1.path = Path(u'foo1\u00e9')
|
f1.path = Path('foo1\u00e9')
|
||||||
f2.path = Path(u'foo2\u00e9')
|
f2.path = Path('foo2\u00e9')
|
||||||
f3.path = Path(u'foo3\u00e9')
|
f3.path = Path('foo3\u00e9')
|
||||||
s.ignore_list.Ignore(unicode(f1.path),unicode(f2.path))
|
s.ignore_list.Ignore(str(f1.path),str(f2.path))
|
||||||
s.ignore_list.Ignore(unicode(f1.path),unicode(f3.path))
|
s.ignore_list.Ignore(str(f1.path),str(f3.path))
|
||||||
r = s.GetDupeGroups([f1,f2,f3])
|
r = s.GetDupeGroups([f1,f2,f3])
|
||||||
eq_(len(r), 1)
|
eq_(len(r), 1)
|
||||||
g = r[0]
|
g = r[0]
|
||||||
@@ -378,7 +382,7 @@ class ScannerTestFakeFiles(TestCase):
|
|||||||
# A very wrong way to use any() was added at some point, causing resulting group list
|
# A very wrong way to use any() was added at some point, causing resulting group list
|
||||||
# to be empty.
|
# to be empty.
|
||||||
class FalseNamedObject(NamedObject):
|
class FalseNamedObject(NamedObject):
|
||||||
def __nonzero__(self):
|
def __bool__(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -425,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
|
# if ref has the same words as dupe, but has some just one extra word which is a digit, it
|
||||||
# becomes a dupe
|
# becomes a dupe
|
||||||
s = Scanner()
|
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')
|
o1.path = Path('deeper/path')
|
||||||
o2.path = Path('foo')
|
o2.path = Path('deeper/path')
|
||||||
[group] = s.GetDupeGroups([o1, o2])
|
o3.path = Path('deeper/path')
|
||||||
assert group.ref is o2
|
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):
|
def test_partial_group_match(self):
|
||||||
# Count the number od discarded matches (when a file doesn't match all other dupes of the
|
# Count the number od discarded matches (when a file doesn't match all other dupes of the
|
||||||
@@ -452,7 +465,7 @@ class ScannerTest(TestCase):
|
|||||||
# In this test, we have to delete one of the files between the get_matches() part and the
|
# In this test, we have to delete one of the files between the get_matches() part and the
|
||||||
# get_groups() part.
|
# get_groups() part.
|
||||||
s = Scanner()
|
s = Scanner()
|
||||||
s.scan_type = SCAN_TYPE_CONTENT
|
s.scan_type = ScanType.Contents
|
||||||
p = self.tmppath()
|
p = self.tmppath()
|
||||||
io.open(p + 'file1', 'w').write('foo')
|
io.open(p + 'file1', 'w').write('foo')
|
||||||
io.open(p + 'file2', 'w').write('foo')
|
io.open(p + 'file2', 'w').write('foo')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import logging
|
|||||||
from appscript import app, k, CommandError
|
from appscript import app, k, CommandError
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from hsutil.cocoa import as_fetch
|
from hscommon.cocoa import as_fetch
|
||||||
|
|
||||||
from core.app_cocoa import JOBID2TITLE, DupeGuru as DupeGuruBase
|
from core.app_cocoa import JOBID2TITLE, DupeGuru as DupeGuruBase
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class DupeGuruME(DupeGuruBase):
|
|||||||
try:
|
try:
|
||||||
track.delete(timeout=0)
|
track.delete(timeout=0)
|
||||||
except CommandError as e:
|
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)
|
self._start_job(JOB_REMOVE_DEAD_TRACKS, do)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ COLUMNS = [
|
|||||||
{'attr':'bitrate','display':'Bitrate'},
|
{'attr':'bitrate','display':'Bitrate'},
|
||||||
{'attr':'samplerate','display':'Sample Rate'},
|
{'attr':'samplerate','display':'Sample Rate'},
|
||||||
{'attr':'extension','display':'Kind'},
|
{'attr':'extension','display':'Kind'},
|
||||||
{'attr':'ctime','display':'Creation'},
|
|
||||||
{'attr':'mtime','display':'Modification'},
|
{'attr':'mtime','display':'Modification'},
|
||||||
{'attr':'title','display':'Title'},
|
{'attr':'title','display':'Title'},
|
||||||
{'attr':'artist','display':'Artist'},
|
{'attr':'artist','display':'Artist'},
|
||||||
@@ -32,7 +31,7 @@ COLUMNS = [
|
|||||||
{'attr':'dupe_count','display':'Dupe Count'},
|
{'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']
|
'album', 'genre', 'year', 'track', 'comment']
|
||||||
|
|
||||||
def GetDisplayInfo(dupe, group, delta):
|
def GetDisplayInfo(dupe, group, delta):
|
||||||
@@ -40,7 +39,6 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
duration = dupe.duration
|
duration = dupe.duration
|
||||||
bitrate = dupe.bitrate
|
bitrate = dupe.bitrate
|
||||||
samplerate = dupe.samplerate
|
samplerate = dupe.samplerate
|
||||||
ctime = dupe.ctime
|
|
||||||
mtime = dupe.mtime
|
mtime = dupe.mtime
|
||||||
m = group.get_match_of(dupe)
|
m = group.get_match_of(dupe)
|
||||||
if m:
|
if m:
|
||||||
@@ -52,7 +50,6 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
duration -= r.duration
|
duration -= r.duration
|
||||||
bitrate -= r.bitrate
|
bitrate -= r.bitrate
|
||||||
samplerate -= r.samplerate
|
samplerate -= r.samplerate
|
||||||
ctime -= r.ctime
|
|
||||||
mtime -= r.mtime
|
mtime -= r.mtime
|
||||||
else:
|
else:
|
||||||
percentage = group.percentage
|
percentage = group.percentage
|
||||||
@@ -65,7 +62,6 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
str(bitrate),
|
str(bitrate),
|
||||||
str(samplerate),
|
str(samplerate),
|
||||||
dupe.extension,
|
dupe.extension,
|
||||||
format_timestamp(ctime,delta and m),
|
|
||||||
format_timestamp(mtime,delta and m),
|
format_timestamp(mtime,delta and m),
|
||||||
dupe.title,
|
dupe.title,
|
||||||
dupe.artist,
|
dupe.artist,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from hsmedia import mpeg, wma, mp4, ogg, flac, aiff
|
from hsaudiotag import mpeg, wma, mp4, ogg, flac, aiff
|
||||||
from hsutil.str import get_file_ext
|
from hsutil.str import get_file_ext
|
||||||
from core import fs
|
from core import fs
|
||||||
|
|
||||||
@@ -42,12 +42,12 @@ class Mp3File(MusicFile):
|
|||||||
HANDLED_EXTS = set(['mp3'])
|
HANDLED_EXTS = set(['mp3'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
fileinfo = mpeg.Mpeg(unicode(self.path))
|
fileinfo = mpeg.Mpeg(str(self.path))
|
||||||
self._md5partial_offset = fileinfo.audio_offset
|
self._md5partial_offset = fileinfo.audio_offset
|
||||||
self._md5partial_size = fileinfo.audio_size
|
self._md5partial_size = fileinfo.audio_size
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
fileinfo = mpeg.Mpeg(unicode(self.path))
|
fileinfo = mpeg.Mpeg(str(self.path))
|
||||||
self.audiosize = fileinfo.audio_size
|
self.audiosize = fileinfo.audio_size
|
||||||
self.bitrate = fileinfo.bitrate
|
self.bitrate = fileinfo.bitrate
|
||||||
self.duration = fileinfo.duration
|
self.duration = fileinfo.duration
|
||||||
@@ -70,12 +70,12 @@ class WmaFile(MusicFile):
|
|||||||
HANDLED_EXTS = set(['wma'])
|
HANDLED_EXTS = set(['wma'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
dec = wma.WMADecoder(unicode(self.path))
|
dec = wma.WMADecoder(str(self.path))
|
||||||
self._md5partial_offset = dec.audio_offset
|
self._md5partial_offset = dec.audio_offset
|
||||||
self._md5partial_size = dec.audio_size
|
self._md5partial_size = dec.audio_size
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
dec = wma.WMADecoder(unicode(self.path))
|
dec = wma.WMADecoder(str(self.path))
|
||||||
self.audiosize = dec.audio_size
|
self.audiosize = dec.audio_size
|
||||||
self.bitrate = dec.bitrate
|
self.bitrate = dec.bitrate
|
||||||
self.duration = dec.duration
|
self.duration = dec.duration
|
||||||
@@ -92,13 +92,13 @@ class Mp4File(MusicFile):
|
|||||||
HANDLED_EXTS = set(['m4a', 'm4p'])
|
HANDLED_EXTS = set(['m4a', 'm4p'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
dec = mp4.File(unicode(self.path))
|
dec = mp4.File(str(self.path))
|
||||||
self._md5partial_offset = dec.audio_offset
|
self._md5partial_offset = dec.audio_offset
|
||||||
self._md5partial_size = dec.audio_size
|
self._md5partial_size = dec.audio_size
|
||||||
dec.close()
|
dec.close()
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
dec = mp4.File(unicode(self.path))
|
dec = mp4.File(str(self.path))
|
||||||
self.audiosize = dec.audio_size
|
self.audiosize = dec.audio_size
|
||||||
self.bitrate = dec.bitrate
|
self.bitrate = dec.bitrate
|
||||||
self.duration = dec.duration
|
self.duration = dec.duration
|
||||||
@@ -116,12 +116,12 @@ class OggFile(MusicFile):
|
|||||||
HANDLED_EXTS = set(['ogg'])
|
HANDLED_EXTS = set(['ogg'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
dec = ogg.Vorbis(unicode(self.path))
|
dec = ogg.Vorbis(str(self.path))
|
||||||
self._md5partial_offset = dec.audio_offset
|
self._md5partial_offset = dec.audio_offset
|
||||||
self._md5partial_size = dec.audio_size
|
self._md5partial_size = dec.audio_size
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
dec = ogg.Vorbis(unicode(self.path))
|
dec = ogg.Vorbis(str(self.path))
|
||||||
self.audiosize = dec.audio_size
|
self.audiosize = dec.audio_size
|
||||||
self.bitrate = dec.bitrate
|
self.bitrate = dec.bitrate
|
||||||
self.duration = dec.duration
|
self.duration = dec.duration
|
||||||
@@ -138,12 +138,12 @@ class FlacFile(MusicFile):
|
|||||||
HANDLED_EXTS = set(['flac'])
|
HANDLED_EXTS = set(['flac'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
dec = flac.FLAC(unicode(self.path))
|
dec = flac.FLAC(str(self.path))
|
||||||
self._md5partial_offset = dec.audio_offset
|
self._md5partial_offset = dec.audio_offset
|
||||||
self._md5partial_size = dec.audio_size
|
self._md5partial_size = dec.audio_size
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
dec = flac.FLAC(unicode(self.path))
|
dec = flac.FLAC(str(self.path))
|
||||||
self.audiosize = dec.audio_size
|
self.audiosize = dec.audio_size
|
||||||
self.bitrate = dec.bitrate
|
self.bitrate = dec.bitrate
|
||||||
self.duration = dec.duration
|
self.duration = dec.duration
|
||||||
@@ -160,12 +160,12 @@ class AiffFile(MusicFile):
|
|||||||
HANDLED_EXTS = set(['aif', 'aiff', 'aifc'])
|
HANDLED_EXTS = set(['aif', 'aiff', 'aifc'])
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field == 'md5partial':
|
if field == 'md5partial':
|
||||||
dec = aiff.File(unicode(self.path))
|
dec = aiff.File(str(self.path))
|
||||||
self._md5partial_offset = dec.audio_offset
|
self._md5partial_offset = dec.audio_offset
|
||||||
self._md5partial_size = dec.audio_size
|
self._md5partial_size = dec.audio_size
|
||||||
MusicFile._read_info(self, field)
|
MusicFile._read_info(self, field)
|
||||||
if field in TAG_FIELDS:
|
if field in TAG_FIELDS:
|
||||||
dec = aiff.File(unicode(self.path))
|
dec = aiff.File(str(self.path))
|
||||||
self.audiosize = dec.audio_size
|
self.audiosize = dec.audio_size
|
||||||
self.bitrate = dec.bitrate
|
self.bitrate = dec.bitrate
|
||||||
self.duration = dec.duration
|
self.duration = dec.duration
|
||||||
|
|||||||
28
core_me/tests/conftest.py
Normal file
28
core_me/tests/conftest.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-07-11
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" 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/bsd_license
|
||||||
|
|
||||||
|
# This unit is required to make tests work with py.test. When running
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def get_testunit(item):
|
||||||
|
if hasattr(item, 'obj'):
|
||||||
|
testunit = py.builtin._getimself(item.obj)
|
||||||
|
if hasattr(testunit, 'global_setup'):
|
||||||
|
return testunit
|
||||||
|
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_setup()
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_teardown()
|
||||||
@@ -7,17 +7,17 @@
|
|||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
import os.path as op
|
import os.path as op
|
||||||
import logging
|
|
||||||
import plistlib
|
import plistlib
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from appscript import app, k, CommandError
|
from appscript import app, k, CommandError
|
||||||
|
|
||||||
from hsutil import io
|
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 hsutil.path import Path
|
||||||
from hsutil.cocoa import as_fetch
|
from hscommon.cocoa import as_fetch
|
||||||
from hsutil.cocoa.objcmin import NSUserDefaults, NSURL
|
from hscommon.cocoa.objcmin import NSUserDefaults, NSURL
|
||||||
|
|
||||||
from core import fs
|
from core import fs
|
||||||
from core import app_cocoa, directories
|
from core import app_cocoa, directories
|
||||||
@@ -38,15 +38,15 @@ class Photo(fs.File):
|
|||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
fs.File._read_info(self, field)
|
fs.File._read_info(self, field)
|
||||||
if field == 'dimensions':
|
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):
|
def get_blocks(self, block_count_per_side):
|
||||||
try:
|
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:
|
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:
|
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
|
return blocks
|
||||||
|
|
||||||
|
|
||||||
@@ -68,16 +68,16 @@ def get_iphoto_database_path():
|
|||||||
def get_iphoto_pictures(plistpath):
|
def get_iphoto_pictures(plistpath):
|
||||||
if not io.exists(plistpath):
|
if not io.exists(plistpath):
|
||||||
return []
|
return []
|
||||||
s = io.open(plistpath).read()
|
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
|
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
||||||
s = s.replace('\x10', '')
|
s = remove_invalid_xml(s, replace_with='')
|
||||||
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
|
# 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
|
# any & char that is not a &-based entity (&, ", etc.). based on TextMate's XML
|
||||||
# bundle's regexp
|
# bundle's regexp
|
||||||
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
||||||
if count:
|
if count:
|
||||||
logging.warning("%d invalid XML entities replacement made", count)
|
logging.warning("%d invalid XML entities replacement made", count)
|
||||||
plist = plistlib.readPlistFromString(s)
|
plist = plistlib.readPlistFromBytes(s.encode('utf-8'))
|
||||||
result = []
|
result = []
|
||||||
for photo_data in plist['Master Image List'].values():
|
for photo_data in plist['Master Image List'].values():
|
||||||
if photo_data['MediaType'] != 'Image':
|
if photo_data['MediaType'] != 'Image':
|
||||||
@@ -122,6 +122,15 @@ class Directories(directories.Directories):
|
|||||||
else:
|
else:
|
||||||
directories.Directories.add_path(self, path)
|
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):
|
class DupeGuruPE(app_cocoa.DupeGuru):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -130,10 +139,10 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
|||||||
self.directories = Directories()
|
self.directories = Directories()
|
||||||
self.scanner.cache_path = op.join(self.appdata, 'cached_pictures.db')
|
self.scanner.cache_path = op.join(self.appdata, 'cached_pictures.db')
|
||||||
|
|
||||||
def _do_delete(self, j):
|
def _do_delete(self, j, replace_with_hardlinks):
|
||||||
def op(dupe):
|
def op(dupe):
|
||||||
j.add_progress()
|
j.add_progress()
|
||||||
return self._do_delete_dupe(dupe)
|
return self._do_delete_dupe(dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
||||||
self.path2iphoto = {}
|
self.path2iphoto = {}
|
||||||
@@ -146,34 +155,29 @@ class DupeGuruPE(app_cocoa.DupeGuru):
|
|||||||
photos = as_fetch(a.photo_library_album().photos, k.item)
|
photos = as_fetch(a.photo_library_album().photos, k.item)
|
||||||
for photo in j.iter_with_progress(photos):
|
for photo in j.iter_with_progress(photos):
|
||||||
try:
|
try:
|
||||||
self.path2iphoto[unicode(photo.image_path(timeout=0))] = photo
|
self.path2iphoto[str(photo.image_path(timeout=0))] = photo
|
||||||
except CommandError:
|
except CommandError:
|
||||||
pass
|
pass
|
||||||
except (CommandError, RuntimeError):
|
except (CommandError, RuntimeError):
|
||||||
pass
|
pass
|
||||||
j.start_job(self.results.mark_count, "Sending dupes to the Trash")
|
j.start_job(self.results.mark_count, "Sending dupes to the Trash")
|
||||||
self.last_op_error_count = self.results.perform_on_marked(op, True)
|
self.results.perform_on_marked(op, True)
|
||||||
del self.path2iphoto
|
del self.path2iphoto
|
||||||
|
|
||||||
def _do_delete_dupe(self, dupe):
|
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
|
||||||
if isinstance(dupe, IPhoto):
|
if isinstance(dupe, IPhoto):
|
||||||
if unicode(dupe.path) in self.path2iphoto:
|
if str(dupe.path) in self.path2iphoto:
|
||||||
photo = self.path2iphoto[unicode(dupe.path)]
|
photo = self.path2iphoto[str(dupe.path)]
|
||||||
try:
|
try:
|
||||||
a = app('iPhoto')
|
a = app('iPhoto')
|
||||||
a.remove(photo, timeout=0)
|
a.remove(photo, timeout=0)
|
||||||
return True
|
except (CommandError, RuntimeError) as e:
|
||||||
except (CommandError, RuntimeError):
|
raise EnvironmentError(str(e))
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
logging.warning(u"Could not find photo %s in iPhoto Library", unicode(dupe.path))
|
msg = "Could not find photo %s in iPhoto Library" % str(dupe.path)
|
||||||
return False
|
raise EnvironmentError(msg)
|
||||||
else:
|
else:
|
||||||
return app_cocoa.DupeGuru._do_delete_dupe(self, dupe)
|
app_cocoa.DupeGuru._do_delete_dupe(self, dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
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):
|
def _get_file(self, str_path):
|
||||||
p = Path(str_path)
|
p = Path(str_path)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from _block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2
|
from ._block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2
|
||||||
|
|
||||||
# Converted to C
|
# Converted to C
|
||||||
# def getblock(image):
|
# def getblock(image):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
|
|
||||||
from _cache import string_to_colors
|
from ._cache import string_to_colors
|
||||||
|
|
||||||
def colors_to_string(colors):
|
def colors_to_string(colors):
|
||||||
"""Transform the 3 sized tuples 'colors' into a hex string.
|
"""Transform the 3 sized tuples 'colors' into a hex string.
|
||||||
@@ -82,7 +82,7 @@ class Cache(object):
|
|||||||
self.con.execute(sql, [value, key])
|
self.con.execute(sql, [value, key])
|
||||||
except sqlite.OperationalError:
|
except sqlite.OperationalError:
|
||||||
logging.warning('Picture cache could not set %r for key %r', value, key)
|
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))
|
logging.warning('DatabaseError while setting %r for key %r: %s', value, key, str(e))
|
||||||
|
|
||||||
def _create_con(self, second_try=False):
|
def _create_con(self, second_try=False):
|
||||||
@@ -97,7 +97,7 @@ class Cache(object):
|
|||||||
self.con.execute("select * from pictures where 1=2")
|
self.con.execute("select * from pictures where 1=2")
|
||||||
except sqlite.OperationalError: # new db
|
except sqlite.OperationalError: # new db
|
||||||
create_tables()
|
create_tables()
|
||||||
except sqlite.DatabaseError, e: # corrupted db
|
except sqlite.DatabaseError as e: # corrupted db
|
||||||
if second_try:
|
if second_try:
|
||||||
raise # Something really strange is happening
|
raise # Something really strange is happening
|
||||||
logging.warning('Could not create picture cache because of an error: %s', str(e))
|
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':'size','display':'Size (KB)'},
|
||||||
{'attr':'extension','display':'Kind'},
|
{'attr':'extension','display':'Kind'},
|
||||||
{'attr':'dimensions','display':'Dimensions'},
|
{'attr':'dimensions','display':'Dimensions'},
|
||||||
{'attr':'ctime','display':'Creation'},
|
|
||||||
{'attr':'mtime','display':'Modification'},
|
{'attr':'mtime','display':'Modification'},
|
||||||
{'attr':'percentage','display':'Match %'},
|
{'attr':'percentage','display':'Match %'},
|
||||||
{'attr':'dupe_count','display':'Dupe Count'},
|
{'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):
|
def GetDisplayInfo(dupe,group,delta=False):
|
||||||
if (dupe is None) or (group is None):
|
if (dupe is None) or (group is None):
|
||||||
return ['---'] * len(COLUMNS)
|
return ['---'] * len(COLUMNS)
|
||||||
size = dupe.size
|
size = dupe.size
|
||||||
ctime = dupe.ctime
|
|
||||||
mtime = dupe.mtime
|
mtime = dupe.mtime
|
||||||
m = group.get_match_of(dupe)
|
m = group.get_match_of(dupe)
|
||||||
if m:
|
if m:
|
||||||
@@ -39,7 +37,6 @@ def GetDisplayInfo(dupe,group,delta=False):
|
|||||||
if delta:
|
if delta:
|
||||||
r = group.ref
|
r = group.ref
|
||||||
size -= r.size
|
size -= r.size
|
||||||
ctime -= r.ctime
|
|
||||||
mtime -= r.mtime
|
mtime -= r.mtime
|
||||||
else:
|
else:
|
||||||
percentage = group.percentage
|
percentage = group.percentage
|
||||||
@@ -51,7 +48,6 @@ def GetDisplayInfo(dupe,group,delta=False):
|
|||||||
format_size(size, 0, 1, False),
|
format_size(size, 0, 1, False),
|
||||||
dupe.extension,
|
dupe.extension,
|
||||||
format_dimensions(dupe.dimensions),
|
format_dimensions(dupe.dimensions),
|
||||||
format_timestamp(ctime, delta and m),
|
|
||||||
format_timestamp(mtime, delta and m),
|
format_timestamp(mtime, delta and m),
|
||||||
format_perc(percentage),
|
format_perc(percentage),
|
||||||
format_dupe_count(dupe_count)
|
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')
|
|
||||||
@@ -8,11 +8,9 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from Queue import Empty
|
from collections import defaultdict, deque
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from hsutil import job
|
from hscommon import job
|
||||||
from hsutil.misc import dedupe
|
|
||||||
|
|
||||||
from core.engine import Match
|
from core.engine import Match
|
||||||
from .block import avgdiff, DifferentBlockCountError, NoBlocksError
|
from .block import avgdiff, DifferentBlockCountError, NoBlocksError
|
||||||
@@ -36,16 +34,16 @@ def prepare_pictures(pictures, cache_path, j=job.nulljob):
|
|||||||
try:
|
try:
|
||||||
for picture in j.iter_with_progress(pictures, 'Analyzed %d/%d pictures'):
|
for picture in j.iter_with_progress(pictures, 'Analyzed %d/%d pictures'):
|
||||||
picture.dimensions
|
picture.dimensions
|
||||||
picture.unicode_path = unicode(picture.path)
|
picture.unicode_path = str(picture.path)
|
||||||
try:
|
try:
|
||||||
if picture.unicode_path not in cache:
|
if picture.unicode_path not in cache:
|
||||||
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
|
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
|
||||||
cache[picture.unicode_path] = blocks
|
cache[picture.unicode_path] = blocks
|
||||||
prepared.append(picture)
|
prepared.append(picture)
|
||||||
except IOError as e:
|
except (IOError, ValueError) as e:
|
||||||
logging.warning(unicode(e))
|
logging.warning(str(e))
|
||||||
except MemoryError:
|
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
|
if picture.size < 10 * 1024 * 1024: # We're really running out of memory
|
||||||
raise
|
raise
|
||||||
except MemoryError:
|
except MemoryError:
|
||||||
@@ -76,13 +74,6 @@ def async_compare(ref_id, other_ids, dbname, threshold):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def getmatches(pictures, cache_path, threshold=75, match_scaled=False, j=job.nulljob):
|
def getmatches(pictures, cache_path, threshold=75, match_scaled=False, j=job.nulljob):
|
||||||
def empty_out_queue(queue, into):
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
into.append(queue.get(block=False))
|
|
||||||
except Empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
j = j.start_subjob([3, 7])
|
j = j.start_subjob([3, 7])
|
||||||
pictures = prepare_pictures(pictures, cache_path, j)
|
pictures = prepare_pictures(pictures, cache_path, j)
|
||||||
j = j.start_subjob([9, 1], 'Preparing for matching')
|
j = j.start_subjob([9, 1], 'Preparing for matching')
|
||||||
@@ -100,7 +91,7 @@ def getmatches(pictures, cache_path, threshold=75, match_scaled=False, j=job.nul
|
|||||||
cache.close()
|
cache.close()
|
||||||
pictures = [p for p in pictures if hasattr(p, 'cache_id')]
|
pictures = [p for p in pictures if hasattr(p, 'cache_id')]
|
||||||
pool = multiprocessing.Pool()
|
pool = multiprocessing.Pool()
|
||||||
async_results = []
|
async_results = deque()
|
||||||
matches = []
|
matches = []
|
||||||
pictures_copy = set(pictures)
|
pictures_copy = set(pictures)
|
||||||
for ref in j.iter_with_progress(pictures, 'Matched %d/%d pictures'):
|
for ref in j.iter_with_progress(pictures, 'Matched %d/%d pictures'):
|
||||||
@@ -111,10 +102,17 @@ def getmatches(pictures, cache_path, threshold=75, match_scaled=False, j=job.nul
|
|||||||
others = [pic for pic in others if not pic.is_ref]
|
others = [pic for pic in others if not pic.is_ref]
|
||||||
if others:
|
if others:
|
||||||
cache_ids = [f.cache_id for f in others]
|
cache_ids = [f.cache_id for f in others]
|
||||||
args = (ref.cache_id, cache_ids, cache_path, threshold)
|
# We limit the number of cache_ids we send for multi-processing because otherwise, we
|
||||||
async_results.append(pool.apply_async(async_compare, args))
|
# might get an error saying "String or BLOB exceeded size limit"
|
||||||
if len(async_results) > RESULTS_QUEUE_LIMIT:
|
ARG_LIMIT = 1000
|
||||||
result = async_results.pop(0)
|
while cache_ids:
|
||||||
|
args = (ref.cache_id, cache_ids[:ARG_LIMIT], cache_path, threshold)
|
||||||
|
async_results.append(pool.apply_async(async_compare, args))
|
||||||
|
cache_ids = cache_ids[ARG_LIMIT:]
|
||||||
|
# We use a while here because it's possible that more than one result has been added if
|
||||||
|
# ARG_LIMIT has been reached.
|
||||||
|
while len(async_results) > RESULTS_QUEUE_LIMIT:
|
||||||
|
result = async_results.popleft()
|
||||||
matches.extend(result.get())
|
matches.extend(result.get())
|
||||||
for result in async_results: # process the rest of the results
|
for result in async_results: # process the rest of the results
|
||||||
matches.extend(result.get())
|
matches.extend(result.get())
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ static PyObject* getblock(PyObject *image)
|
|||||||
pg = PySequence_ITEM(ppixel, 1);
|
pg = PySequence_ITEM(ppixel, 1);
|
||||||
pb = PySequence_ITEM(ppixel, 2);
|
pb = PySequence_ITEM(ppixel, 2);
|
||||||
Py_DECREF(ppixel);
|
Py_DECREF(ppixel);
|
||||||
r = PyInt_AsSsize_t(pr);
|
r = PyLong_AsLong(pr);
|
||||||
g = PyInt_AsSsize_t(pg);
|
g = PyLong_AsLong(pg);
|
||||||
b = PyInt_AsSsize_t(pb);
|
b = PyLong_AsLong(pb);
|
||||||
Py_DECREF(pr);
|
Py_DECREF(pr);
|
||||||
Py_DECREF(pg);
|
Py_DECREF(pg);
|
||||||
Py_DECREF(pb);
|
Py_DECREF(pb);
|
||||||
@@ -67,14 +67,14 @@ static PyObject* getblock(PyObject *image)
|
|||||||
*/
|
*/
|
||||||
static int diff(PyObject *first, PyObject *second)
|
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;
|
PyObject *pr, *pg, *pb;
|
||||||
pr = PySequence_ITEM(first, 0);
|
pr = PySequence_ITEM(first, 0);
|
||||||
pg = PySequence_ITEM(first, 1);
|
pg = PySequence_ITEM(first, 1);
|
||||||
pb = PySequence_ITEM(first, 2);
|
pb = PySequence_ITEM(first, 2);
|
||||||
r1 = PyInt_AsSsize_t(pr);
|
r1 = PyLong_AsLong(pr);
|
||||||
g1 = PyInt_AsSsize_t(pg);
|
g1 = PyLong_AsLong(pg);
|
||||||
b1 = PyInt_AsSsize_t(pb);
|
b1 = PyLong_AsLong(pb);
|
||||||
Py_DECREF(pr);
|
Py_DECREF(pr);
|
||||||
Py_DECREF(pg);
|
Py_DECREF(pg);
|
||||||
Py_DECREF(pb);
|
Py_DECREF(pb);
|
||||||
@@ -82,9 +82,9 @@ static int diff(PyObject *first, PyObject *second)
|
|||||||
pr = PySequence_ITEM(second, 0);
|
pr = PySequence_ITEM(second, 0);
|
||||||
pg = PySequence_ITEM(second, 1);
|
pg = PySequence_ITEM(second, 1);
|
||||||
pb = PySequence_ITEM(second, 2);
|
pb = PySequence_ITEM(second, 2);
|
||||||
r2 = PyInt_AsSsize_t(pr);
|
r2 = PyLong_AsLong(pr);
|
||||||
g2 = PyInt_AsSsize_t(pg);
|
g2 = PyLong_AsLong(pg);
|
||||||
b2 = PyInt_AsSsize_t(pb);
|
b2 = PyLong_AsLong(pb);
|
||||||
Py_DECREF(pr);
|
Py_DECREF(pr);
|
||||||
Py_DECREF(pg);
|
Py_DECREF(pg);
|
||||||
Py_DECREF(pb);
|
Py_DECREF(pb);
|
||||||
@@ -115,8 +115,8 @@ static PyObject* block_getblocks2(PyObject *self, PyObject *args)
|
|||||||
pimage_size = PyObject_GetAttrString(image, "size");
|
pimage_size = PyObject_GetAttrString(image, "size");
|
||||||
pwidth = PySequence_ITEM(pimage_size, 0);
|
pwidth = PySequence_ITEM(pimage_size, 0);
|
||||||
pheight = PySequence_ITEM(pimage_size, 1);
|
pheight = PySequence_ITEM(pimage_size, 1);
|
||||||
width = PyInt_AsSsize_t(pwidth);
|
width = PyLong_AsLong(pwidth);
|
||||||
height = PyInt_AsSsize_t(pheight);
|
height = PyLong_AsLong(pheight);
|
||||||
Py_DECREF(pimage_size);
|
Py_DECREF(pimage_size);
|
||||||
Py_DECREF(pwidth);
|
Py_DECREF(pwidth);
|
||||||
Py_DECREF(pheight);
|
Py_DECREF(pheight);
|
||||||
@@ -147,8 +147,8 @@ static PyObject* block_getblocks2(PyObject *self, PyObject *args)
|
|||||||
left = min(iw*block_width, width-block_width);
|
left = min(iw*block_width, width-block_width);
|
||||||
right = left + block_width;
|
right = left + block_width;
|
||||||
pbox = inttuple(4, left, top, right, bottom);
|
pbox = inttuple(4, left, top, right, bottom);
|
||||||
pmethodname = PyString_FromString("crop");
|
pmethodname = PyUnicode_FromString("crop");
|
||||||
pcrop = PyObject_CallMethodObjArgs(image, pmethodname, pbox);
|
pcrop = PyObject_CallMethodObjArgs(image, pmethodname, pbox, NULL);
|
||||||
Py_DECREF(pmethodname);
|
Py_DECREF(pmethodname);
|
||||||
Py_DECREF(pbox);
|
Py_DECREF(pbox);
|
||||||
if (pcrop == NULL) {
|
if (pcrop == NULL) {
|
||||||
@@ -207,7 +207,7 @@ static PyObject* block_avgdiff(PyObject *self, PyObject *args)
|
|||||||
Py_DECREF(item1);
|
Py_DECREF(item1);
|
||||||
Py_DECREF(item2);
|
Py_DECREF(item2);
|
||||||
if ((sum > limit*iteration_count) && (iteration_count >= min_iterations)) {
|
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) {
|
if (!result && sum) {
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
return PyInt_FromSsize_t(result);
|
return PyLong_FromLong(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef BlockMethods[] = {
|
static PyMethodDef BlockMethods[] = {
|
||||||
@@ -224,16 +224,30 @@ static PyMethodDef BlockMethods[] = {
|
|||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
static struct PyModuleDef BlockDef = {
|
||||||
init_block(void)
|
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) {
|
if (m == NULL) {
|
||||||
return;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
NoBlocksError = PyErr_NewException("_block.NoBlocksError", NULL, NULL);
|
NoBlocksError = PyErr_NewException("_block.NoBlocksError", NULL, NULL);
|
||||||
PyModule_AddObject(m, "NoBlocksError", NoBlocksError);
|
PyModule_AddObject(m, "NoBlocksError", NoBlocksError);
|
||||||
DifferentBlockCountError = PyErr_NewException("_block.DifferentBlockCountError", NULL, NULL);
|
DifferentBlockCountError = PyErr_NewException("_block.DifferentBlockCountError", NULL, NULL);
|
||||||
PyModule_AddObject(m, "DifferentBlockCountError", DifferentBlockCountError);
|
PyModule_AddObject(m, "DifferentBlockCountError", DifferentBlockCountError);
|
||||||
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,8 @@ pystring2cfstring(PyObject *pystring)
|
|||||||
Py_INCREF(encoded);
|
Py_INCREF(encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
s = (UInt8*)PyString_AS_STRING(encoded);
|
s = (UInt8*)PyBytes_AS_STRING(encoded);
|
||||||
size = PyString_GET_SIZE(encoded);
|
size = PyUnicode_GET_SIZE(encoded);
|
||||||
result = CFStringCreateWithBytes(NULL, s, size, kCFStringEncodingUTF8, FALSE);
|
result = CFStringCreateWithBytes(NULL, s, size, kCFStringEncodingUTF8, FALSE);
|
||||||
Py_DECREF(encoded);
|
Py_DECREF(encoded);
|
||||||
return result;
|
return result;
|
||||||
@@ -43,7 +43,7 @@ static PyObject* block_osx_get_image_size(PyObject *self, PyObject *args)
|
|||||||
CFURLRef image_url;
|
CFURLRef image_url;
|
||||||
CGImageSourceRef source;
|
CGImageSourceRef source;
|
||||||
CGImageRef image;
|
CGImageRef image;
|
||||||
size_t width, height;
|
long width, height;
|
||||||
PyObject *pwidth, *pheight;
|
PyObject *pwidth, *pheight;
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
|
|
||||||
@@ -72,11 +72,11 @@ static PyObject* block_osx_get_image_size(PyObject *self, PyObject *args)
|
|||||||
CFRelease(source);
|
CFRelease(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
pwidth = PyInt_FromSsize_t(width);
|
pwidth = PyLong_FromLong(width);
|
||||||
if (pwidth == NULL) {
|
if (pwidth == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
pheight = PyInt_FromSsize_t(height);
|
pheight = PyLong_FromLong(height);
|
||||||
if (pheight == NULL) {
|
if (pheight == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,11 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PySequence_Length(path) == 0) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "empty path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
image_path = pystring2cfstring(path);
|
image_path = pystring2cfstring(path);
|
||||||
if (image_path == NULL) {
|
if (image_path == NULL) {
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
@@ -223,8 +228,24 @@ static PyMethodDef BlockOsxMethods[] = {
|
|||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
static struct PyModuleDef BlockOsxDef = {
|
||||||
init_block_osx(void)
|
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 */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
static struct PyModuleDef CacheDef = {
|
||||||
init_cache(void)
|
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);
|
result = PyTuple_New(n);
|
||||||
|
|
||||||
for (i=0; i<n; i++) {
|
for (i=0; i<n; i++) {
|
||||||
pnumber = PyInt_FromLong(va_arg(numbers, int));
|
pnumber = PyLong_FromLong(va_arg(numbers, long));
|
||||||
if (pnumber == NULL) {
|
if (pnumber == NULL) {
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
return NULL;
|
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):
|
def test_return_at_least_1_at_the_slightest_difference(self):
|
||||||
ref = (0,0,0)
|
ref = (0,0,0)
|
||||||
b1 = (1,0,0)
|
b1 = (1,0,0)
|
||||||
blocks1 = [ref for i in xrange(250)]
|
blocks1 = [ref for i in range(250)]
|
||||||
blocks2 = [ref for i in xrange(250)]
|
blocks2 = [ref for i in range(250)]
|
||||||
blocks2[0] = b1
|
blocks2[0] = b1
|
||||||
self.assertEqual(1,my_avgdiff(blocks1,blocks2))
|
self.assertEqual(1,my_avgdiff(blocks1,blocks2))
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from StringIO import StringIO
|
|
||||||
import os.path as op
|
import os.path as op
|
||||||
import os
|
import os
|
||||||
import threading
|
|
||||||
|
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
|
||||||
|
|||||||
28
core_pe/tests/conftest.py
Normal file
28
core_pe/tests/conftest.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-07-11
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" 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/bsd_license
|
||||||
|
|
||||||
|
# This unit is required to make tests work with py.test. When running
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def get_testunit(item):
|
||||||
|
if hasattr(item, 'obj'):
|
||||||
|
testunit = py.builtin._getimself(item.obj)
|
||||||
|
if hasattr(testunit, 'global_setup'):
|
||||||
|
return testunit
|
||||||
|
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_setup()
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_teardown()
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
# http://www.hardcoded.net/licenses/hs_license
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from hsutil import io
|
from hsutil import io
|
||||||
from hsutil.path import Path
|
from hsutil.path import Path
|
||||||
from hsutil.cocoa.objcmin import NSWorkspace
|
from hscommon.cocoa.objcmin import NSWorkspace
|
||||||
|
|
||||||
from core import fs
|
from core import fs
|
||||||
from core.app_cocoa import DupeGuru as DupeGuruBase
|
from core.app_cocoa import DupeGuru as DupeGuruBase
|
||||||
@@ -24,17 +24,17 @@ def is_bundle(str_path):
|
|||||||
sw = NSWorkspace.sharedWorkspace()
|
sw = NSWorkspace.sharedWorkspace()
|
||||||
uti, error = sw.typeOfFile_error_(str_path, None)
|
uti, error = sw.typeOfFile_error_(str_path, None)
|
||||||
if error is not 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')
|
return sw.type_conformsToType_(uti, 'com.apple.bundle') or sw.type_conformsToType_(uti, 'com.apple.package')
|
||||||
|
|
||||||
class Bundle(BundleBase):
|
class Bundle(BundleBase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_handle(cls, path):
|
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):
|
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')]
|
HOME_PATH_TO_EXCLUDE = [Path('Library')]
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File])
|
DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File])
|
||||||
|
|||||||
@@ -15,18 +15,16 @@ COLUMNS = [
|
|||||||
{'attr':'path','display':'Directory'},
|
{'attr':'path','display':'Directory'},
|
||||||
{'attr':'size','display':'Size (KB)'},
|
{'attr':'size','display':'Size (KB)'},
|
||||||
{'attr':'extension','display':'Kind'},
|
{'attr':'extension','display':'Kind'},
|
||||||
{'attr':'ctime','display':'Creation'},
|
|
||||||
{'attr':'mtime','display':'Modification'},
|
{'attr':'mtime','display':'Modification'},
|
||||||
{'attr':'percentage','display':'Match %'},
|
{'attr':'percentage','display':'Match %'},
|
||||||
{'attr':'words','display':'Words Used'},
|
{'attr':'words','display':'Words Used'},
|
||||||
{'attr':'dupe_count','display':'Dupe Count'},
|
{'attr':'dupe_count','display':'Dupe Count'},
|
||||||
]
|
]
|
||||||
|
|
||||||
METADATA_TO_READ = ['size', 'ctime', 'mtime']
|
METADATA_TO_READ = ['size', 'mtime']
|
||||||
|
|
||||||
def GetDisplayInfo(dupe, group, delta):
|
def GetDisplayInfo(dupe, group, delta):
|
||||||
size = dupe.size
|
size = dupe.size
|
||||||
ctime = dupe.ctime
|
|
||||||
mtime = dupe.mtime
|
mtime = dupe.mtime
|
||||||
m = group.get_match_of(dupe)
|
m = group.get_match_of(dupe)
|
||||||
if m:
|
if m:
|
||||||
@@ -35,7 +33,6 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
if delta:
|
if delta:
|
||||||
r = group.ref
|
r = group.ref
|
||||||
size -= r.size
|
size -= r.size
|
||||||
ctime -= r.ctime
|
|
||||||
mtime -= r.mtime
|
mtime -= r.mtime
|
||||||
else:
|
else:
|
||||||
percentage = group.percentage
|
percentage = group.percentage
|
||||||
@@ -45,7 +42,6 @@ def GetDisplayInfo(dupe, group, delta):
|
|||||||
format_path(dupe.path),
|
format_path(dupe.path),
|
||||||
format_size(size, 0, 1, False),
|
format_size(size, 0, 1, False),
|
||||||
dupe.extension,
|
dupe.extension,
|
||||||
format_timestamp(ctime, delta and m),
|
|
||||||
format_timestamp(mtime, delta and m),
|
format_timestamp(mtime, delta and m),
|
||||||
format_perc(percentage),
|
format_perc(percentage),
|
||||||
format_words(dupe.words) if hasattr(dupe, 'words') else '',
|
format_words(dupe.words) if hasattr(dupe, 'words') else '',
|
||||||
|
|||||||
@@ -20,12 +20,11 @@ class Bundle(fs.File):
|
|||||||
to see them as files.
|
to see them as files.
|
||||||
"""
|
"""
|
||||||
def _read_info(self, field):
|
def _read_info(self, field):
|
||||||
if field in ('size', 'ctime', 'mtime'):
|
if field in ('size', 'mtime'):
|
||||||
files = fs.get_all_files(self.path)
|
files = fs.get_all_files(self.path)
|
||||||
size = sum((file.size for file in files), 0)
|
size = sum((file.size for file in files), 0)
|
||||||
self.size = size
|
self.size = size
|
||||||
stats = io.stat(self.path)
|
stats = io.stat(self.path)
|
||||||
self.ctime = nonone(stats.st_ctime, 0)
|
|
||||||
self.mtime = nonone(stats.st_mtime, 0)
|
self.mtime = nonone(stats.st_mtime, 0)
|
||||||
elif field in ('md5', 'md5partial'):
|
elif field in ('md5', 'md5partial'):
|
||||||
# What's sensitive here is that we must make sure that subfiles'
|
# 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 = fs.get_all_files(self.path)
|
||||||
files.sort(key=lambda f:f.path)
|
files.sort(key=lambda f:f.path)
|
||||||
md5s = [getattr(f, field) for f in files]
|
md5s = [getattr(f, field) for f in files]
|
||||||
return ''.join(md5s)
|
return b''.join(md5s)
|
||||||
|
|
||||||
md5 = hashlib.md5(get_dir_md5_concat())
|
md5 = hashlib.md5(get_dir_md5_concat())
|
||||||
digest = md5.digest()
|
digest = md5.digest()
|
||||||
|
|||||||
28
core_se/tests/conftest.py
Normal file
28
core_se/tests/conftest.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2010-07-11
|
||||||
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" 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/bsd_license
|
||||||
|
|
||||||
|
# This unit is required to make tests work with py.test. When running
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def get_testunit(item):
|
||||||
|
if hasattr(item, 'obj'):
|
||||||
|
testunit = py.builtin._getimself(item.obj)
|
||||||
|
if hasattr(testunit, 'global_setup'):
|
||||||
|
return testunit
|
||||||
|
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_setup()
|
||||||
|
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
testunit = get_testunit(item)
|
||||||
|
if testunit is not None:
|
||||||
|
testunit.global_teardown()
|
||||||
@@ -9,9 +9,8 @@
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from hsutil.testcase import TestCase
|
from hsutil.testcase import TestCase
|
||||||
|
from hsutil.testutil import eq_
|
||||||
from core.fs import File
|
from core.fs import File
|
||||||
from core.tests.directories_test import create_fake_fs
|
from core.tests.directories_test import create_fake_fs
|
||||||
|
|
||||||
@@ -39,9 +38,8 @@ class TCBundle(TestCase):
|
|||||||
eq_(b.md5, md5.digest())
|
eq_(b.md5, md5.digest())
|
||||||
|
|
||||||
def test_has_file_attrs(self):
|
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())
|
b = fs.Bundle(self.tmppath())
|
||||||
assert b.mtime > 0
|
assert b.mtime > 0
|
||||||
assert b.ctime > 0
|
|
||||||
eq_(b.extension, '')
|
eq_(b.extension, '')
|
||||||
|
|
||||||
1
debian_me/compat
Normal file
1
debian_me/compat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7
|
||||||
12
debian_me/control
Normal file
12
debian_me/control
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Source: dupeguru-me
|
||||||
|
Section: devel
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Virgil Dupras <hsoft@hardcoded.net>
|
||||||
|
Build-Depends: debhelper (>= 7)
|
||||||
|
Standards-Version: 3.8.1
|
||||||
|
Homepage: http://www.hardcoded.net
|
||||||
|
|
||||||
|
Package: dupeguru-me
|
||||||
|
Architecture: any
|
||||||
|
Depends: python3 (>= 3.1)
|
||||||
|
Description: dupeGuru Music Edition
|
||||||
3
debian_me/dirs
Normal file
3
debian_me/dirs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
usr/local/bin
|
||||||
|
usr/local/share
|
||||||
|
usr/share/applications
|
||||||
10
debian_me/dupeguru_me.desktop
Normal file
10
debian_me/dupeguru_me.desktop
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
[Desktop Entry]
|
||||||
|
Encoding=UTF-8
|
||||||
|
Name=dupeGuru Music Edition
|
||||||
|
Comment=Find duplicate songs in your collection.
|
||||||
|
Exec=dupeguru_me
|
||||||
|
Icon=/usr/local/share/dupeguru_me/dgme_logo_128.png
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility
|
||||||
86
debian_me/rules
Executable file
86
debian_me/rules
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
# Sample debian/rules that uses debhelper.
|
||||||
|
# This file was originally written by Joey Hess and Craig Small.
|
||||||
|
# As a special exception, when this file is copied by dh-make into a
|
||||||
|
# dh-make output file, you may use that output file without restriction.
|
||||||
|
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
configure: configure-stamp
|
||||||
|
configure-stamp:
|
||||||
|
dh_testdir
|
||||||
|
# Add here commands to configure the package.
|
||||||
|
|
||||||
|
touch configure-stamp
|
||||||
|
|
||||||
|
|
||||||
|
build: build-stamp
|
||||||
|
|
||||||
|
build-stamp: configure-stamp
|
||||||
|
dh_testdir
|
||||||
|
|
||||||
|
# Add here commands to compile the package.
|
||||||
|
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
rm -f build-stamp configure-stamp
|
||||||
|
|
||||||
|
# Add here commands to clean up after the build process.
|
||||||
|
|
||||||
|
dh_clean
|
||||||
|
|
||||||
|
install: build
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
dh_prep
|
||||||
|
dh_installdirs
|
||||||
|
|
||||||
|
chmod +x src/start.py
|
||||||
|
cp -R src/ $(CURDIR)/debian/dupeguru-me/usr/local/share/dupeguru_me
|
||||||
|
cp $(CURDIR)/debian/dupeguru_me.desktop $(CURDIR)/debian/dupeguru-me/usr/share/applications
|
||||||
|
ln -s /usr/local/share/dupeguru_me/start.py $(CURDIR)/debian/dupeguru-me/usr/local/bin/dupeguru_me
|
||||||
|
|
||||||
|
|
||||||
|
# Build architecture-independent files here.
|
||||||
|
binary-indep: install
|
||||||
|
# We have nothing to do by default.
|
||||||
|
|
||||||
|
# Build architecture-dependent files here.
|
||||||
|
binary-arch: install
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
dh_installchangelogs
|
||||||
|
dh_installdocs
|
||||||
|
dh_installexamples
|
||||||
|
dh_install
|
||||||
|
dh_installmenu
|
||||||
|
# dh_installdebconf
|
||||||
|
# dh_installlogrotate
|
||||||
|
# dh_installemacsen
|
||||||
|
# dh_installpam
|
||||||
|
# dh_installmime
|
||||||
|
# dh_python
|
||||||
|
# dh_installinit
|
||||||
|
# dh_installcron
|
||||||
|
# dh_installinfo
|
||||||
|
dh_installman
|
||||||
|
dh_link
|
||||||
|
dh_strip
|
||||||
|
dh_compress
|
||||||
|
dh_fixperms
|
||||||
|
# dh_perl
|
||||||
|
# dh_makeshlibs
|
||||||
|
dh_installdeb
|
||||||
|
dh_shlibdeps
|
||||||
|
dh_gencontrol
|
||||||
|
dh_md5sums
|
||||||
|
dh_builddeb
|
||||||
|
|
||||||
|
binary: binary-indep binary-arch
|
||||||
|
.PHONY: build clean binary-indep binary-arch binary install configure
|
||||||
1
debian_pe/compat
Normal file
1
debian_pe/compat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7
|
||||||
12
debian_pe/control
Normal file
12
debian_pe/control
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Source: dupeguru-pe
|
||||||
|
Section: devel
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Virgil Dupras <hsoft@hardcoded.net>
|
||||||
|
Build-Depends: debhelper (>= 7)
|
||||||
|
Standards-Version: 3.8.1
|
||||||
|
Homepage: http://www.hardcoded.net
|
||||||
|
|
||||||
|
Package: dupeguru-pe
|
||||||
|
Architecture: any
|
||||||
|
Depends: python3 (>= 3.1)
|
||||||
|
Description: dupeGuru Picture Edition
|
||||||
3
debian_pe/dirs
Normal file
3
debian_pe/dirs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
usr/local/bin
|
||||||
|
usr/local/share
|
||||||
|
usr/share/applications
|
||||||
9
debian_pe/dupeguru_pe.desktop
Normal file
9
debian_pe/dupeguru_pe.desktop
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Encoding=UTF-8
|
||||||
|
Name=dupeGuru Picture Edition
|
||||||
|
Comment=Find duplicate pictures in your library.
|
||||||
|
Exec=dupeguru_pe
|
||||||
|
Icon=/usr/local/share/dupeguru_pe/dgpe_logo_128.png
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Utility
|
||||||
86
debian_pe/rules
Executable file
86
debian_pe/rules
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
# Sample debian/rules that uses debhelper.
|
||||||
|
# This file was originally written by Joey Hess and Craig Small.
|
||||||
|
# As a special exception, when this file is copied by dh-make into a
|
||||||
|
# dh-make output file, you may use that output file without restriction.
|
||||||
|
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
configure: configure-stamp
|
||||||
|
configure-stamp:
|
||||||
|
dh_testdir
|
||||||
|
# Add here commands to configure the package.
|
||||||
|
|
||||||
|
touch configure-stamp
|
||||||
|
|
||||||
|
|
||||||
|
build: build-stamp
|
||||||
|
|
||||||
|
build-stamp: configure-stamp
|
||||||
|
dh_testdir
|
||||||
|
|
||||||
|
# Add here commands to compile the package.
|
||||||
|
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
rm -f build-stamp configure-stamp
|
||||||
|
|
||||||
|
# Add here commands to clean up after the build process.
|
||||||
|
|
||||||
|
dh_clean
|
||||||
|
|
||||||
|
install: build
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
dh_prep
|
||||||
|
dh_installdirs
|
||||||
|
|
||||||
|
chmod +x src/start.py
|
||||||
|
cp -R src/ $(CURDIR)/debian/dupeguru-pe/usr/local/share/dupeguru_pe
|
||||||
|
cp $(CURDIR)/debian/dupeguru_pe.desktop $(CURDIR)/debian/dupeguru-pe/usr/share/applications
|
||||||
|
ln -s /usr/local/share/dupeguru_pe/start.py $(CURDIR)/debian/dupeguru-pe/usr/local/bin/dupeguru_pe
|
||||||
|
|
||||||
|
|
||||||
|
# Build architecture-independent files here.
|
||||||
|
binary-indep: install
|
||||||
|
# We have nothing to do by default.
|
||||||
|
|
||||||
|
# Build architecture-dependent files here.
|
||||||
|
binary-arch: install
|
||||||
|
dh_testdir
|
||||||
|
dh_testroot
|
||||||
|
dh_installchangelogs
|
||||||
|
dh_installdocs
|
||||||
|
dh_installexamples
|
||||||
|
dh_install
|
||||||
|
dh_installmenu
|
||||||
|
# dh_installdebconf
|
||||||
|
# dh_installlogrotate
|
||||||
|
# dh_installemacsen
|
||||||
|
# dh_installpam
|
||||||
|
# dh_installmime
|
||||||
|
# dh_python
|
||||||
|
# dh_installinit
|
||||||
|
# dh_installcron
|
||||||
|
# dh_installinfo
|
||||||
|
dh_installman
|
||||||
|
dh_link
|
||||||
|
dh_strip
|
||||||
|
dh_compress
|
||||||
|
dh_fixperms
|
||||||
|
# dh_perl
|
||||||
|
# dh_makeshlibs
|
||||||
|
dh_installdeb
|
||||||
|
dh_shlibdeps
|
||||||
|
dh_gencontrol
|
||||||
|
dh_md5sums
|
||||||
|
dh_builddeb
|
||||||
|
|
||||||
|
binary: binary-indep binary-arch
|
||||||
|
.PHONY: build clean binary-indep binary-arch binary install configure
|
||||||
1
debian_se/compat
Normal file
1
debian_se/compat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user