mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-10 05:34:36 +00:00
Merge commit 'a65077f871481ca98ce51810751e66f228cb096a'
# Conflicts: # build.py # core/pe/iphoto_plist.py
This commit is contained in:
commit
b780816e3c
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,25 +1,21 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.pyc
|
__pycache__
|
||||||
*.so
|
*.so
|
||||||
*.mo
|
*.mo
|
||||||
*.pyd
|
*.pyd
|
||||||
*.waf*
|
*.waf*
|
||||||
.lock-waf*
|
.lock-waf*
|
||||||
.idea
|
|
||||||
.tox
|
.tox
|
||||||
|
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
install
|
|
||||||
installer_tmp-cache
|
|
||||||
env
|
env
|
||||||
/deps
|
/deps
|
||||||
cocoa/autogen
|
cocoa/autogen
|
||||||
|
|
||||||
/run.py
|
/run.py
|
||||||
/conf.json
|
|
||||||
/cocoa/*/Info.plist
|
/cocoa/*/Info.plist
|
||||||
/cocoa/*/build
|
/cocoa/*/build
|
||||||
/qt/base/*_rc.py
|
/qt/*_rc.py
|
||||||
/help/*/conf.py
|
/help/*/conf.py
|
||||||
/help/*/changelog.rst
|
/help/*/changelog.rst
|
||||||
|
@ -5,9 +5,6 @@ a system. It's written mostly in Python 3 and has the peculiarity of using
|
|||||||
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
||||||
is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
|
is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
|
||||||
|
|
||||||
dupeGuru comes in 3 editions (standard, music and picture) which are all buildable from this same
|
|
||||||
source tree. You choose the edition you want to build in a `configure.py` flag.
|
|
||||||
|
|
||||||
# Current status: People wanted
|
# Current status: People wanted
|
||||||
|
|
||||||
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be
|
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be
|
||||||
@ -121,7 +118,6 @@ For OS X:
|
|||||||
|
|
||||||
With your virtualenv activated, you can build and run dupeGuru with these commands:
|
With your virtualenv activated, you can build and run dupeGuru with these commands:
|
||||||
|
|
||||||
$ python configure.py
|
|
||||||
$ python build.py
|
$ python build.py
|
||||||
$ python run.py
|
$ python run.py
|
||||||
|
|
||||||
|
@ -44,4 +44,4 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:"
|
echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:"
|
||||||
echo ". env/bin/activate && python configure.py && python build.py && python run.py"
|
echo ". env/bin/activate && python build.py && python run.py"
|
||||||
|
189
build.py
189
build.py
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -11,7 +9,6 @@ import os
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
|
||||||
import importlib
|
import importlib
|
||||||
import compileall
|
import compileall
|
||||||
|
|
||||||
@ -22,10 +19,10 @@ from hscommon.build import (
|
|||||||
add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
||||||
get_module_version, move_all, copy_all, OSXAppStructure,
|
get_module_version, move_all, copy_all, OSXAppStructure,
|
||||||
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
||||||
collect_stdlib_dependencies, copy
|
collect_stdlib_dependencies
|
||||||
)
|
)
|
||||||
from hscommon import loc
|
from hscommon import loc
|
||||||
from hscommon.plat import ISOSX, ISLINUX
|
from hscommon.plat import ISOSX
|
||||||
from hscommon.util import ensure_folder, delete_files_with_pattern
|
from hscommon.util import ensure_folder, delete_files_with_pattern
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@ -39,6 +36,14 @@ def parse_args():
|
|||||||
'--doc', action='store_true', dest='doc',
|
'--doc', action='store_true', dest='doc',
|
||||||
help="Build only the help file"
|
help="Build only the help file"
|
||||||
)
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'--ui', dest='ui',
|
||||||
|
help="Type of UI to build. 'qt' or 'cocoa'. Default is determined by your system."
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'--dev', action='store_true', dest='dev', default=False,
|
||||||
|
help="If this flag is set, will configure for dev builds."
|
||||||
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--loc', action='store_true', dest='loc',
|
'--loc', action='store_true', dest='loc',
|
||||||
help="Build only localization"
|
help="Build only localization"
|
||||||
@ -70,15 +75,11 @@ def parse_args():
|
|||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def cocoa_app(edition):
|
def cocoa_app():
|
||||||
app_path = {
|
app_path = 'build/dupeGuru.app'
|
||||||
'se': 'build/dupeGuru.app',
|
|
||||||
'me': 'build/dupeGuru ME.app',
|
|
||||||
'pe': 'build/dupeGuru PE.app',
|
|
||||||
}[edition]
|
|
||||||
return OSXAppStructure(app_path)
|
return OSXAppStructure(app_path)
|
||||||
|
|
||||||
def build_xibless(edition, dest='cocoa/autogen'):
|
def build_xibless(dest='cocoa/autogen'):
|
||||||
import xibless
|
import xibless
|
||||||
ensure_folder(dest)
|
ensure_folder(dest)
|
||||||
FNPAIRS = [
|
FNPAIRS = [
|
||||||
@ -94,56 +95,50 @@ def build_xibless(edition, dest='cocoa/autogen'):
|
|||||||
for srcname, dstname in FNPAIRS:
|
for srcname, dstname in FNPAIRS:
|
||||||
xibless.generate(
|
xibless.generate(
|
||||||
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
|
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
|
||||||
localizationTable='Localizable', args={'edition': edition}
|
|
||||||
)
|
|
||||||
if edition == 'pe':
|
|
||||||
xibless.generate(
|
|
||||||
'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
|
|
||||||
localizationTable='Localizable'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
xibless.generate(
|
|
||||||
'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
|
|
||||||
localizationTable='Localizable'
|
localizationTable='Localizable'
|
||||||
)
|
)
|
||||||
|
# XXX This is broken
|
||||||
|
assert False
|
||||||
|
# if edition == 'pe':
|
||||||
|
# xibless.generate(
|
||||||
|
# 'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
|
||||||
|
# localizationTable='Localizable'
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# xibless.generate(
|
||||||
|
# 'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
|
||||||
|
# localizationTable='Localizable'
|
||||||
|
# )
|
||||||
|
|
||||||
def build_cocoa(edition, dev):
|
def build_cocoa(dev):
|
||||||
print("Creating OS X app structure")
|
print("Creating OS X app structure")
|
||||||
ed = lambda s: s.format(edition)
|
app = cocoa_app()
|
||||||
app = cocoa_app(edition)
|
app_version = get_module_version('core')
|
||||||
app_version = get_module_version(ed('core_{}'))
|
cocoa_project_path = 'cocoa/se'
|
||||||
cocoa_project_path = ed('cocoa/{}')
|
|
||||||
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
|
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
|
||||||
app.create(op.join('build', 'Info.plist'))
|
app.create(op.join('build', 'Info.plist'))
|
||||||
print("Building localizations")
|
print("Building localizations")
|
||||||
build_localizations('cocoa', edition)
|
build_localizations('cocoa')
|
||||||
print("Building xibless UIs")
|
print("Building xibless UIs")
|
||||||
build_cocoalib_xibless()
|
build_cocoalib_xibless()
|
||||||
build_xibless(edition)
|
build_xibless()
|
||||||
print("Building Python extensions")
|
print("Building Python extensions")
|
||||||
build_cocoa_proxy_module()
|
build_cocoa_proxy_module()
|
||||||
build_cocoa_bridging_interfaces(edition)
|
build_cocoa_bridging_interfaces()
|
||||||
print("Building the cocoa layer")
|
print("Building the cocoa layer")
|
||||||
copy_embeddable_python_dylib('build')
|
copy_embeddable_python_dylib('build')
|
||||||
pydep_folder = op.join(app.resources, 'py')
|
pydep_folder = op.join(app.resources, 'py')
|
||||||
if not op.exists(pydep_folder):
|
if not op.exists(pydep_folder):
|
||||||
os.mkdir(pydep_folder)
|
os.mkdir(pydep_folder)
|
||||||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
||||||
specific_packages = {
|
|
||||||
'se': ['core_se'],
|
|
||||||
'me': ['core_me', 'hsaudiotag'],
|
|
||||||
'pe': ['core_pe'],
|
|
||||||
}[edition]
|
|
||||||
tocopy = [
|
tocopy = [
|
||||||
'core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash'
|
'core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash', 'hsaudiotag',
|
||||||
] + specific_packages
|
]
|
||||||
copy_packages(tocopy, pydep_folder, create_links=dev)
|
copy_packages(tocopy, pydep_folder, create_links=dev)
|
||||||
sys.path.insert(0, 'build')
|
sys.path.insert(0, 'build')
|
||||||
extra_deps = None
|
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
||||||
if edition == 'pe':
|
# to manually specify it.
|
||||||
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
extra_deps = ['multiprocessing']
|
||||||
# to manually specify it.
|
|
||||||
extra_deps = ['multiprocessing']
|
|
||||||
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
|
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
|
||||||
del sys.path[0]
|
del sys.path[0]
|
||||||
# Views are not referenced by python code, so they're not found by the collector.
|
# Views are not referenced by python code, so they're not found by the collector.
|
||||||
@ -156,12 +151,12 @@ def build_cocoa(edition, dev):
|
|||||||
delete_files_with_pattern(pydep_folder, '__pycache__')
|
delete_files_with_pattern(pydep_folder, '__pycache__')
|
||||||
print("Compiling with WAF")
|
print("Compiling with WAF")
|
||||||
os.chdir('cocoa')
|
os.chdir('cocoa')
|
||||||
print_and_do('{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition))
|
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
app.copy_executable('cocoa/build/dupeGuru')
|
app.copy_executable('cocoa/build/dupeGuru')
|
||||||
build_help(edition)
|
build_help()
|
||||||
print("Copying resources and frameworks")
|
print("Copying resources and frameworks")
|
||||||
image_path = ed('cocoa/{}/dupeguru.icns')
|
image_path = 'cocoa/se/dupeguru.icns'
|
||||||
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
||||||
app.copy_resources(*resources, use_symlinks=dev)
|
app.copy_resources(*resources, use_symlinks=dev)
|
||||||
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
||||||
@ -170,26 +165,26 @@ def build_cocoa(edition, dev):
|
|||||||
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
||||||
open('run.py', 'wt').write(run_contents)
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
def build_qt(edition, dev, conf):
|
def build_qt(dev):
|
||||||
print("Building localizations")
|
print("Building localizations")
|
||||||
build_localizations('qt', edition)
|
build_localizations('qt')
|
||||||
print("Building Qt stuff")
|
print("Building Qt stuff")
|
||||||
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'dg.qrc'), op.join('qt', 'dg_rc.py')))
|
||||||
fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
|
fix_qt_resource_file(op.join('qt', 'dg_rc.py'))
|
||||||
build_help(edition)
|
build_help()
|
||||||
print("Creating the run.py file")
|
print("Creating the run.py file")
|
||||||
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
|
shutil.copy(op.join('qt', 'run_template.py'), 'run.py')
|
||||||
|
|
||||||
def build_help(edition):
|
def build_help():
|
||||||
print("Generating Help")
|
print("Generating Help")
|
||||||
current_path = op.abspath('.')
|
current_path = op.abspath('.')
|
||||||
help_basepath = op.join(current_path, 'help', 'en')
|
help_basepath = op.join(current_path, 'help', 'en')
|
||||||
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
help_destpath = op.join(current_path, 'build', 'help')
|
||||||
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
changelog_path = op.join(current_path, 'help', 'changelog_se')
|
||||||
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
||||||
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
appname = 'dupeGuru'
|
||||||
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
homepage = 'https://www.hardcoded.net/dupeguru/'
|
||||||
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
|
confrepl = {'appname': appname, 'homepage': homepage, 'language': 'en'}
|
||||||
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
||||||
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
||||||
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
||||||
@ -198,10 +193,10 @@ def build_qt_localizations():
|
|||||||
loc.compile_all_po(op.join('qtlib', 'locale'))
|
loc.compile_all_po(op.join('qtlib', 'locale'))
|
||||||
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
||||||
|
|
||||||
def build_localizations(ui, edition):
|
def build_localizations(ui):
|
||||||
loc.compile_all_po('locale')
|
loc.compile_all_po('locale')
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
app = cocoa_app(edition)
|
app = cocoa_app()
|
||||||
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
|
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
|
||||||
locale_dest = op.join(app.resources, 'locale')
|
locale_dest = op.join(app.resources, 'locale')
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
@ -210,32 +205,19 @@ def build_localizations(ui, edition):
|
|||||||
if op.exists(locale_dest):
|
if op.exists(locale_dest):
|
||||||
shutil.rmtree(locale_dest)
|
shutil.rmtree(locale_dest)
|
||||||
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
||||||
if ui == 'qt' and not ISLINUX:
|
|
||||||
print("Copying qt_*.qm files into the 'locale' folder")
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
|
||||||
trfolder = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
|
||||||
for lang in loc.get_langs('locale'):
|
|
||||||
qmname = 'qt_%s.qm' % lang
|
|
||||||
src = op.join(trfolder, qmname)
|
|
||||||
if op.exists(src):
|
|
||||||
copy(src, op.join('build', 'locale', qmname))
|
|
||||||
|
|
||||||
def build_updatepot():
|
def build_updatepot():
|
||||||
if ISOSX:
|
if ISOSX:
|
||||||
print("Updating Cocoa strings file.")
|
print("Updating Cocoa strings file.")
|
||||||
# We need to have strings from *all* editions in here, so we'll call xibless for all editions
|
|
||||||
# in dummy subfolders.
|
|
||||||
build_cocoalib_xibless('cocoalib/autogen')
|
build_cocoalib_xibless('cocoalib/autogen')
|
||||||
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
|
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
|
||||||
for edition in ('se', 'me', 'pe'):
|
build_xibless('se', op.join('cocoa', 'autogen', 'se'))
|
||||||
build_xibless(edition, op.join('cocoa', 'autogen', edition))
|
|
||||||
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
|
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
|
||||||
print("Building .pot files from source files")
|
print("Building .pot files from source files")
|
||||||
print("Building core.pot")
|
print("Building core.pot")
|
||||||
all_cores = ['core', 'core_se', 'core_me', 'core_pe']
|
loc.generate_pot(['core'], op.join('locale', 'core.pot'), ['tr'])
|
||||||
loc.generate_pot(all_cores, op.join('locale', 'core.pot'), ['tr'])
|
|
||||||
print("Building columns.pot")
|
print("Building columns.pot")
|
||||||
loc.generate_pot(all_cores, op.join('locale', 'columns.pot'), ['coltr'])
|
loc.generate_pot(['core'], op.join('locale', 'columns.pot'), ['coltr'])
|
||||||
print("Building ui.pot")
|
print("Building ui.pot")
|
||||||
# When we're not under OS X, we don't want to overwrite ui.pot because it contains Cocoa locs
|
# When we're not under OS X, we don't want to overwrite ui.pot because it contains Cocoa locs
|
||||||
# We want to merge the generated pot with the old pot in the most preserving way possible.
|
# We want to merge the generated pot with the old pot in the most preserving way possible.
|
||||||
@ -279,7 +261,7 @@ def build_cocoa_proxy_module():
|
|||||||
['cocoalib', 'cocoa/autogen']
|
['cocoalib', 'cocoa/autogen']
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_cocoa_bridging_interfaces(edition):
|
def build_cocoa_bridging_interfaces():
|
||||||
print("Building Cocoa Bridging Interfaces")
|
print("Building Cocoa Bridging Interfaces")
|
||||||
import objp.o2p
|
import objp.o2p
|
||||||
import objp.p2o
|
import objp.p2o
|
||||||
@ -300,7 +282,7 @@ def build_cocoa_bridging_interfaces(edition):
|
|||||||
from inter.result_table import PyResultTable, ResultTableView
|
from inter.result_table import PyResultTable, ResultTableView
|
||||||
from inter.stats_label import PyStatsLabel, StatsLabelView
|
from inter.stats_label import PyStatsLabel, StatsLabelView
|
||||||
from inter.app import PyDupeGuruBase, DupeGuruView
|
from inter.app import PyDupeGuruBase, DupeGuruView
|
||||||
appmod = importlib.import_module('inter.app_{}'.format(edition))
|
appmod = importlib.import_module('inter.app_se')
|
||||||
allclasses = [
|
allclasses = [
|
||||||
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
|
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
|
||||||
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
||||||
@ -322,14 +304,21 @@ def build_cocoa_bridging_interfaces(edition):
|
|||||||
def build_pe_modules(ui):
|
def build_pe_modules(ui):
|
||||||
print("Building PE Modules")
|
print("Building PE Modules")
|
||||||
exts = [
|
exts = [
|
||||||
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
Extension(
|
||||||
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]),
|
"_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':
|
if ui == 'qt':
|
||||||
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
|
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
|
||||||
elif ui == 'cocoa':
|
elif ui == 'cocoa':
|
||||||
exts.append(Extension(
|
exts.append(Extension(
|
||||||
"_block_osx", [op.join('core_pe', 'modules', 'block_osx.m'), op.join('core_pe', 'modules', 'common.c')],
|
"_block_osx",
|
||||||
|
[op.join('core', 'pe', 'modules', 'block_osx.m'), op.join('core', 'pe', 'modules', 'common.c')],
|
||||||
extra_link_args=[
|
extra_link_args=[
|
||||||
"-framework", "CoreFoundation",
|
"-framework", "CoreFoundation",
|
||||||
"-framework", "Foundation",
|
"-framework", "Foundation",
|
||||||
@ -341,27 +330,25 @@ def build_pe_modules(ui):
|
|||||||
ext_modules=exts,
|
ext_modules=exts,
|
||||||
)
|
)
|
||||||
move_all('_block_qt*', op.join('qt', 'pe'))
|
move_all('_block_qt*', op.join('qt', 'pe'))
|
||||||
move_all('_block*', 'core_pe')
|
move_all('_block*', op.join('core', 'pe'))
|
||||||
move_all('_cache*', 'core_pe')
|
move_all('_cache*', op.join('core', 'pe'))
|
||||||
|
|
||||||
def build_normal(edition, ui, dev, conf):
|
def build_normal(ui, dev):
|
||||||
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
print("Building dupeGuru with UI {}".format(ui))
|
||||||
add_to_pythonpath('.')
|
add_to_pythonpath('.')
|
||||||
print("Building dupeGuru")
|
print("Building dupeGuru")
|
||||||
if edition == 'pe':
|
build_pe_modules(ui)
|
||||||
build_pe_modules(ui)
|
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
build_cocoa(edition, dev)
|
build_cocoa(dev)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
build_qt(edition, dev, conf)
|
build_qt(dev)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
options = parse_args()
|
options = parse_args()
|
||||||
conf = json.load(open('conf.json'))
|
ui = options.ui
|
||||||
edition = conf['edition']
|
if ui not in ('cocoa', 'qt'):
|
||||||
ui = conf['ui']
|
ui = 'cocoa' if ISOSX else 'qt'
|
||||||
dev = conf['dev']
|
if options.dev:
|
||||||
if dev:
|
|
||||||
print("Building in Dev mode")
|
print("Building in Dev mode")
|
||||||
if options.clean:
|
if options.clean:
|
||||||
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
||||||
@ -370,9 +357,9 @@ def main():
|
|||||||
if not op.exists('build'):
|
if not op.exists('build'):
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
if options.doc:
|
if options.doc:
|
||||||
build_help(edition)
|
build_help()
|
||||||
elif options.loc:
|
elif options.loc:
|
||||||
build_localizations(ui, edition)
|
build_localizations(ui)
|
||||||
elif options.updatepot:
|
elif options.updatepot:
|
||||||
build_updatepot()
|
build_updatepot()
|
||||||
elif options.mergepot:
|
elif options.mergepot:
|
||||||
@ -381,17 +368,17 @@ def main():
|
|||||||
build_normpo()
|
build_normpo()
|
||||||
elif options.cocoa_ext:
|
elif options.cocoa_ext:
|
||||||
build_cocoa_proxy_module()
|
build_cocoa_proxy_module()
|
||||||
build_cocoa_bridging_interfaces(edition)
|
build_cocoa_bridging_interfaces()
|
||||||
elif options.cocoa_compile:
|
elif options.cocoa_compile:
|
||||||
os.chdir('cocoa')
|
os.chdir('cocoa')
|
||||||
print_and_do('{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition))
|
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
|
cocoa_app().copy_executable('cocoa/build/dupeGuru')
|
||||||
elif options.xibless:
|
elif options.xibless:
|
||||||
build_cocoalib_xibless()
|
build_cocoalib_xibless()
|
||||||
build_xibless(edition)
|
build_xibless()
|
||||||
else:
|
else:
|
||||||
build_normal(edition, ui, dev, conf)
|
build_normal(ui, options.dev)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
45
configure.py
45
configure.py
@ -1,45 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from optparse import OptionParser
|
|
||||||
import json
|
|
||||||
|
|
||||||
from hscommon.plat import ISOSX
|
|
||||||
|
|
||||||
def main(options):
|
|
||||||
if options.edition not in {'se', 'me', 'pe'}:
|
|
||||||
options.edition = 'se'
|
|
||||||
if options.ui not in {'cocoa', 'qt'}:
|
|
||||||
options.ui = 'cocoa' if ISOSX else 'qt'
|
|
||||||
build_type = 'Dev' if options.dev else 'Release'
|
|
||||||
print("Configuring dupeGuru {0} for UI {1} ({2})".format(options.edition.upper(), options.ui, build_type))
|
|
||||||
conf = {
|
|
||||||
'edition': options.edition,
|
|
||||||
'ui': options.ui,
|
|
||||||
'dev': options.dev,
|
|
||||||
}
|
|
||||||
json.dump(conf, open('conf.json', 'w'))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
usage = "usage: %prog [options]"
|
|
||||||
parser = OptionParser(usage=usage)
|
|
||||||
parser.add_option(
|
|
||||||
'--edition', dest='edition',
|
|
||||||
help="dupeGuru edition to build (se, me or pe). Default is se."
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--ui', dest='ui',
|
|
||||||
help="Type of UI to build. 'qt' or 'cocoa'. Default is determined by your system."
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--dev', action='store_true', dest='dev', default=False,
|
|
||||||
help="If this flag is set, will configure for dev builds."
|
|
||||||
)
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
main(options)
|
|
||||||
|
|
@ -1 +1,3 @@
|
|||||||
|
__version__ = '3.9.1'
|
||||||
|
__appname__ = 'dupeGuru'
|
||||||
|
|
||||||
|
154
core/app.py
154
core/app.py
@ -9,7 +9,6 @@ import os.path as op
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from send2trash import send2trash
|
from send2trash import send2trash
|
||||||
@ -18,14 +17,16 @@ from hscommon.notify import Broadcaster
|
|||||||
from hscommon.path import Path
|
from hscommon.path import Path
|
||||||
from hscommon.conflict import smart_move, smart_copy
|
from hscommon.conflict import smart_move, smart_copy
|
||||||
from hscommon.gui.progress_window import ProgressWindow
|
from hscommon.gui.progress_window import ProgressWindow
|
||||||
from hscommon.util import delete_if_empty, first, escape, nonone, format_time_decimal, allsame
|
from hscommon.util import delete_if_empty, first, escape, nonone, allsame
|
||||||
from hscommon.trans import tr
|
from hscommon.trans import tr
|
||||||
from hscommon.plat import ISWINDOWS
|
|
||||||
from hscommon import desktop
|
from hscommon import desktop
|
||||||
|
|
||||||
from . import directories, results, export, fs
|
from . import se, me, pe
|
||||||
|
from .pe.photo import get_delta_dimensions
|
||||||
|
from .util import cmp_value, fix_surrogate_encoding
|
||||||
|
from . import directories, results, export, fs, prioritize
|
||||||
from .ignore import IgnoreList
|
from .ignore import IgnoreList
|
||||||
from .scanner import ScanType, Scanner
|
from .scanner import ScanType
|
||||||
from .gui.deletion_options import DeletionOptions
|
from .gui.deletion_options import DeletionOptions
|
||||||
from .gui.details_panel import DetailsPanel
|
from .gui.details_panel import DetailsPanel
|
||||||
from .gui.directory_tree import DirectoryTree
|
from .gui.directory_tree import DirectoryTree
|
||||||
@ -55,6 +56,11 @@ class JobType:
|
|||||||
Copy = 'job_copy'
|
Copy = 'job_copy'
|
||||||
Delete = 'job_delete'
|
Delete = 'job_delete'
|
||||||
|
|
||||||
|
class AppMode:
|
||||||
|
Standard = 0
|
||||||
|
Music = 1
|
||||||
|
Picture = 2
|
||||||
|
|
||||||
JOBID2TITLE = {
|
JOBID2TITLE = {
|
||||||
JobType.Scan: tr("Scanning for duplicates"),
|
JobType.Scan: tr("Scanning for duplicates"),
|
||||||
JobType.Load: tr("Loading"),
|
JobType.Load: tr("Loading"),
|
||||||
@ -62,53 +68,6 @@ JOBID2TITLE = {
|
|||||||
JobType.Copy: tr("Copying"),
|
JobType.Copy: tr("Copying"),
|
||||||
JobType.Delete: tr("Sending to Trash"),
|
JobType.Delete: tr("Sending to Trash"),
|
||||||
}
|
}
|
||||||
if ISWINDOWS:
|
|
||||||
JOBID2TITLE[JobType.Delete] = tr("Sending files to the recycle bin")
|
|
||||||
|
|
||||||
def format_timestamp(t, delta):
|
|
||||||
if delta:
|
|
||||||
return format_time_decimal(t)
|
|
||||||
else:
|
|
||||||
if t > 0:
|
|
||||||
return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(t))
|
|
||||||
else:
|
|
||||||
return '---'
|
|
||||||
|
|
||||||
def format_words(w):
|
|
||||||
def do_format(w):
|
|
||||||
if isinstance(w, list):
|
|
||||||
return '(%s)' % ', '.join(do_format(item) for item in w)
|
|
||||||
else:
|
|
||||||
return w.replace('\n', ' ')
|
|
||||||
|
|
||||||
return ', '.join(do_format(item) for item in w)
|
|
||||||
|
|
||||||
def format_perc(p):
|
|
||||||
return "%0.0f" % p
|
|
||||||
|
|
||||||
def format_dupe_count(c):
|
|
||||||
return str(c) if c else '---'
|
|
||||||
|
|
||||||
def cmp_value(dupe, attrname):
|
|
||||||
value = getattr(dupe, attrname, '')
|
|
||||||
return value.lower() if isinstance(value, str) else value
|
|
||||||
|
|
||||||
def fix_surrogate_encoding(s, encoding='utf-8'):
|
|
||||||
# ref #210. It's possible to end up with file paths that, while correct unicode strings, are
|
|
||||||
# decoded with the 'surrogateescape' option, which make the string unencodable to utf-8. We fix
|
|
||||||
# these strings here by trying to encode them and, if it fails, we do an encode/decode dance
|
|
||||||
# to remove the problematic characters. This dance is *lossy* but there's not much we can do
|
|
||||||
# because if we end up with this type of string, it means that we don't know the encoding of the
|
|
||||||
# underlying filesystem that brought them. Don't use this for strings you're going to re-use in
|
|
||||||
# fs-related functions because you're going to lose your path (it's going to change). Use this
|
|
||||||
# if you need to export the path somewhere else, outside of the unicode realm.
|
|
||||||
# See http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/
|
|
||||||
try:
|
|
||||||
s.encode(encoding)
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
return s.encode(encoding, 'replace').decode(encoding)
|
|
||||||
else:
|
|
||||||
return s
|
|
||||||
|
|
||||||
class DupeGuru(Broadcaster):
|
class DupeGuru(Broadcaster):
|
||||||
"""Holds everything together.
|
"""Holds everything together.
|
||||||
@ -149,13 +108,13 @@ class DupeGuru(Broadcaster):
|
|||||||
# open_path(path)
|
# open_path(path)
|
||||||
# reveal_path(path)
|
# reveal_path(path)
|
||||||
# ask_yes_no(prompt) --> bool
|
# ask_yes_no(prompt) --> bool
|
||||||
|
# create_results_window()
|
||||||
# show_results_window()
|
# show_results_window()
|
||||||
# show_problem_dialog()
|
# show_problem_dialog()
|
||||||
# select_dest_folder(prompt: str) --> str
|
# select_dest_folder(prompt: str) --> str
|
||||||
# select_dest_file(prompt: str, ext: str) --> str
|
# select_dest_file(prompt: str, ext: str) --> str
|
||||||
|
|
||||||
PROMPT_NAME = "dupeGuru"
|
NAME = PROMPT_NAME = "dupeGuru"
|
||||||
SCANNER_CLASS = Scanner
|
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, view):
|
||||||
if view.get_default(DEBUG_MODE_PREFERENCE):
|
if view.get_default(DEBUG_MODE_PREFERENCE):
|
||||||
@ -166,9 +125,8 @@ class DupeGuru(Broadcaster):
|
|||||||
self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME)
|
self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME)
|
||||||
if not op.exists(self.appdata):
|
if not op.exists(self.appdata):
|
||||||
os.makedirs(self.appdata)
|
os.makedirs(self.appdata)
|
||||||
|
self.app_mode = AppMode.Standard
|
||||||
self.discarded_file_count = 0
|
self.discarded_file_count = 0
|
||||||
self.fileclasses = [fs.File]
|
|
||||||
self.folderclass = fs.Folder
|
|
||||||
self.directories = directories.Directories()
|
self.directories = directories.Directories()
|
||||||
self.results = results.Results(self)
|
self.results = results.Results(self)
|
||||||
self.ignore_list = IgnoreList()
|
self.ignore_list = IgnoreList()
|
||||||
@ -180,6 +138,7 @@ class DupeGuru(Broadcaster):
|
|||||||
'clean_empty_dirs': False,
|
'clean_empty_dirs': False,
|
||||||
'ignore_hardlink_matches': False,
|
'ignore_hardlink_matches': False,
|
||||||
'copymove_dest_type': DestType.Relative,
|
'copymove_dest_type': DestType.Relative,
|
||||||
|
'cache_path': op.join(self.appdata, 'cached_pictures.db'),
|
||||||
}
|
}
|
||||||
self.selected_dupes = []
|
self.selected_dupes = []
|
||||||
self.details_panel = DetailsPanel(self)
|
self.details_panel = DetailsPanel(self)
|
||||||
@ -187,22 +146,32 @@ class DupeGuru(Broadcaster):
|
|||||||
self.problem_dialog = ProblemDialog(self)
|
self.problem_dialog = ProblemDialog(self)
|
||||||
self.ignore_list_dialog = IgnoreListDialog(self)
|
self.ignore_list_dialog = IgnoreListDialog(self)
|
||||||
self.stats_label = StatsLabel(self)
|
self.stats_label = StatsLabel(self)
|
||||||
self.result_table = self._create_result_table()
|
self.result_table = None
|
||||||
self.deletion_options = DeletionOptions()
|
self.deletion_options = DeletionOptions()
|
||||||
self.progress_window = ProgressWindow(self._job_completed)
|
self.progress_window = ProgressWindow(self._job_completed)
|
||||||
children = [self.result_table, self.directory_tree, self.stats_label, self.details_panel]
|
children = [self.directory_tree, self.stats_label, self.details_panel]
|
||||||
for child in children:
|
for child in children:
|
||||||
child.connect()
|
child.connect()
|
||||||
|
|
||||||
#--- Virtual
|
|
||||||
def _prioritization_categories(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _create_result_table(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
#--- Private
|
#--- Private
|
||||||
|
def _create_result_table(self):
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
return pe.result_table.ResultTable(self)
|
||||||
|
elif self.app_mode == AppMode.Music:
|
||||||
|
return me.result_table.ResultTable(self)
|
||||||
|
else:
|
||||||
|
return se.result_table.ResultTable(self)
|
||||||
|
|
||||||
def _get_dupe_sort_key(self, dupe, get_group, key, delta):
|
def _get_dupe_sort_key(self, dupe, get_group, key, delta):
|
||||||
|
if self.app_mode in (AppMode.Music, AppMode.Picture):
|
||||||
|
if key == 'folder_path':
|
||||||
|
dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path)
|
||||||
|
return str(dupe_folder_path).lower()
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
if delta and key == 'dimensions':
|
||||||
|
r = cmp_value(dupe, key)
|
||||||
|
ref_value = cmp_value(get_group().ref, key)
|
||||||
|
return get_delta_dimensions(r, ref_value)
|
||||||
if key == 'marked':
|
if key == 'marked':
|
||||||
return self.results.is_marked(dupe)
|
return self.results.is_marked(dupe)
|
||||||
if key == 'percentage':
|
if key == 'percentage':
|
||||||
@ -222,6 +191,10 @@ class DupeGuru(Broadcaster):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _get_group_sort_key(self, group, key):
|
def _get_group_sort_key(self, group, key):
|
||||||
|
if self.app_mode in (AppMode.Music, AppMode.Picture):
|
||||||
|
if key == 'folder_path':
|
||||||
|
dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path)
|
||||||
|
return str(dupe_folder_path).lower()
|
||||||
if key == 'percentage':
|
if key == 'percentage':
|
||||||
return group.percentage
|
return group.percentage
|
||||||
if key == 'dupe_count':
|
if key == 'dupe_count':
|
||||||
@ -349,6 +322,15 @@ class DupeGuru(Broadcaster):
|
|||||||
self.selected_dupes = dupes
|
self.selected_dupes = dupes
|
||||||
self.notify('dupes_selected')
|
self.notify('dupes_selected')
|
||||||
|
|
||||||
|
#--- Protected
|
||||||
|
def _prioritization_categories(self):
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
return pe.prioritize.all_categories()
|
||||||
|
elif self.app_mode == AppMode.Music:
|
||||||
|
return me.prioritize.all_categories()
|
||||||
|
else:
|
||||||
|
return prioritize.all_categories()
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def add_directory(self, d):
|
def add_directory(self, d):
|
||||||
"""Adds folder ``d`` to :attr:`directories`.
|
"""Adds folder ``d`` to :attr:`directories`.
|
||||||
@ -400,6 +382,11 @@ class DupeGuru(Broadcaster):
|
|||||||
while delete_if_empty(path, ['.DS_Store']):
|
while delete_if_empty(path, ['.DS_Store']):
|
||||||
path = path.parent()
|
path = path.parent()
|
||||||
|
|
||||||
|
def clear_picture_cache(self):
|
||||||
|
cache = pe.cache.Cache(self.options['cache_path'])
|
||||||
|
cache.clear()
|
||||||
|
cache.close()
|
||||||
|
|
||||||
def copy_or_move(self, dupe, copy: bool, destination: str, dest_type: DestType):
|
def copy_or_move(self, dupe, copy: bool, destination: str, dest_type: DestType):
|
||||||
source_path = dupe.path
|
source_path = dupe.path
|
||||||
location_path = first(p for p in self.directories if dupe.path in p)
|
location_path = first(p for p in self.directories if dupe.path in p)
|
||||||
@ -746,12 +733,17 @@ class DupeGuru(Broadcaster):
|
|||||||
if hasattr(scanner, k):
|
if hasattr(scanner, k):
|
||||||
setattr(scanner, k, v)
|
setattr(scanner, k, v)
|
||||||
self.results.groups = []
|
self.results.groups = []
|
||||||
|
if self.result_table is not None:
|
||||||
|
self.result_table.disconnect()
|
||||||
|
self.result_table = self._create_result_table()
|
||||||
|
self.result_table.connect()
|
||||||
|
self.view.create_results_window()
|
||||||
self._results_changed()
|
self._results_changed()
|
||||||
|
|
||||||
def do(j):
|
def do(j):
|
||||||
j.set_progress(0, tr("Collecting files to scan"))
|
j.set_progress(0, tr("Collecting files to scan"))
|
||||||
if scanner.scan_type == ScanType.Folders:
|
if scanner.scan_type == ScanType.Folders:
|
||||||
files = list(self.directories.get_folders(folderclass=self.folderclass, j=j))
|
files = list(self.directories.get_folders(folderclass=se.fs.folder, j=j))
|
||||||
else:
|
else:
|
||||||
files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j))
|
files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j))
|
||||||
if self.options['ignore_hardlink_matches']:
|
if self.options['ignore_hardlink_matches']:
|
||||||
@ -800,3 +792,33 @@ class DupeGuru(Broadcaster):
|
|||||||
result = tr("%s (%d discarded)") % (result, self.discarded_file_count)
|
result = tr("%s (%d discarded)") % (result, self.discarded_file_count)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fileclasses(self):
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
return [pe.photo.PLAT_SPECIFIC_PHOTO_CLASS]
|
||||||
|
elif self.app_mode == AppMode.Music:
|
||||||
|
return [me.fs.MusicFile]
|
||||||
|
else:
|
||||||
|
return [se.fs.File]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def SCANNER_CLASS(self):
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
return pe.scanner.ScannerPE
|
||||||
|
elif self.app_mode == AppMode.Music:
|
||||||
|
return me.scanner.ScannerME
|
||||||
|
else:
|
||||||
|
return se.scanner.ScannerSE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def METADATA_TO_READ(self):
|
||||||
|
if self.app_mode == AppMode.Picture:
|
||||||
|
return ['size', 'mtime', 'dimensions', 'exif_timestamp']
|
||||||
|
elif self.app_mode == AppMode.Music:
|
||||||
|
return [
|
||||||
|
'size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
|
||||||
|
'album', 'genre', 'year', 'track', 'comment'
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return ['size', 'mtime']
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from .base import DupeGuruGUIObject
|
|||||||
|
|
||||||
class DetailsPanel(GUIObject, DupeGuruGUIObject):
|
class DetailsPanel(GUIObject, DupeGuruGUIObject):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
GUIObject.__init__(self)
|
GUIObject.__init__(self, multibind=True)
|
||||||
DupeGuruGUIObject.__init__(self, app)
|
DupeGuruGUIObject.__init__(self, app)
|
||||||
self._table = []
|
self._table = []
|
||||||
|
|
||||||
|
1
core/me/__init__.py
Normal file
1
core/me/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import fs, prioritize, result_table, scanner # noqa
|
@ -9,7 +9,7 @@
|
|||||||
from hsaudiotag import auto
|
from hsaudiotag import auto
|
||||||
from hscommon.util import get_file_ext, format_size, format_time
|
from hscommon.util import get_file_ext, format_size, format_time
|
||||||
|
|
||||||
from core.app import format_timestamp, format_perc, format_words, format_dupe_count
|
from core.util import format_timestamp, format_perc, format_words, format_dupe_count
|
||||||
from core import fs
|
from core import fs
|
||||||
|
|
||||||
TAG_FIELDS = {
|
TAG_FIELDS = {
|
1
core/pe/__init__.py
Normal file
1
core/pe/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import block, cache, exif, iphoto_plist, matchblock, matchexif, photo, prioritize, result_table, scanner # noqa
|
31
core/pe/iphoto_plist.py
Normal file
31
core/pe/iphoto_plist.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2014-03-15
|
||||||
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
import plistlib
|
||||||
|
|
||||||
|
class IPhotoPlistParser(plistlib._PlistParser):
|
||||||
|
"""A parser for iPhoto plists.
|
||||||
|
|
||||||
|
iPhoto plists tend to be malformed, so we have to subclass the built-in parser to be a bit more
|
||||||
|
lenient.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
plistlib._PlistParser.__init__(self, use_builtin_types=True, dict_type=dict)
|
||||||
|
# For debugging purposes, we remember the last bit of data to be analyzed so that we can
|
||||||
|
# log it in case of an exception
|
||||||
|
self.lastdata = ''
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
self.lastdata = plistlib._PlistParser.get_data(self)
|
||||||
|
return self.lastdata
|
||||||
|
|
||||||
|
def end_integer(self):
|
||||||
|
try:
|
||||||
|
self.add_object(int(self.get_data()))
|
||||||
|
except ValueError:
|
||||||
|
self.add_object(0)
|
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2011-05-29
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -9,10 +7,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from hscommon.util import get_file_ext, format_size
|
from hscommon.util import get_file_ext, format_size
|
||||||
|
|
||||||
from core.app import format_timestamp, format_perc, format_dupe_count
|
from core.util import format_timestamp, format_perc, format_dupe_count
|
||||||
from core import fs
|
from core import fs
|
||||||
from . import exif
|
from . import exif
|
||||||
|
|
||||||
|
# This global value is set by the platform-specific subclasser of the Photo base class
|
||||||
|
PLAT_SPECIFIC_PHOTO_CLASS = None
|
||||||
|
|
||||||
def format_dimensions(dimensions):
|
def format_dimensions(dimensions):
|
||||||
return '%d x %d' % (dimensions[0], dimensions[1])
|
return '%d x %d' % (dimensions[0], dimensions[1])
|
||||||
|
|
@ -9,7 +9,6 @@ from hscommon.trans import tr
|
|||||||
from core.scanner import Scanner, ScanType, ScanOption
|
from core.scanner import Scanner, ScanType, ScanOption
|
||||||
|
|
||||||
from . import matchblock, matchexif
|
from . import matchblock, matchexif
|
||||||
from .cache import Cache
|
|
||||||
|
|
||||||
class ScannerPE(Scanner):
|
class ScannerPE(Scanner):
|
||||||
cache_path = None
|
cache_path = None
|
||||||
@ -31,8 +30,3 @@ class ScannerPE(Scanner):
|
|||||||
else:
|
else:
|
||||||
raise Exception("Invalid scan type")
|
raise Exception("Invalid scan type")
|
||||||
|
|
||||||
def clear_picture_cache(self):
|
|
||||||
cache = Cache(self.cache_path)
|
|
||||||
cache.clear()
|
|
||||||
cache.close()
|
|
||||||
|
|
1
core/se/__init__.py
Normal file
1
core/se/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import fs, result_table, scanner # noqa
|
@ -9,7 +9,7 @@
|
|||||||
from hscommon.util import format_size
|
from hscommon.util import format_size
|
||||||
|
|
||||||
from core import fs
|
from core import fs
|
||||||
from core.app import format_timestamp, format_perc, format_words, format_dupe_count
|
from core.util import format_timestamp, format_perc, format_words, format_dupe_count
|
||||||
|
|
||||||
def get_display_info(dupe, group, delta):
|
def get_display_info(dupe, group, delta):
|
||||||
size = dupe.size
|
size = dupe.size
|
@ -384,7 +384,7 @@ class TestCaseDupeGuruWithResults:
|
|||||||
app.JOB = Job(1, lambda *args, **kw: False) # Cancels the task
|
app.JOB = Job(1, lambda *args, **kw: False) # Cancels the task
|
||||||
add_fake_files_to_directories(app.directories, self.objects) # We want the scan to at least start
|
add_fake_files_to_directories(app.directories, self.objects) # We want the scan to at least start
|
||||||
app.start_scanning() # will be cancelled immediately
|
app.start_scanning() # will be cancelled immediately
|
||||||
eq_(len(self.rtable), 0)
|
eq_(len(app.result_table), 0)
|
||||||
|
|
||||||
def test_selected_dupes_after_removal(self, do_setup):
|
def test_selected_dupes_after_removal(self, do_setup):
|
||||||
# Purge the app's `selected_dupes` attribute when removing dupes, or else it might cause a
|
# Purge the app's `selected_dupes` attribute when removing dupes, or else it might cause a
|
||||||
|
@ -4,7 +4,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.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from hscommon.testutil import TestApp as TestAppBase, eq_, with_app # noqa
|
from hscommon.testutil import TestApp as TestAppBase, CallLogger, eq_, with_app # noqa
|
||||||
from hscommon.path import Path
|
from hscommon.path import Path
|
||||||
from hscommon.util import get_file_ext, format_size
|
from hscommon.util import get_file_ext, format_size
|
||||||
from hscommon.gui.column import Column
|
from hscommon.gui.column import Column
|
||||||
@ -41,6 +41,8 @@ class DupeGuruView:
|
|||||||
def ask_yes_no(self, prompt):
|
def ask_yes_no(self, prompt):
|
||||||
return True # always answer yes
|
return True # always answer yes
|
||||||
|
|
||||||
|
def create_results_window(self):
|
||||||
|
pass
|
||||||
|
|
||||||
class ResultTable(ResultTableBase):
|
class ResultTable(ResultTableBase):
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
@ -59,12 +61,16 @@ class DupeGuru(DupeGuruBase):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
DupeGuruBase.__init__(self, DupeGuruView())
|
DupeGuruBase.__init__(self, DupeGuruView())
|
||||||
self.appdata = '/tmp'
|
self.appdata = '/tmp'
|
||||||
|
self.result_table = self._create_result_table()
|
||||||
|
self.result_table.connect()
|
||||||
|
|
||||||
def _prioritization_categories(self):
|
def _prioritization_categories(self):
|
||||||
return prioritize.all_categories()
|
return prioritize.all_categories()
|
||||||
|
|
||||||
def _create_result_table(self):
|
def _create_result_table(self):
|
||||||
return ResultTable(self)
|
result = ResultTable(self)
|
||||||
|
result.view = CallLogger()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class NamedObject:
|
class NamedObject:
|
||||||
@ -141,7 +147,6 @@ class TestApp(TestAppBase):
|
|||||||
TestAppBase.__init__(self)
|
TestAppBase.__init__(self)
|
||||||
self.app = DupeGuru()
|
self.app = DupeGuru()
|
||||||
self.default_parent = self.app
|
self.default_parent = self.app
|
||||||
self.rtable = link_gui(self.app.result_table)
|
|
||||||
self.dtree = link_gui(self.app.directory_tree)
|
self.dtree = link_gui(self.app.directory_tree)
|
||||||
self.dpanel = link_gui(self.app.details_panel)
|
self.dpanel = link_gui(self.app.details_panel)
|
||||||
self.slabel = link_gui(self.app.stats_label)
|
self.slabel = link_gui(self.app.stats_label)
|
||||||
@ -155,6 +160,11 @@ class TestApp(TestAppBase):
|
|||||||
link_gui(self.app.progress_window.jobdesc_textfield)
|
link_gui(self.app.progress_window.jobdesc_textfield)
|
||||||
link_gui(self.app.progress_window.progressdesc_textfield)
|
link_gui(self.app.progress_window.progressdesc_textfield)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rtable(self):
|
||||||
|
# rtable is a property because its instance can be replaced during execution
|
||||||
|
return self.app.result_table
|
||||||
|
|
||||||
#--- Helpers
|
#--- Helpers
|
||||||
def select_pri_criterion(self, name):
|
def select_pri_criterion(self, name):
|
||||||
# Select a main prioritize criterion by name instead of by index. Makes tests more
|
# Select a main prioritize criterion by name instead of by index. Makes tests more
|
||||||
|
@ -9,7 +9,7 @@ from pytest import raises, skip
|
|||||||
from hscommon.testutil import eq_
|
from hscommon.testutil import eq_
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ..block import avgdiff, getblocks2, NoBlocksError, DifferentBlockCountError
|
from ..pe.block import avgdiff, getblocks2, NoBlocksError, DifferentBlockCountError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
skip("Can't import the block module, probably hasn't been compiled.")
|
skip("Can't import the block module, probably hasn't been compiled.")
|
||||||
|
|
@ -10,7 +10,7 @@ from pytest import raises, skip
|
|||||||
from hscommon.testutil import eq_
|
from hscommon.testutil import eq_
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ..cache import Cache, colors_to_string, string_to_colors
|
from ..pe.cache import Cache, colors_to_string, string_to_colors
|
||||||
except ImportError:
|
except ImportError:
|
||||||
skip("Can't import the cache module, probably hasn't been compiled.")
|
skip("Can't import the cache module, probably hasn't been compiled.")
|
||||||
|
|
@ -12,6 +12,7 @@ from .. import fs
|
|||||||
from ..engine import getwords, Match
|
from ..engine import getwords, Match
|
||||||
from ..ignore import IgnoreList
|
from ..ignore import IgnoreList
|
||||||
from ..scanner import Scanner, ScanType
|
from ..scanner import Scanner, ScanType
|
||||||
|
from ..me.scanner import ScannerME
|
||||||
|
|
||||||
class NamedObject:
|
class NamedObject:
|
||||||
def __init__(self, name="foobar", size=1, path=None):
|
def __init__(self, name="foobar", size=1, path=None):
|
||||||
@ -528,3 +529,13 @@ def test_dont_count_ref_files_as_discarded(fake_fileexists):
|
|||||||
o2.is_ref = True
|
o2.is_ref = True
|
||||||
eq_(len(s.get_dupe_groups([o1, o2, o3])), 1)
|
eq_(len(s.get_dupe_groups([o1, o2, o3])), 1)
|
||||||
eq_(s.discarded_file_count, 0)
|
eq_(s.discarded_file_count, 0)
|
||||||
|
|
||||||
|
def test_priorize_me(fake_fileexists):
|
||||||
|
# in ScannerME, bitrate goes first (right after is_ref) in priorization
|
||||||
|
s = ScannerME()
|
||||||
|
o1, o2 = no('foo', path='p1'), no('foo', path='p2')
|
||||||
|
o1.bitrate = 1
|
||||||
|
o2.bitrate = 2
|
||||||
|
[group] = s.get_dupe_groups([o1, o2])
|
||||||
|
assert group.ref is o2
|
||||||
|
|
||||||
|
56
core/util.py
Normal file
56
core/util.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from hscommon.util import format_time_decimal
|
||||||
|
|
||||||
|
def format_timestamp(t, delta):
|
||||||
|
if delta:
|
||||||
|
return format_time_decimal(t)
|
||||||
|
else:
|
||||||
|
if t > 0:
|
||||||
|
return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(t))
|
||||||
|
else:
|
||||||
|
return '---'
|
||||||
|
|
||||||
|
def format_words(w):
|
||||||
|
def do_format(w):
|
||||||
|
if isinstance(w, list):
|
||||||
|
return '(%s)' % ', '.join(do_format(item) for item in w)
|
||||||
|
else:
|
||||||
|
return w.replace('\n', ' ')
|
||||||
|
|
||||||
|
return ', '.join(do_format(item) for item in w)
|
||||||
|
|
||||||
|
def format_perc(p):
|
||||||
|
return "%0.0f" % p
|
||||||
|
|
||||||
|
def format_dupe_count(c):
|
||||||
|
return str(c) if c else '---'
|
||||||
|
|
||||||
|
def cmp_value(dupe, attrname):
|
||||||
|
value = getattr(dupe, attrname, '')
|
||||||
|
return value.lower() if isinstance(value, str) else value
|
||||||
|
|
||||||
|
def fix_surrogate_encoding(s, encoding='utf-8'):
|
||||||
|
# ref #210. It's possible to end up with file paths that, while correct unicode strings, are
|
||||||
|
# decoded with the 'surrogateescape' option, which make the string unencodable to utf-8. We fix
|
||||||
|
# these strings here by trying to encode them and, if it fails, we do an encode/decode dance
|
||||||
|
# to remove the problematic characters. This dance is *lossy* but there's not much we can do
|
||||||
|
# because if we end up with this type of string, it means that we don't know the encoding of the
|
||||||
|
# underlying filesystem that brought them. Don't use this for strings you're going to re-use in
|
||||||
|
# fs-related functions because you're going to lose your path (it's going to change). Use this
|
||||||
|
# if you need to export the path somewhere else, outside of the unicode realm.
|
||||||
|
# See http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/
|
||||||
|
try:
|
||||||
|
s.encode(encoding)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
return s.encode(encoding, 'replace').decode(encoding)
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
__version__ = '6.8.1'
|
|
||||||
__appname__ = 'dupeGuru Music Edition'
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core.app import DupeGuru as DupeGuruBase
|
|
||||||
from . import prioritize
|
|
||||||
from . import __appname__
|
|
||||||
from . import scanner, fs
|
|
||||||
from .result_table import ResultTable
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
NAME = __appname__
|
|
||||||
METADATA_TO_READ = [
|
|
||||||
'size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist',
|
|
||||||
'album', 'genre', 'year', 'track', 'comment'
|
|
||||||
]
|
|
||||||
SCANNER_CLASS = scanner.ScannerME
|
|
||||||
|
|
||||||
def __init__(self, view):
|
|
||||||
DupeGuruBase.__init__(self, view)
|
|
||||||
self.fileclasses = [fs.MusicFile]
|
|
||||||
|
|
||||||
def _get_dupe_sort_key(self, dupe, get_group, key, delta):
|
|
||||||
if key == 'folder_path':
|
|
||||||
dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path)
|
|
||||||
return str(dupe_folder_path).lower()
|
|
||||||
return DupeGuruBase._get_dupe_sort_key(self, dupe, get_group, key, delta)
|
|
||||||
|
|
||||||
def _get_group_sort_key(self, group, key):
|
|
||||||
if key == 'folder_path':
|
|
||||||
dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path)
|
|
||||||
return str(dupe_folder_path).lower()
|
|
||||||
return DupeGuruBase._get_group_sort_key(self, group, key)
|
|
||||||
|
|
||||||
def _prioritization_categories(self):
|
|
||||||
return prioritize.all_categories()
|
|
||||||
|
|
||||||
def _create_result_table(self):
|
|
||||||
return ResultTable(self)
|
|
@ -1,26 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from hscommon.path import Path
|
|
||||||
|
|
||||||
from core.tests.scanner_test import no
|
|
||||||
from ..scanner import ScannerME
|
|
||||||
|
|
||||||
def pytest_funcarg__fake_fileexists(request):
|
|
||||||
# This is a hack to avoid invalidating all previous tests since the scanner started to test
|
|
||||||
# for file existence before doing the match grouping.
|
|
||||||
monkeypatch = request.getfuncargvalue('monkeypatch')
|
|
||||||
monkeypatch.setattr(Path, 'exists', lambda _: True)
|
|
||||||
|
|
||||||
def test_priorize_me(fake_fileexists):
|
|
||||||
# in ScannerME, bitrate goes first (right after is_ref) in priorization
|
|
||||||
s = ScannerME()
|
|
||||||
o1, o2 = no('foo', path='p1'), no('foo', path='p2')
|
|
||||||
o1.bitrate = 1
|
|
||||||
o2.bitrate = 2
|
|
||||||
[group] = s.get_dupe_groups([o1, o2])
|
|
||||||
assert group.ref is o2
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
|||||||
__version__ = '2.10.1'
|
|
||||||
__appname__ = 'dupeGuru Picture Edition'
|
|
@ -1,45 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
import os.path as op
|
|
||||||
|
|
||||||
from core.app import DupeGuru as DupeGuruBase, cmp_value
|
|
||||||
from .scanner import ScannerPE
|
|
||||||
from . import prioritize
|
|
||||||
from . import __appname__
|
|
||||||
from .photo import get_delta_dimensions
|
|
||||||
from .result_table import ResultTable
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
NAME = __appname__
|
|
||||||
METADATA_TO_READ = ['size', 'mtime', 'dimensions', 'exif_timestamp']
|
|
||||||
SCANNER_CLASS = ScannerPE
|
|
||||||
|
|
||||||
def __init__(self, view):
|
|
||||||
DupeGuruBase.__init__(self, view)
|
|
||||||
self.options['cache_path'] = op.join(self.appdata, 'cached_pictures.db')
|
|
||||||
|
|
||||||
def _get_dupe_sort_key(self, dupe, get_group, key, delta):
|
|
||||||
if key == 'folder_path':
|
|
||||||
dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path)
|
|
||||||
return str(dupe_folder_path).lower()
|
|
||||||
if delta and key == 'dimensions':
|
|
||||||
r = cmp_value(dupe, key)
|
|
||||||
ref_value = cmp_value(get_group().ref, key)
|
|
||||||
return get_delta_dimensions(r, ref_value)
|
|
||||||
return DupeGuruBase._get_dupe_sort_key(self, dupe, get_group, key, delta)
|
|
||||||
|
|
||||||
def _get_group_sort_key(self, group, key):
|
|
||||||
if key == 'folder_path':
|
|
||||||
dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path)
|
|
||||||
return str(dupe_folder_path).lower()
|
|
||||||
return DupeGuruBase._get_group_sort_key(self, group, key)
|
|
||||||
|
|
||||||
def _prioritization_categories(self):
|
|
||||||
return prioritize.all_categories()
|
|
||||||
|
|
||||||
def _create_result_table(self):
|
|
||||||
return ResultTable(self)
|
|
@ -1,3 +0,0 @@
|
|||||||
__version__ = '3.9.1'
|
|
||||||
__appname__ = 'dupeGuru'
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core.app import DupeGuru as DupeGuruBase
|
|
||||||
from core import prioritize
|
|
||||||
from . import __appname__, fs, scanner
|
|
||||||
from .result_table import ResultTable
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
NAME = __appname__
|
|
||||||
METADATA_TO_READ = ['size', 'mtime']
|
|
||||||
SCANNER_CLASS = scanner.ScannerSE
|
|
||||||
|
|
||||||
def __init__(self, view):
|
|
||||||
DupeGuruBase.__init__(self, view)
|
|
||||||
self.fileclasses = [fs.File]
|
|
||||||
self.folderclass = fs.Folder
|
|
||||||
|
|
||||||
def _prioritization_categories(self):
|
|
||||||
return prioritize.all_categories()
|
|
||||||
|
|
||||||
def _create_result_table(self):
|
|
||||||
return ResultTable(self)
|
|
||||||
|
|
2
hscommon
2
hscommon
@ -1 +1 @@
|
|||||||
Subproject commit ea634cefdf78ae9e4c7470e571fce859760f6f38
|
Subproject commit 316af1bca53915f99b9bb874064cba6bee881cc1
|
188
package.py
188
package.py
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -13,13 +11,12 @@ import shutil
|
|||||||
import json
|
import json
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
import platform
|
import platform
|
||||||
import glob
|
|
||||||
|
|
||||||
from hscommon.plat import ISWINDOWS, ISLINUX
|
from hscommon.plat import ISOSX
|
||||||
from hscommon.build import (
|
from hscommon.build import (
|
||||||
add_to_pythonpath, print_and_do, copy_packages, build_debian_changelog,
|
print_and_do, copy_packages, build_debian_changelog,
|
||||||
copy_qt_plugins, get_module_version, filereplace, copy, setup_package_argparser,
|
get_module_version, filereplace, copy, setup_package_argparser,
|
||||||
package_cocoa_app_in_dmg, copy_all, find_in_path
|
package_cocoa_app_in_dmg, copy_all
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@ -27,90 +24,10 @@ def parse_args():
|
|||||||
setup_package_argparser(parser)
|
setup_package_argparser(parser)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
def package_cocoa(edition, args):
|
def package_cocoa(args):
|
||||||
app_path = {
|
app_path = 'build/dupeGuru.app'
|
||||||
'se': 'build/dupeGuru.app',
|
|
||||||
'me': 'build/dupeGuru ME.app',
|
|
||||||
'pe': 'build/dupeGuru PE.app',
|
|
||||||
}[edition]
|
|
||||||
package_cocoa_app_in_dmg(app_path, '.', args)
|
package_cocoa_app_in_dmg(app_path, '.', args)
|
||||||
|
|
||||||
def package_windows(edition, dev):
|
|
||||||
if not ISWINDOWS:
|
|
||||||
print("Qt packaging only works under Windows.")
|
|
||||||
return
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
|
||||||
add_to_pythonpath('.')
|
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
|
||||||
distdir = 'dist'
|
|
||||||
|
|
||||||
if op.exists(distdir):
|
|
||||||
shutil.rmtree(distdir)
|
|
||||||
|
|
||||||
if not dev:
|
|
||||||
# Copy qt plugins
|
|
||||||
plugin_dest = distdir
|
|
||||||
plugin_names = ['accessible', 'codecs', 'iconengines', 'imageformats']
|
|
||||||
copy_qt_plugins(plugin_names, plugin_dest)
|
|
||||||
|
|
||||||
# Since v4.2.3, cx_freeze started to falsely include tkinter in the package. We exclude it
|
|
||||||
# explicitly because of that.
|
|
||||||
options = {
|
|
||||||
'build_exe': {
|
|
||||||
'includes': 'atexit',
|
|
||||||
'excludes': ['tkinter'],
|
|
||||||
'bin_excludes': ['icudt51', 'icuin51.dll', 'icuuc51.dll'],
|
|
||||||
'icon': 'images\\dg{0}_logo.ico'.format(edition),
|
|
||||||
'include_msvcr': True,
|
|
||||||
},
|
|
||||||
'install_exe': {
|
|
||||||
'install_dir': 'dist',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executables = [
|
|
||||||
Executable(
|
|
||||||
'run.py',
|
|
||||||
base='Win32GUI',
|
|
||||||
targetDir=distdir,
|
|
||||||
targetName={'se': 'dupeGuru', 'me': 'dupeGuru ME', 'pe': 'dupeGuru PE'}[edition] + '.exe',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
setup(
|
|
||||||
script_args=['install'],
|
|
||||||
options=options,
|
|
||||||
executables=executables
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Removing useless files")
|
|
||||||
# Debug info that cx_freeze brings in.
|
|
||||||
for fn in glob.glob(op.join(distdir, '*', '*.pdb')):
|
|
||||||
os.remove(fn)
|
|
||||||
print("Copying forgotten DLLs")
|
|
||||||
qtlibpath = QLibraryInfo.location(QLibraryInfo.LibrariesPath)
|
|
||||||
shutil.copy(op.join(qtlibpath, 'libEGL.dll'), distdir)
|
|
||||||
shutil.copy(find_in_path('msvcp110.dll'), distdir)
|
|
||||||
print("Copying the rest")
|
|
||||||
help_path = op.join('build', 'help')
|
|
||||||
print("Copying {} to dist\\help".format(help_path))
|
|
||||||
shutil.copytree(help_path, op.join(distdir, 'help'))
|
|
||||||
locale_path = op.join('build', 'locale')
|
|
||||||
print("Copying {} to dist\\locale".format(locale_path))
|
|
||||||
shutil.copytree(locale_path, op.join(distdir, 'locale'))
|
|
||||||
|
|
||||||
# AdvancedInstaller.com has to be in your PATH
|
|
||||||
# this is so we don'a have to re-commit installer.aip at every version change
|
|
||||||
installer_file = 'installer.aip'
|
|
||||||
installer_path = op.join('qt', edition, installer_file)
|
|
||||||
shutil.copy(installer_path, 'installer_tmp.aip')
|
|
||||||
print_and_do('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % app_version)
|
|
||||||
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
|
|
||||||
os.remove('installer_tmp.aip')
|
|
||||||
if op.exists('installer_tmp.back.aip'):
|
|
||||||
os.remove('installer_tmp.back.aip')
|
|
||||||
|
|
||||||
def copy_files_to_package(destpath, packages, with_so):
|
def copy_files_to_package(destpath, packages, with_so):
|
||||||
# when with_so is true, we keep .so files in the package, and otherwise, we don't. We need this
|
# when with_so is true, we keep .so files in the package, and otherwise, we don't. We need this
|
||||||
# flag because when building debian src pkg, we *don't* want .so files (they're compiled later)
|
# flag because when building debian src pkg, we *don't* want .so files (they're compiled later)
|
||||||
@ -126,71 +43,68 @@ def copy_files_to_package(destpath, packages, with_so):
|
|||||||
shutil.copytree(op.join('build', 'locale'), op.join(destpath, 'locale'))
|
shutil.copytree(op.join('build', 'locale'), op.join(destpath, 'locale'))
|
||||||
compileall.compile_dir(destpath)
|
compileall.compile_dir(destpath)
|
||||||
|
|
||||||
def package_debian_distribution(edition, distribution):
|
def package_debian_distribution(distribution):
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
app_version = get_module_version('core')
|
||||||
version = '{}~{}'.format(app_version, distribution)
|
version = '{}~{}'.format(app_version, distribution)
|
||||||
ed = lambda s: s.format(edition)
|
destpath = op.join('build', 'dupeguru-{}'.format(version))
|
||||||
destpath = op.join('build', 'dupeguru-{0}-{1}'.format(edition, version))
|
|
||||||
srcpath = op.join(destpath, 'src')
|
srcpath = op.join(destpath, 'src')
|
||||||
packages = ['hscommon', 'core', ed('core_{0}'), 'qtlib', 'qt', 'send2trash']
|
packages = [
|
||||||
if edition == 'me':
|
'hscommon', 'core', 'qtlib', 'qt', 'send2trash', 'hsaudiotag'
|
||||||
packages.append('hsaudiotag')
|
]
|
||||||
copy_files_to_package(srcpath, packages, with_so=False)
|
copy_files_to_package(srcpath, packages, with_so=False)
|
||||||
if edition == 'pe':
|
os.mkdir(op.join(destpath, 'modules'))
|
||||||
os.mkdir(op.join(destpath, 'modules'))
|
copy_all(op.join('core', 'pe', 'modules', '*.*'), op.join(destpath, 'modules'))
|
||||||
copy_all(op.join('core_pe', 'modules', '*.*'), op.join(destpath, 'modules'))
|
copy(op.join('qt', 'pe', 'modules', 'block.c'), op.join(destpath, 'modules', 'block_qt.c'))
|
||||||
copy(op.join('qt', 'pe', 'modules', 'block.c'), op.join(destpath, 'modules', 'block_qt.c'))
|
copy(op.join('pkg', 'debian', 'build_pe_modules.py'), op.join(destpath, 'build_pe_modules.py'))
|
||||||
copy(op.join('pkg', 'debian', 'build_pe_modules.py'), op.join(destpath, 'build_pe_modules.py'))
|
|
||||||
debdest = op.join(destpath, 'debian')
|
debdest = op.join(destpath, 'debian')
|
||||||
debskel = op.join('pkg', 'debian')
|
debskel = op.join('pkg', 'debian')
|
||||||
os.makedirs(debdest)
|
os.makedirs(debdest)
|
||||||
debopts = json.load(open(op.join(debskel, ed('{}.json'))))
|
debopts = json.load(open(op.join(debskel, 'se.json')))
|
||||||
for fn in ['compat', 'copyright', 'dirs', 'rules']:
|
for fn in ['compat', 'copyright', 'dirs', 'rules']:
|
||||||
copy(op.join(debskel, fn), op.join(debdest, fn))
|
copy(op.join(debskel, fn), op.join(debdest, fn))
|
||||||
filereplace(op.join(debskel, 'control'), op.join(debdest, 'control'), **debopts)
|
filereplace(op.join(debskel, 'control'), op.join(debdest, 'control'), **debopts)
|
||||||
filereplace(op.join(debskel, 'Makefile'), op.join(destpath, 'Makefile'), **debopts)
|
filereplace(op.join(debskel, 'Makefile'), op.join(destpath, 'Makefile'), **debopts)
|
||||||
filereplace(op.join(debskel, 'dupeguru.desktop'), op.join(debdest, ed('dupeguru_{}.desktop')), **debopts)
|
filereplace(op.join(debskel, 'dupeguru.desktop'), op.join(debdest, 'dupeguru.desktop'), **debopts)
|
||||||
changelogpath = op.join('help', ed('changelog_{}'))
|
changelogpath = op.join('help', 'changelog_se')
|
||||||
changelog_dest = op.join(debdest, 'changelog')
|
changelog_dest = op.join(debdest, 'changelog')
|
||||||
project_name = debopts['pkgname']
|
project_name = debopts['pkgname']
|
||||||
from_version = {'se': '2.9.2', 'me': '5.7.2', 'pe': '1.8.5'}[edition]
|
from_version = '2.9.2'
|
||||||
build_debian_changelog(
|
build_debian_changelog(
|
||||||
changelogpath, changelog_dest, project_name, from_version=from_version,
|
changelogpath, changelog_dest, project_name, from_version=from_version,
|
||||||
distribution=distribution
|
distribution=distribution
|
||||||
)
|
)
|
||||||
shutil.copy(op.join('images', ed('dg{0}_logo_128.png')), srcpath)
|
shutil.copy(op.join('images', 'dgse_logo_128.png'), srcpath)
|
||||||
os.chdir(destpath)
|
os.chdir(destpath)
|
||||||
cmd = "dpkg-buildpackage -S"
|
cmd = "dpkg-buildpackage -S"
|
||||||
os.system(cmd)
|
os.system(cmd)
|
||||||
os.chdir('../..')
|
os.chdir('../..')
|
||||||
|
|
||||||
def package_debian(edition):
|
def package_debian():
|
||||||
print("Packaging for Ubuntu")
|
print("Packaging for Ubuntu")
|
||||||
for distribution in ['trusty', 'utopic']:
|
for distribution in ['trusty', 'utopic']:
|
||||||
package_debian_distribution(edition, distribution)
|
package_debian_distribution(distribution)
|
||||||
|
|
||||||
def package_arch(edition):
|
def package_arch():
|
||||||
# For now, package_arch() will only copy the source files into build/. It copies less packages
|
# For now, package_arch() will only copy the source files into build/. It copies less packages
|
||||||
# than package_debian because there are more python packages available in Arch (so we don't
|
# than package_debian because there are more python packages available in Arch (so we don't
|
||||||
# need to include them).
|
# need to include them).
|
||||||
print("Packaging for Arch")
|
print("Packaging for Arch")
|
||||||
ed = lambda s: s.format(edition)
|
srcpath = op.join('build', 'dupeguru-arch')
|
||||||
srcpath = op.join('build', ed('dupeguru-{}-arch'))
|
packages = [
|
||||||
packages = ['hscommon', 'core', ed('core_{0}'), 'qtlib', 'qt', 'send2trash']
|
'hscommon', 'core', 'qtlib', 'qt', 'send2trash', 'hsaudiotag',
|
||||||
if edition == 'me':
|
]
|
||||||
packages.append('hsaudiotag')
|
|
||||||
copy_files_to_package(srcpath, packages, with_so=True)
|
copy_files_to_package(srcpath, packages, with_so=True)
|
||||||
shutil.copy(op.join('images', ed('dg{}_logo_128.png')), srcpath)
|
shutil.copy(op.join('images', 'dgse_logo_128.png'), srcpath)
|
||||||
debopts = json.load(open(op.join('pkg', 'arch', ed('{}.json'))))
|
debopts = json.load(open(op.join('pkg', 'arch', 'se.json')))
|
||||||
filereplace(op.join('pkg', 'arch', 'dupeguru.desktop'), op.join(srcpath, ed('dupeguru-{}.desktop')), **debopts)
|
filereplace(op.join('pkg', 'arch', 'dupeguru.desktop'), op.join(srcpath, 'dupeguru.desktop'), **debopts)
|
||||||
|
|
||||||
def package_source_tgz(edition):
|
def package_source_tgz():
|
||||||
if not op.exists('deps'):
|
if not op.exists('deps'):
|
||||||
print("Downloading PyPI dependencies")
|
print("Downloading PyPI dependencies")
|
||||||
print_and_do('./download_deps.sh')
|
print_and_do('./download_deps.sh')
|
||||||
print("Creating git archive")
|
print("Creating git archive")
|
||||||
app_version = get_module_version('core_{}'.format(edition))
|
app_version = get_module_version('core')
|
||||||
name = 'dupeguru-{}-src-{}.tar'.format(edition, app_version)
|
name = 'dupeguru-src-{}.tar'.format(app_version)
|
||||||
dest = op.join('build', name)
|
dest = op.join('build', name)
|
||||||
print_and_do('git archive -o {} HEAD'.format(dest))
|
print_and_do('git archive -o {} HEAD'.format(dest))
|
||||||
print("Adding dependencies and wrapping up")
|
print("Adding dependencies and wrapping up")
|
||||||
@ -199,31 +113,23 @@ def package_source_tgz(edition):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
conf = json.load(open('conf.json'))
|
ui = 'cocoa' if ISOSX else 'qt'
|
||||||
edition = conf['edition']
|
|
||||||
ui = conf['ui']
|
|
||||||
dev = conf['dev']
|
|
||||||
if args.src_pkg:
|
if args.src_pkg:
|
||||||
print("Creating source package for dupeGuru {}".format(edition.upper()))
|
print("Creating source package for dupeGuru")
|
||||||
package_source_tgz(edition)
|
package_source_tgz()
|
||||||
return
|
return
|
||||||
print("Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
print("Packaging dupeGuru with UI {}".format(ui))
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
package_cocoa(edition, args)
|
package_cocoa(args)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
if ISWINDOWS:
|
if not args.arch_pkg:
|
||||||
package_windows(edition, dev)
|
distname, _, _ = platform.dist()
|
||||||
elif ISLINUX:
|
|
||||||
if not args.arch_pkg:
|
|
||||||
distname, _, _ = platform.dist()
|
|
||||||
else:
|
|
||||||
distname = 'arch'
|
|
||||||
if distname == 'arch':
|
|
||||||
package_arch(edition)
|
|
||||||
else:
|
|
||||||
package_debian(edition)
|
|
||||||
else:
|
else:
|
||||||
print("Qt packaging only works under Windows or Linux.")
|
distname = 'arch'
|
||||||
|
if distname == 'arch':
|
||||||
|
package_arch()
|
||||||
|
else:
|
||||||
|
package_debian()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-04-25
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -21,49 +19,51 @@ from qtlib.recent import Recent
|
|||||||
from qtlib.util import createActions
|
from qtlib.util import createActions
|
||||||
from qtlib.progress_window import ProgressWindow
|
from qtlib.progress_window import ProgressWindow
|
||||||
|
|
||||||
|
from core.app import AppMode, DupeGuru as DupeGuruModel
|
||||||
|
import core.pe.photo
|
||||||
from . import platform
|
from . import platform
|
||||||
|
from .preferences import Preferences
|
||||||
from .result_window import ResultWindow
|
from .result_window import ResultWindow
|
||||||
from .directories_dialog import DirectoriesDialog
|
from .directories_dialog import DirectoriesDialog
|
||||||
from .problem_dialog import ProblemDialog
|
from .problem_dialog import ProblemDialog
|
||||||
from .ignore_list_dialog import IgnoreListDialog
|
from .ignore_list_dialog import IgnoreListDialog
|
||||||
from .deletion_options import DeletionOptions
|
from .deletion_options import DeletionOptions
|
||||||
|
from .se.details_dialog import DetailsDialog as DetailsDialogStandard
|
||||||
|
from .me.details_dialog import DetailsDialog as DetailsDialogMusic
|
||||||
|
from .pe.details_dialog import DetailsDialog as DetailsDialogPicture
|
||||||
|
from .se.preferences_dialog import PreferencesDialog as PreferencesDialogStandard
|
||||||
|
from .me.preferences_dialog import PreferencesDialog as PreferencesDialogMusic
|
||||||
|
from .pe.preferences_dialog import PreferencesDialog as PreferencesDialogPicture
|
||||||
|
from .pe.photo import File as PlatSpecificPhoto
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
class DupeGuru(QObject):
|
class DupeGuru(QObject):
|
||||||
MODELCLASS = None
|
LOGO_NAME = 'logo_se'
|
||||||
LOGO_NAME = '<replace this>'
|
NAME = 'dupeGuru'
|
||||||
NAME = '<replace this>'
|
|
||||||
|
|
||||||
DETAILS_DIALOG_CLASS = None
|
|
||||||
RESULT_WINDOW_CLASS = ResultWindow
|
|
||||||
RESULT_MODEL_CLASS = None
|
|
||||||
PREFERENCES_CLASS = None
|
|
||||||
PREFERENCES_DIALOG_CLASS = None
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.prefs = self.PREFERENCES_CLASS()
|
self.prefs = Preferences()
|
||||||
self.prefs.load()
|
self.prefs.load()
|
||||||
self.model = self.MODELCLASS(view=self)
|
self.model = DupeGuruModel(view=self)
|
||||||
self._setup()
|
self._setup()
|
||||||
self.prefsChanged.emit(self.prefs)
|
|
||||||
|
|
||||||
#--- Private
|
#--- Private
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
|
core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto
|
||||||
self._setupActions()
|
self._setupActions()
|
||||||
self._update_options()
|
self._update_options()
|
||||||
self.recentResults = Recent(self, 'recentResults')
|
self.recentResults = Recent(self, 'recentResults')
|
||||||
self.recentResults.mustOpenItem.connect(self.model.load_from)
|
self.recentResults.mustOpenItem.connect(self.model.load_from)
|
||||||
|
self.resultWindow = None
|
||||||
|
self.details_dialog = None
|
||||||
self.directories_dialog = DirectoriesDialog(self)
|
self.directories_dialog = DirectoriesDialog(self)
|
||||||
self.resultWindow = self.RESULT_WINDOW_CLASS(self.directories_dialog, self)
|
self.progress_window = ProgressWindow(self.directories_dialog, self.model.progress_window)
|
||||||
self.progress_window = ProgressWindow(self.resultWindow, self.model.progress_window)
|
self.problemDialog = ProblemDialog(parent=self.directories_dialog, model=self.model.problem_dialog)
|
||||||
self.details_dialog = self.DETAILS_DIALOG_CLASS(self.resultWindow, self)
|
self.ignoreListDialog = IgnoreListDialog(parent=self.directories_dialog, model=self.model.ignore_list_dialog)
|
||||||
self.problemDialog = ProblemDialog(parent=self.resultWindow, model=self.model.problem_dialog)
|
self.deletionOptions = DeletionOptions(parent=self.directories_dialog, model=self.model.deletion_options)
|
||||||
self.ignoreListDialog = IgnoreListDialog(parent=self.resultWindow, model=self.model.ignore_list_dialog)
|
self.about_box = AboutBox(self.directories_dialog, self)
|
||||||
self.deletionOptions = DeletionOptions(parent=self.resultWindow, model=self.model.deletion_options)
|
|
||||||
self.preferences_dialog = self.PREFERENCES_DIALOG_CLASS(self.resultWindow, self)
|
|
||||||
self.about_box = AboutBox(self.resultWindow, self)
|
|
||||||
|
|
||||||
self.directories_dialog.show()
|
self.directories_dialog.show()
|
||||||
self.model.load()
|
self.model.load()
|
||||||
@ -82,6 +82,7 @@ class DupeGuru(QObject):
|
|||||||
('actionQuit', 'Ctrl+Q', '', tr("Quit"), self.quitTriggered),
|
('actionQuit', 'Ctrl+Q', '', tr("Quit"), self.quitTriggered),
|
||||||
('actionPreferences', 'Ctrl+P', '', tr("Options"), self.preferencesTriggered),
|
('actionPreferences', 'Ctrl+P', '', tr("Options"), self.preferencesTriggered),
|
||||||
('actionIgnoreList', '', '', tr("Ignore List"), self.ignoreListTriggered),
|
('actionIgnoreList', '', '', tr("Ignore List"), self.ignoreListTriggered),
|
||||||
|
('actionClearPictureCache', 'Ctrl+Shift+P', '', tr("Clear Picture Cache"), self.clearPictureCacheTriggered),
|
||||||
('actionShowHelp', 'F1', '', tr("dupeGuru Help"), self.showHelpTriggered),
|
('actionShowHelp', 'F1', '', tr("dupeGuru Help"), self.showHelpTriggered),
|
||||||
('actionAbout', '', '', tr("About dupeGuru"), self.showAboutBoxTriggered),
|
('actionAbout', '', '', tr("About dupeGuru"), self.showAboutBoxTriggered),
|
||||||
('actionOpenDebugLog', '', '', tr("Open Debug Log"), self.openDebugLogTriggered),
|
('actionOpenDebugLog', '', '', tr("Open Debug Log"), self.openDebugLogTriggered),
|
||||||
@ -94,6 +95,44 @@ class DupeGuru(QObject):
|
|||||||
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
|
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
|
||||||
self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
|
self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
|
||||||
self.model.options['copymove_dest_type'] = self.prefs.destination_type
|
self.model.options['copymove_dest_type'] = self.prefs.destination_type
|
||||||
|
self.model.options['scan_type'] = self.prefs.get_scan_type(self.model.app_mode)
|
||||||
|
self.model.options['min_match_percentage'] = self.prefs.filter_hardness
|
||||||
|
self.model.options['word_weighting'] = self.prefs.word_weighting
|
||||||
|
self.model.options['match_similar_words'] = self.prefs.match_similar
|
||||||
|
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
|
||||||
|
self.model.options['size_threshold'] = threshold * 1024 # threshold is in KB. the scanner wants bytes
|
||||||
|
scanned_tags = set()
|
||||||
|
if self.prefs.scan_tag_track:
|
||||||
|
scanned_tags.add('track')
|
||||||
|
if self.prefs.scan_tag_artist:
|
||||||
|
scanned_tags.add('artist')
|
||||||
|
if self.prefs.scan_tag_album:
|
||||||
|
scanned_tags.add('album')
|
||||||
|
if self.prefs.scan_tag_title:
|
||||||
|
scanned_tags.add('title')
|
||||||
|
if self.prefs.scan_tag_genre:
|
||||||
|
scanned_tags.add('genre')
|
||||||
|
if self.prefs.scan_tag_year:
|
||||||
|
scanned_tags.add('year')
|
||||||
|
self.model.options['scanned_tags'] = scanned_tags
|
||||||
|
self.model.options['match_scaled'] = self.prefs.match_scaled
|
||||||
|
|
||||||
|
#--- Private
|
||||||
|
def _get_details_dialog_class(self):
|
||||||
|
if self.model.app_mode == AppMode.Picture:
|
||||||
|
return DetailsDialogPicture
|
||||||
|
elif self.model.app_mode == AppMode.Music:
|
||||||
|
return DetailsDialogMusic
|
||||||
|
else:
|
||||||
|
return DetailsDialogStandard
|
||||||
|
|
||||||
|
def _get_preferences_dialog_class(self):
|
||||||
|
if self.model.app_mode == AppMode.Picture:
|
||||||
|
return PreferencesDialogPicture
|
||||||
|
elif self.model.app_mode == AppMode.Music:
|
||||||
|
return PreferencesDialogMusic
|
||||||
|
else:
|
||||||
|
return PreferencesDialogStandard
|
||||||
|
|
||||||
#--- Public
|
#--- Public
|
||||||
def add_selected_to_ignore_list(self):
|
def add_selected_to_ignore_list(self):
|
||||||
@ -112,14 +151,15 @@ class DupeGuru(QObject):
|
|||||||
self.model.invoke_custom_command()
|
self.model.invoke_custom_command()
|
||||||
|
|
||||||
def show_details(self):
|
def show_details(self):
|
||||||
self.details_dialog.show()
|
if self.details_dialog is not None:
|
||||||
|
self.details_dialog.show()
|
||||||
|
|
||||||
def showResultsWindow(self):
|
def showResultsWindow(self):
|
||||||
self.resultWindow.show()
|
if self.resultWindow is not None:
|
||||||
|
self.resultWindow.show()
|
||||||
|
|
||||||
#--- Signals
|
#--- Signals
|
||||||
willSavePrefs = pyqtSignal()
|
willSavePrefs = pyqtSignal()
|
||||||
prefsChanged = pyqtSignal(object)
|
|
||||||
|
|
||||||
#--- Events
|
#--- Events
|
||||||
def finishedLaunching(self):
|
def finishedLaunching(self):
|
||||||
@ -135,6 +175,14 @@ class DupeGuru(QObject):
|
|||||||
self.prefs.save()
|
self.prefs.save()
|
||||||
self.model.save()
|
self.model.save()
|
||||||
|
|
||||||
|
def clearPictureCacheTriggered(self):
|
||||||
|
title = tr("Clear Picture Cache")
|
||||||
|
msg = tr("Do you really want to remove all your cached picture analysis?")
|
||||||
|
if self.confirm(title, msg, QMessageBox.No):
|
||||||
|
self.model.clear_picture_cache()
|
||||||
|
active = QApplication.activeWindow()
|
||||||
|
QMessageBox.information(active, title, tr("Picture cache cleared."))
|
||||||
|
|
||||||
def ignoreListTriggered(self):
|
def ignoreListTriggered(self):
|
||||||
self.model.ignore_list_dialog.show()
|
self.model.ignore_list_dialog.show()
|
||||||
|
|
||||||
@ -143,13 +191,14 @@ class DupeGuru(QObject):
|
|||||||
desktop.open_path(debugLogPath)
|
desktop.open_path(debugLogPath)
|
||||||
|
|
||||||
def preferencesTriggered(self):
|
def preferencesTriggered(self):
|
||||||
self.preferences_dialog.load()
|
preferences_dialog = self._get_preferences_dialog_class()(self.directories_dialog, self)
|
||||||
result = self.preferences_dialog.exec()
|
preferences_dialog.load()
|
||||||
|
result = preferences_dialog.exec()
|
||||||
if result == QDialog.Accepted:
|
if result == QDialog.Accepted:
|
||||||
self.preferences_dialog.save()
|
preferences_dialog.save()
|
||||||
self.prefs.save()
|
self.prefs.save()
|
||||||
self._update_options()
|
self._update_options()
|
||||||
self.prefsChanged.emit(self.prefs)
|
preferences_dialog.setParent(None)
|
||||||
|
|
||||||
def quitTriggered(self):
|
def quitTriggered(self):
|
||||||
self.directories_dialog.close()
|
self.directories_dialog.close()
|
||||||
@ -176,6 +225,18 @@ class DupeGuru(QObject):
|
|||||||
def ask_yes_no(self, prompt):
|
def ask_yes_no(self, prompt):
|
||||||
return self.confirm('', prompt)
|
return self.confirm('', prompt)
|
||||||
|
|
||||||
|
def create_results_window(self):
|
||||||
|
"""Creates resultWindow and details_dialog depending on the selected ``app_mode``.
|
||||||
|
"""
|
||||||
|
if self.details_dialog is not None:
|
||||||
|
self.details_dialog.close()
|
||||||
|
self.details_dialog.setParent(None)
|
||||||
|
if self.resultWindow is not None:
|
||||||
|
self.resultWindow.close()
|
||||||
|
self.resultWindow.setParent(None)
|
||||||
|
self.resultWindow = ResultWindow(self.directories_dialog, self)
|
||||||
|
self.details_dialog = self._get_details_dialog_class()(self.resultWindow, self)
|
||||||
|
|
||||||
def show_results_window(self):
|
def show_results_window(self):
|
||||||
self.showResultsWindow()
|
self.showResultsWindow()
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
# cxfreeze has some problems detecting all dependencies.
|
|
||||||
# This modules explicitly import those problematic modules.
|
|
||||||
# flake8: noqa
|
|
||||||
|
|
||||||
import xml.etree.ElementPath
|
|
||||||
import gzip
|
|
||||||
|
|
||||||
import os
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE RCC><RCC version="1.0">
|
|
||||||
<qresource>
|
|
||||||
<file alias="logo_pe">../../images/dgpe_logo_32.png</file>
|
|
||||||
<file alias="logo_pe_big">../../images/dgpe_logo_128.png</file>
|
|
||||||
<file alias="logo_me">../../images/dgme_logo_32.png</file>
|
|
||||||
<file alias="logo_me_big">../../images/dgme_logo_128.png</file>
|
|
||||||
<file alias="logo_se">../../images/dgse_logo_32.png</file>
|
|
||||||
<file alias="logo_se_big">../../images/dgse_logo_128.png</file>
|
|
||||||
<file alias="plus">../../images/plus_8.png</file>
|
|
||||||
<file alias="minus">../../images/minus_8.png</file>
|
|
||||||
<file alias="search_clear_13">../../qtlib/images/search_clear_13.png</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
9
qt/dg.qrc
Normal file
9
qt/dg.qrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource>
|
||||||
|
<file alias="logo_se">../images/dgse_logo_32.png</file>
|
||||||
|
<file alias="logo_se_big">../images/dgse_logo_128.png</file>
|
||||||
|
<file alias="plus">../images/plus_8.png</file>
|
||||||
|
<file alias="minus">../images/minus_8.png</file>
|
||||||
|
<file alias="search_clear_13">../qtlib/images/search_clear_13.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
@ -13,6 +13,8 @@ from PyQt5.QtWidgets import (
|
|||||||
from PyQt5.QtGui import QPixmap, QIcon
|
from PyQt5.QtGui import QPixmap, QIcon
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
|
from core.app import AppMode
|
||||||
|
from qtlib.radio_box import RadioBox
|
||||||
from qtlib.recent import Recent
|
from qtlib.recent import Recent
|
||||||
from qtlib.util import moveToScreenCenter, createActions
|
from qtlib.util import moveToScreenCenter, createActions
|
||||||
|
|
||||||
@ -28,9 +30,7 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
|
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
|
||||||
self.recentFolders = Recent(self.app, 'recentFolders')
|
self.recentFolders = Recent(self.app, 'recentFolders')
|
||||||
self._setupUi()
|
self._setupUi()
|
||||||
SCAN_TYPE_ORDER = [so.scan_type for so in self.app.model.SCANNER_CLASS.get_scan_options()]
|
self._updateScanTypeList()
|
||||||
scan_type_index = SCAN_TYPE_ORDER.index(self.app.prefs.scan_type)
|
|
||||||
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
|
|
||||||
self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
|
self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
|
||||||
self.directoriesDelegate = DirectoriesDelegate()
|
self.directoriesDelegate = DirectoriesDelegate()
|
||||||
self.treeView.setItemDelegate(self.directoriesDelegate)
|
self.treeView.setItemDelegate(self.directoriesDelegate)
|
||||||
@ -41,11 +41,12 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self._updateAddButton()
|
self._updateAddButton()
|
||||||
self._updateRemoveButton()
|
self._updateRemoveButton()
|
||||||
self._updateLoadResultsButton()
|
self._updateLoadResultsButton()
|
||||||
|
self._updateActionsState()
|
||||||
self._setupBindings()
|
self._setupBindings()
|
||||||
|
|
||||||
def _setupBindings(self):
|
def _setupBindings(self):
|
||||||
|
self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected)
|
||||||
self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger)
|
self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger)
|
||||||
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
|
|
||||||
self.scanButton.clicked.connect(self.scanButtonClicked)
|
self.scanButton.clicked.connect(self.scanButtonClicked)
|
||||||
self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
|
self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
|
||||||
self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
|
self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
|
||||||
@ -82,6 +83,8 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self.menuFile.addAction(self.actionLoadResults)
|
self.menuFile.addAction(self.actionLoadResults)
|
||||||
self.menuFile.addAction(self.menuLoadRecent.menuAction())
|
self.menuFile.addAction(self.menuLoadRecent.menuAction())
|
||||||
self.menuFile.addSeparator()
|
self.menuFile.addSeparator()
|
||||||
|
self.menuFile.addAction(self.app.actionClearPictureCache)
|
||||||
|
self.menuFile.addSeparator()
|
||||||
self.menuFile.addAction(self.app.actionQuit)
|
self.menuFile.addAction(self.app.actionQuit)
|
||||||
self.menuView.addAction(self.app.actionPreferences)
|
self.menuView.addAction(self.app.actionPreferences)
|
||||||
self.menuView.addAction(self.actionShowResultsWindow)
|
self.menuView.addAction(self.actionShowResultsWindow)
|
||||||
@ -123,6 +126,17 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self.treeView.setUniformRowHeights(True)
|
self.treeView.setUniformRowHeights(True)
|
||||||
self.verticalLayout.addWidget(self.treeView)
|
self.verticalLayout.addWidget(self.treeView)
|
||||||
hl = QHBoxLayout()
|
hl = QHBoxLayout()
|
||||||
|
label = QLabel(tr("Application Mode:"), self)
|
||||||
|
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||||
|
hl.addWidget(label)
|
||||||
|
self.appModeRadioBox = RadioBox(
|
||||||
|
self,
|
||||||
|
items=[tr("Standard"), tr("Music"), tr("Picture")],
|
||||||
|
spread=False
|
||||||
|
)
|
||||||
|
hl.addWidget(self.appModeRadioBox)
|
||||||
|
self.verticalLayout.addLayout(hl)
|
||||||
|
hl = QHBoxLayout()
|
||||||
hl.setAlignment(Qt.AlignLeft)
|
hl.setAlignment(Qt.AlignLeft)
|
||||||
label = QLabel(tr("Scan Type:"), self)
|
label = QLabel(tr("Scan Type:"), self)
|
||||||
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||||
@ -130,10 +144,8 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self.scanTypeComboBox = QComboBox(self)
|
self.scanTypeComboBox = QComboBox(self)
|
||||||
self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
|
self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
|
||||||
self.scanTypeComboBox.setMaximumWidth(400)
|
self.scanTypeComboBox.setMaximumWidth(400)
|
||||||
for scan_option in self.app.model.SCANNER_CLASS.get_scan_options():
|
|
||||||
self.scanTypeComboBox.addItem(scan_option.label)
|
|
||||||
hl.addWidget(self.scanTypeComboBox)
|
hl.addWidget(self.scanTypeComboBox)
|
||||||
self.showPreferencesButton = QPushButton(tr("Options"), self.centralwidget)
|
self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget)
|
||||||
self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||||
hl.addWidget(self.showPreferencesButton)
|
hl.addWidget(self.showPreferencesButton)
|
||||||
self.verticalLayout.addLayout(hl)
|
self.verticalLayout.addLayout(hl)
|
||||||
@ -172,6 +184,9 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
header.setSectionResizeMode(1, QHeaderView.Fixed)
|
header.setSectionResizeMode(1, QHeaderView.Fixed)
|
||||||
header.resizeSection(1, 100)
|
header.resizeSection(1, 100)
|
||||||
|
|
||||||
|
def _updateActionsState(self):
|
||||||
|
self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None)
|
||||||
|
|
||||||
def _updateAddButton(self):
|
def _updateAddButton(self):
|
||||||
if self.recentFolders.isEmpty():
|
if self.recentFolders.isEmpty():
|
||||||
self.addFolderButton.setMenu(None)
|
self.addFolderButton.setMenu(None)
|
||||||
@ -191,6 +206,23 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self.loadResultsButton.setMenu(self.menuRecentResults)
|
self.loadResultsButton.setMenu(self.menuRecentResults)
|
||||||
|
|
||||||
|
def _updateScanTypeList(self):
|
||||||
|
try:
|
||||||
|
self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged)
|
||||||
|
except TypeError:
|
||||||
|
# Not connected, ignore
|
||||||
|
pass
|
||||||
|
self.scanTypeComboBox.clear()
|
||||||
|
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
|
||||||
|
for scan_option in scan_options:
|
||||||
|
self.scanTypeComboBox.addItem(scan_option.label)
|
||||||
|
SCAN_TYPE_ORDER = [so.scan_type for so in scan_options]
|
||||||
|
selected_scan_type = self.app.prefs.get_scan_type(self.app.model.app_mode)
|
||||||
|
scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type)
|
||||||
|
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
|
||||||
|
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
|
||||||
|
self.app._update_options()
|
||||||
|
|
||||||
#--- QWidget overrides
|
#--- QWidget overrides
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
event.accept()
|
event.accept()
|
||||||
@ -213,6 +245,16 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
self.app.model.add_directory(dirpath)
|
self.app.model.add_directory(dirpath)
|
||||||
self.recentFolders.insertItem(dirpath)
|
self.recentFolders.insertItem(dirpath)
|
||||||
|
|
||||||
|
def appModeButtonSelected(self, index):
|
||||||
|
if index == 2:
|
||||||
|
mode = AppMode.Picture
|
||||||
|
elif index == 1:
|
||||||
|
mode = AppMode.Music
|
||||||
|
else:
|
||||||
|
mode = AppMode.Standard
|
||||||
|
self.app.model.app_mode = mode
|
||||||
|
self._updateScanTypeList()
|
||||||
|
|
||||||
def appWillSavePrefs(self):
|
def appWillSavePrefs(self):
|
||||||
self.app.prefs.directoriesWindowRect = self.geometry()
|
self.app.prefs.directoriesWindowRect = self.geometry()
|
||||||
|
|
||||||
@ -241,7 +283,7 @@ class DirectoriesDialog(QMainWindow):
|
|||||||
|
|
||||||
def scanTypeChanged(self, index):
|
def scanTypeChanged(self, index):
|
||||||
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
|
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
|
||||||
self.app.prefs.scan_type = scan_options[index].scan_type
|
self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type)
|
||||||
self.app._update_options()
|
self.app._update_options()
|
||||||
|
|
||||||
def selectionChanged(self, selected, deselected):
|
def selectionChanged(self, selected, deselected):
|
47
qt/me/app.py
47
qt/me/app.py
@ -1,47 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core_me import __appname__
|
|
||||||
from core_me.app import DupeGuru as DupeGuruModel
|
|
||||||
|
|
||||||
from ..base.app import DupeGuru as DupeGuruBase
|
|
||||||
from .details_dialog import DetailsDialog
|
|
||||||
from .results_model import ResultsModel
|
|
||||||
from .preferences import Preferences
|
|
||||||
from .preferences_dialog import PreferencesDialog
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
MODELCLASS = DupeGuruModel
|
|
||||||
EDITION = 'me'
|
|
||||||
LOGO_NAME = 'logo_me'
|
|
||||||
NAME = __appname__
|
|
||||||
|
|
||||||
DETAILS_DIALOG_CLASS = DetailsDialog
|
|
||||||
RESULT_MODEL_CLASS = ResultsModel
|
|
||||||
PREFERENCES_CLASS = Preferences
|
|
||||||
PREFERENCES_DIALOG_CLASS = PreferencesDialog
|
|
||||||
|
|
||||||
def _update_options(self):
|
|
||||||
DupeGuruBase._update_options(self)
|
|
||||||
self.model.options['min_match_percentage'] = self.prefs.filter_hardness
|
|
||||||
self.model.options['scan_type'] = self.prefs.scan_type
|
|
||||||
self.model.options['word_weighting'] = self.prefs.word_weighting
|
|
||||||
self.model.options['match_similar_words'] = self.prefs.match_similar
|
|
||||||
scanned_tags = set()
|
|
||||||
if self.prefs.scan_tag_track:
|
|
||||||
scanned_tags.add('track')
|
|
||||||
if self.prefs.scan_tag_artist:
|
|
||||||
scanned_tags.add('artist')
|
|
||||||
if self.prefs.scan_tag_album:
|
|
||||||
scanned_tags.add('album')
|
|
||||||
if self.prefs.scan_tag_title:
|
|
||||||
scanned_tags.add('title')
|
|
||||||
if self.prefs.scan_tag_genre:
|
|
||||||
scanned_tags.add('genre')
|
|
||||||
if self.prefs.scan_tag_year:
|
|
||||||
scanned_tags.add('year')
|
|
||||||
self.model.options['scanned_tags'] = scanned_tags
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-04-27
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -10,8 +8,8 @@ from PyQt5.QtCore import QSize
|
|||||||
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView
|
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from ..base.details_dialog import DetailsDialog as DetailsDialogBase
|
from ..details_dialog import DetailsDialog as DetailsDialogBase
|
||||||
from ..base.details_table import DetailsTable
|
from ..details_table import DetailsTable
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<DOCUMENT Type="Advanced Installer" CreateVersion="4.7.2" version="8.0.2" Modules="professional" RootPath="." Language="en">
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
|
|
||||||
<ROW Property="ALLUSERS" Value="2"/>
|
|
||||||
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
|
|
||||||
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
|
|
||||||
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
|
|
||||||
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru_me/"/>
|
|
||||||
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru_me/"/>
|
|
||||||
<ROW Property="BannerBitmap" MultiBuildValue="DefaultBuild:banner_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="CTRLS" Value="2"/>
|
|
||||||
<ROW Property="DialogBitmap" MultiBuildValue="DefaultBuild:dialog_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductCode" Value="1033:{A7037226-389C-4704-81C3-E2CA17D11058} " Type="16"/>
|
|
||||||
<ROW Property="ProductLanguage" Value="1033"/>
|
|
||||||
<ROW Property="ProductName" Value="dupeGuru Music Edition" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductVersion" Value="5.6.0"/>
|
|
||||||
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
|
|
||||||
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
|
|
||||||
<ROW Property="UpgradeCode" Value="{E11BFC48-7639-44BD-BB5B-A6AC934BC12D}"/>
|
|
||||||
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsType9XDisplay" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNT64" MultiBuildValue="DefaultBuild:Windows XP x64, Windows XP x64 ServicePack 1, Windows XP x64 ServicePack 2, Windows Server 2003 x64, Windows Server 2003 x64 Service Pack 1, Windows Server 2003 x64 Service pack 2" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNT64Display" MultiBuildValue="DefaultBuild:Windows XP x64, Windows Server 2003 x64" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNTDisplay" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
|
|
||||||
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
|
|
||||||
<ROW Component="AIShRegAnswer" ComponentId="{7DB5C163-2978-436F-A47D-54F1C76F1D64}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer"/>
|
|
||||||
<ROW Component="CurrentVersion" ComponentId="{1EF88868-DCB1-4F7D-83F6-CC17C9F65740}" Directory_="APPDIR" Attributes="4" KeyPath="CurrentVersion"/>
|
|
||||||
<ROW Component="SHORTCUTDIR" ComponentId="{B21C7D52-4D02-4470-AD46-9EF2780EEB12}" Directory_="SHORTCUTDIR" Attributes="0"/>
|
|
||||||
<ROW Component="dupeGuru_ME.exe" ComponentId="{14F7B73A-FA85-4C10-AA92-10BE05FE103B}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru_ME.exe"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
|
|
||||||
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="dupeGuru_ME.exe AIShRegAnswer CurrentVersion SHORTCUTDIR"/>
|
|
||||||
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
|
|
||||||
<ROW File="dupeGuru_ME.exe" Component_="dupeGuru_ME.exe" FileName="dupeGu~1.exe|dupeGuru ME.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="dist\dupeGuru ME.exe" SelfReg="false" Sequence="1"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
|
|
||||||
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="install" PackageFileName="dupeguru_me_win64_[|ProductVersion]" Languages="en" InstallationType="4" CreateMd5="true" ExtUI="true" MsiPackageType="x64"/>
|
|
||||||
<ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.CacheComponent">
|
|
||||||
<ATTRIBUTE name="Enable" value="false"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
|
|
||||||
<ROW Path="<AI_DICTS>ui.ail"/>
|
|
||||||
<ROW Path="<AI_DICTS>ui_en.ail"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
|
|
||||||
<ROW Fragment="CommonUI.aip" Path="<AI_FRAGS>CommonUI.aip"/>
|
|
||||||
<ROW Fragment="FolderDlg.aip" Path="<AI_THEMES>classic\fragments\FolderDlg.aip"/>
|
|
||||||
<ROW Fragment="SequenceDialogs.aip" Path="<AI_THEMES>classic\fragments\SequenceDialogs.aip"/>
|
|
||||||
<ROW Fragment="Sequences.aip" Path="<AI_FRAGS>Sequences.aip"/>
|
|
||||||
<ROW Fragment="ShortcutsDlg.aip" Path="<AI_THEMES>classic\fragments\ShortcutsDlg.aip"/>
|
|
||||||
<ROW Fragment="StaticUIStrings.aip" Path="<AI_FRAGS>StaticUIStrings.aip"/>
|
|
||||||
<ROW Fragment="UI.aip" Path="<AI_THEMES>classic\fragments\UI.aip"/>
|
|
||||||
<ROW Fragment="Validation.aip" Path="<AI_FRAGS>Validation.aip"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
|
|
||||||
<ROW Name="aicustact.dll" SourcePath="<AI_CUSTACTS>aicustact.dll"/>
|
|
||||||
<ROW Name="banner_image.jpg" SourcePath="<AI_THEMES>classic\resources\banner-image.jpg"/>
|
|
||||||
<ROW Name="dialog_image.jpg" SourcePath="<AI_THEMES>classic\resources\dialog-image.jpg"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
|
|
||||||
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed) AND (VersionNT<"601")"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
|
|
||||||
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT<"601")@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
|
|
||||||
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="ChangeButton" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="301"/>
|
|
||||||
<ROW Dialog_="ResumeDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_RESUME" Ordering="299"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_MAINT" Ordering="197"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="198"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="199"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="201"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
|
|
||||||
<ROW Directory_="SHORTCUTDIR" Component_="SHORTCUTDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
|
|
||||||
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
|
|
||||||
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru_ME.exe]"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
|
|
||||||
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[ProgramFiles64Folder][Manufacturer]\[ProductName]"/>
|
|
||||||
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
|
|
||||||
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
|
|
||||||
<ROW Name="SystemFolder_msiexec.exe" SourcePath="<AI_RES>uninstall.ico" Index="0"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
|
|
||||||
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel <> 5)" Sequence="210"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE="No" AND (Not Installed)" Sequence="1300"/>
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE="ALL")" Sequence="1449"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
|
|
||||||
<ROW Condition="( Version9X OR ( NOT VersionNT64 ) OR ( VersionNT64 AND ((VersionNT64 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel >= 1)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel <> 1)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel <> 2)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel >= 1)))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel <> 1)))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel <> 2)))) ) )" Description="[ProductName] cannot be installed on the following Windows versions: [WindowsTypeNT64Display]" DescriptionLocId="AI.LaunchCondition.NoSpecificNT64" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
<ROW Condition="( Version9X OR VersionNT64 )" Description="[ProductName] cannot be installed on [WindowsTypeNTDisplay]" DescriptionLocId="AI.LaunchCondition.NoNT" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]" DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
|
|
||||||
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
|
|
||||||
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
|
|
||||||
<ROW Registry="CurrentVersion" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="CurrentVersion" Value="[ProductVersion]" Component_="CurrentVersion"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
|
|
||||||
<ROW Shortcut="Uninstall_dupeGuru_ME" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru ME" Component_="AIShRegAnswer" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
|
|
||||||
<ROW Shortcut="dupeGuru_ME" Directory_="SHORTCUTDIR" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
<ROW Shortcut="dupeGuru_ME_1" Directory_="DesktopFolder" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
|
|
||||||
<ATTRIBUTE name="UsedTheme" value="classic"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
|
|
||||||
<ROW Directory_="APPDIR" SourcePath="dist" Feature="MainFeature" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.pdb|*.vshost.*" ExcludeFlags="6"/>
|
|
||||||
</COMPONENT>
|
|
||||||
</DOCUMENT>
|
|
@ -1,46 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core.scanner import ScanType
|
|
||||||
|
|
||||||
from ..base.preferences import Preferences as PreferencesBase
|
|
||||||
|
|
||||||
class Preferences(PreferencesBase):
|
|
||||||
DEFAULT_SCAN_TYPE = ScanType.Tag
|
|
||||||
|
|
||||||
def _load_specific(self, settings):
|
|
||||||
get = self.get_value
|
|
||||||
self.word_weighting = get('WordWeighting', self.word_weighting)
|
|
||||||
self.match_similar = get('MatchSimilar', self.match_similar)
|
|
||||||
self.scan_tag_track = get('ScanTagTrack', self.scan_tag_track)
|
|
||||||
self.scan_tag_artist = get('ScanTagArtist', self.scan_tag_artist)
|
|
||||||
self.scan_tag_album = get('ScanTagAlbum', self.scan_tag_album)
|
|
||||||
self.scan_tag_title = get('ScanTagTitle', self.scan_tag_title)
|
|
||||||
self.scan_tag_genre = get('ScanTagGenre', self.scan_tag_genre)
|
|
||||||
self.scan_tag_year = get('ScanTagYear', self.scan_tag_year)
|
|
||||||
|
|
||||||
def _reset_specific(self):
|
|
||||||
self.filter_hardness = 80
|
|
||||||
self.word_weighting = True
|
|
||||||
self.match_similar = False
|
|
||||||
self.scan_tag_track = False
|
|
||||||
self.scan_tag_artist = True
|
|
||||||
self.scan_tag_album = True
|
|
||||||
self.scan_tag_title = True
|
|
||||||
self.scan_tag_genre = False
|
|
||||||
self.scan_tag_year = False
|
|
||||||
|
|
||||||
def _save_specific(self, settings):
|
|
||||||
set_ = self.set_value
|
|
||||||
set_('WordWeighting', self.word_weighting)
|
|
||||||
set_('MatchSimilar', self.match_similar)
|
|
||||||
set_('ScanTagTrack', self.scan_tag_track)
|
|
||||||
set_('ScanTagArtist', self.scan_tag_artist)
|
|
||||||
set_('ScanTagAlbum', self.scan_tag_album)
|
|
||||||
set_('ScanTagTitle', self.scan_tag_title)
|
|
||||||
set_('ScanTagGenre', self.scan_tag_genre)
|
|
||||||
set_('ScanTagYear', self.scan_tag_year)
|
|
||||||
|
|
@ -10,10 +10,10 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
|
from core.app import AppMode
|
||||||
from core.scanner import ScanType
|
from core.scanner import ScanType
|
||||||
|
|
||||||
from ..base.preferences_dialog import PreferencesDialogBase
|
from ..preferences_dialog import PreferencesDialogBase
|
||||||
from . import preferences
|
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class PreferencesDialog(PreferencesDialogBase):
|
|||||||
setchecked(self.wordWeightingBox, prefs.word_weighting)
|
setchecked(self.wordWeightingBox, prefs.word_weighting)
|
||||||
|
|
||||||
# Update UI state based on selected scan type
|
# Update UI state based on selected scan type
|
||||||
scan_type = prefs.scan_type
|
scan_type = prefs.get_scan_type(AppMode.Music)
|
||||||
word_based = scan_type in (
|
word_based = scan_type in (
|
||||||
ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder,
|
ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder,
|
||||||
ScanType.Tag
|
ScanType.Tag
|
||||||
@ -100,6 +100,3 @@ class PreferencesDialog(PreferencesDialogBase):
|
|||||||
prefs.match_similar = ischecked(self.matchSimilarBox)
|
prefs.match_similar = ischecked(self.matchSimilarBox)
|
||||||
prefs.word_weighting = ischecked(self.wordWeightingBox)
|
prefs.word_weighting = ischecked(self.wordWeightingBox)
|
||||||
|
|
||||||
def resetToDefaults(self):
|
|
||||||
self.load(preferences.Preferences())
|
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# Created On: 2011-11-27
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from qtlib.column import Column
|
from qtlib.column import Column
|
||||||
from ..base.results_model import ResultsModel as ResultsModelBase
|
from ..results_model import ResultsModel as ResultsModelBase
|
||||||
|
|
||||||
class ResultsModel(ResultsModelBase):
|
class ResultsModel(ResultsModelBase):
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-04-27
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -11,8 +9,8 @@ from PyQt5.QtGui import QPixmap
|
|||||||
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView, QHBoxLayout, QLabel, QSizePolicy
|
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView, QHBoxLayout, QLabel, QSizePolicy
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from ..base.details_dialog import DetailsDialog as DetailsDialogBase
|
from ..details_dialog import DetailsDialog as DetailsDialogBase
|
||||||
from ..base.details_table import DetailsTable
|
from ..details_table import DetailsTable
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<DOCUMENT Type="Advanced Installer" CreateVersion="4.7.2" version="8.0.2" Modules="professional" RootPath="." Language="en">
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
|
|
||||||
<ROW Property="ALLUSERS" Value="2"/>
|
|
||||||
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
|
|
||||||
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
|
|
||||||
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
|
|
||||||
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru_pe/"/>
|
|
||||||
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru_pe/"/>
|
|
||||||
<ROW Property="BannerBitmap" MultiBuildValue="DefaultBuild:banner_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="CTRLS" Value="2"/>
|
|
||||||
<ROW Property="DialogBitmap" MultiBuildValue="DefaultBuild:dialog_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductCode" Value="1033:{189C7FAD-CA63-4A56-B592-B68C34889265} " Type="16"/>
|
|
||||||
<ROW Property="ProductLanguage" Value="1033"/>
|
|
||||||
<ROW Property="ProductName" Value="dupeGuru Picture Edition" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductVersion" Value="1.7.0"/>
|
|
||||||
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
|
|
||||||
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
|
|
||||||
<ROW Property="UpgradeCode" Value="{B1E28F97-9CE2-45E2-B19D-C4137F4DEB85}"/>
|
|
||||||
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsType9XDisplay" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNTDisplay" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
|
|
||||||
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
|
|
||||||
<ROW Component="AIShRegAnswer" ComponentId="{F315B3C7-7C86-41EB-BE7D-2A6A8E3073B4}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer"/>
|
|
||||||
<ROW Component="CurrentVersion" ComponentId="{1A0BFEFD-F9D9-4861-93AC-D9717B7A635E}" Directory_="APPDIR" Attributes="4" KeyPath="CurrentVersion"/>
|
|
||||||
<ROW Component="SHORTCUTDIR" ComponentId="{29E7E841-7820-418B-8542-7F8CCC9777A8}" Directory_="SHORTCUTDIR" Attributes="0"/>
|
|
||||||
<ROW Component="dupeGuru_PE.exe" ComponentId="{4A31F2AE-F42E-4B0F-BC4D-A09F312D469B}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru_PE.exe"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
|
|
||||||
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="dupeGuru_PE.exe AIShRegAnswer SHORTCUTDIR CurrentVersion"/>
|
|
||||||
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
|
|
||||||
<ROW File="dupeGuru_PE.exe" Component_="dupeGuru_PE.exe" FileName="dupeGu~2.exe|dupeGuru PE.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="dist\dupeGuru PE.exe" SelfReg="false" Sequence="1"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
|
|
||||||
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="install" PackageFileName="dupeguru_pe_win64_[|ProductVersion]" Languages="en" InstallationType="4" CreateMd5="true" ExtUI="true" MsiPackageType="x64"/>
|
|
||||||
<ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
|
|
||||||
<ROW Path="<AI_DICTS>ui.ail"/>
|
|
||||||
<ROW Path="<AI_DICTS>ui_en.ail"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
|
|
||||||
<ROW Fragment="CommonUI.aip" Path="<AI_FRAGS>CommonUI.aip"/>
|
|
||||||
<ROW Fragment="FolderDlg.aip" Path="<AI_THEMES>classic\fragments\FolderDlg.aip"/>
|
|
||||||
<ROW Fragment="SequenceDialogs.aip" Path="<AI_THEMES>classic\fragments\SequenceDialogs.aip"/>
|
|
||||||
<ROW Fragment="Sequences.aip" Path="<AI_FRAGS>Sequences.aip"/>
|
|
||||||
<ROW Fragment="ShortcutsDlg.aip" Path="<AI_THEMES>classic\fragments\ShortcutsDlg.aip"/>
|
|
||||||
<ROW Fragment="StaticUIStrings.aip" Path="<AI_FRAGS>StaticUIStrings.aip"/>
|
|
||||||
<ROW Fragment="UI.aip" Path="<AI_THEMES>classic\fragments\UI.aip"/>
|
|
||||||
<ROW Fragment="Validation.aip" Path="<AI_FRAGS>Validation.aip"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
|
|
||||||
<ROW Name="aicustact.dll" SourcePath="<AI_CUSTACTS>aicustact.dll"/>
|
|
||||||
<ROW Name="banner_image.jpg" SourcePath="<AI_THEMES>classic\resources\banner-image.jpg"/>
|
|
||||||
<ROW Name="dialog_image.jpg" SourcePath="<AI_THEMES>classic\resources\dialog-image.jpg"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
|
|
||||||
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
|
|
||||||
<ATTRIBUTE name="MultiBuildFixedSizeBitmaps" value="DefaultBuild:1"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed) AND (VersionNT<"601")"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
|
|
||||||
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT<"601")@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
|
|
||||||
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="ChangeButton" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="301"/>
|
|
||||||
<ROW Dialog_="ResumeDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_RESUME" Ordering="299"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_MAINT" Ordering="197"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="198"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="199"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="201"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
|
|
||||||
<ROW Directory_="SHORTCUTDIR" Component_="SHORTCUTDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
|
|
||||||
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
|
|
||||||
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru_PE.exe]"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
|
|
||||||
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[ProgramFiles64Folder][Manufacturer]\[ProductName]"/>
|
|
||||||
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
|
|
||||||
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
|
|
||||||
<ROW Name="SystemFolder_msiexec.exe" SourcePath="<AI_RES>uninstall.ico" Index="0"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
|
|
||||||
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel <> 5)" Sequence="210"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE="No" AND (Not Installed)" Sequence="1300"/>
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE="ALL")" Sequence="1449"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
|
|
||||||
<ROW Condition="( Version9X OR VersionNT64 )" Description="[ProductName] cannot be installed on [WindowsTypeNTDisplay]" DescriptionLocId="AI.LaunchCondition.NoNT" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]" DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
|
|
||||||
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
|
|
||||||
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
|
|
||||||
<ROW Registry="CurrentVersion" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="CurrentVersion" Value="[ProductVersion]" Component_="CurrentVersion"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
|
|
||||||
<ROW Shortcut="Uninstall_dupeGuru_ME" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru PE" Component_="AIShRegAnswer" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
|
|
||||||
<ROW Shortcut="dupeGuru_PE" Directory_="SHORTCUTDIR" Name="dupeGu~1|dupeGuru PE" Component_="dupeGuru_PE.exe" Target="[#dupeGuru_PE.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
<ROW Shortcut="dupeGuru_PE_1" Directory_="DesktopFolder" Name="dupeGu~1|dupeGuru PE" Component_="dupeGuru_PE.exe" Target="[#dupeGuru_PE.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
|
|
||||||
<ATTRIBUTE name="UsedTheme" value="classic"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
|
|
||||||
<ROW Directory_="APPDIR" SourcePath="dist" Feature="MainFeature" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store" ExcludeFlags="6"/>
|
|
||||||
</COMPONENT>
|
|
||||||
</DOCUMENT>
|
|
@ -8,17 +8,9 @@ import logging
|
|||||||
|
|
||||||
from PyQt5.QtGui import QImage, QImageReader, QTransform
|
from PyQt5.QtGui import QImage, QImageReader, QTransform
|
||||||
|
|
||||||
from core_pe import __appname__
|
from core.pe.photo import Photo as PhotoBase
|
||||||
from core_pe.photo import Photo as PhotoBase
|
|
||||||
from core_pe.app import DupeGuru as DupeGuruModel
|
|
||||||
|
|
||||||
from ..base.app import DupeGuru as DupeGuruBase
|
|
||||||
from .block import getblocks
|
from .block import getblocks
|
||||||
from .details_dialog import DetailsDialog
|
|
||||||
from .result_window import ResultWindow
|
|
||||||
from .results_model import ResultsModel
|
|
||||||
from .preferences import Preferences
|
|
||||||
from .preferences_dialog import PreferencesDialog
|
|
||||||
|
|
||||||
class File(PhotoBase):
|
class File(PhotoBase):
|
||||||
def _plat_get_dimensions(self):
|
def _plat_get_dimensions(self):
|
||||||
@ -62,29 +54,3 @@ class File(PhotoBase):
|
|||||||
image = image.transformed(t)
|
image = image.transformed(t)
|
||||||
return getblocks(image, block_count_per_side)
|
return getblocks(image, block_count_per_side)
|
||||||
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
MODELCLASS = DupeGuruModel
|
|
||||||
EDITION = 'pe'
|
|
||||||
LOGO_NAME = 'logo_pe'
|
|
||||||
NAME = __appname__
|
|
||||||
|
|
||||||
DETAILS_DIALOG_CLASS = DetailsDialog
|
|
||||||
RESULT_WINDOW_CLASS = ResultWindow
|
|
||||||
RESULT_MODEL_CLASS = ResultsModel
|
|
||||||
PREFERENCES_CLASS = Preferences
|
|
||||||
PREFERENCES_DIALOG_CLASS = PreferencesDialog
|
|
||||||
|
|
||||||
def _setup(self):
|
|
||||||
self.model.fileclasses = [File]
|
|
||||||
DupeGuruBase._setup(self)
|
|
||||||
self.directories_dialog.menuFile.insertAction(
|
|
||||||
self.directories_dialog.actionLoadResults, self.resultWindow.actionClearPictureCache
|
|
||||||
)
|
|
||||||
|
|
||||||
def _update_options(self):
|
|
||||||
DupeGuruBase._update_options(self)
|
|
||||||
self.model.options['scan_type'] = self.prefs.scan_type
|
|
||||||
self.model.options['match_scaled'] = self.prefs.match_scaled
|
|
||||||
self.model.options['threshold'] = self.prefs.filter_hardness
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core.scanner import ScanType
|
|
||||||
|
|
||||||
from ..base.preferences import Preferences as PreferencesBase
|
|
||||||
|
|
||||||
class Preferences(PreferencesBase):
|
|
||||||
DEFAULT_SCAN_TYPE = ScanType.FuzzyBlock
|
|
||||||
|
|
||||||
def _load_specific(self, settings):
|
|
||||||
get = self.get_value
|
|
||||||
self.match_scaled = get('MatchScaled', self.match_scaled)
|
|
||||||
|
|
||||||
def _reset_specific(self):
|
|
||||||
self.filter_hardness = 95
|
|
||||||
self.match_scaled = False
|
|
||||||
|
|
||||||
def _save_specific(self, settings):
|
|
||||||
set_ = self.set_value
|
|
||||||
set_('MatchScaled', self.match_scaled)
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from core.scanner import ScanType
|
from core.scanner import ScanType
|
||||||
|
from core.app import AppMode
|
||||||
|
|
||||||
from ..base.preferences_dialog import PreferencesDialogBase
|
from ..preferences_dialog import PreferencesDialogBase
|
||||||
from . import preferences
|
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
@ -34,13 +34,10 @@ class PreferencesDialog(PreferencesDialogBase):
|
|||||||
setchecked(self.matchScaledBox, prefs.match_scaled)
|
setchecked(self.matchScaledBox, prefs.match_scaled)
|
||||||
|
|
||||||
# Update UI state based on selected scan type
|
# Update UI state based on selected scan type
|
||||||
scan_type = prefs.scan_type
|
scan_type = prefs.get_scan_type(AppMode.Picture)
|
||||||
fuzzy_scan = scan_type == ScanType.FuzzyBlock
|
fuzzy_scan = scan_type == ScanType.FuzzyBlock
|
||||||
self.filterHardnessSlider.setEnabled(fuzzy_scan)
|
self.filterHardnessSlider.setEnabled(fuzzy_scan)
|
||||||
|
|
||||||
def _save(self, prefs, ischecked):
|
def _save(self, prefs, ischecked):
|
||||||
prefs.match_scaled = ischecked(self.matchScaledBox)
|
prefs.match_scaled = ischecked(self.matchScaledBox)
|
||||||
|
|
||||||
def resetToDefaults(self):
|
|
||||||
self.load(preferences.Preferences())
|
|
||||||
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-23
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QMessageBox, QAction
|
|
||||||
|
|
||||||
from hscommon.trans import trget
|
|
||||||
from ..base.result_window import ResultWindow as ResultWindowBase
|
|
||||||
|
|
||||||
tr = trget('ui')
|
|
||||||
|
|
||||||
class ResultWindow(ResultWindowBase):
|
|
||||||
def _setupMenu(self):
|
|
||||||
ResultWindowBase._setupMenu(self)
|
|
||||||
self.actionClearPictureCache = QAction(tr("Clear Picture Cache"), self)
|
|
||||||
self.actionClearPictureCache.setShortcut('Ctrl+Shift+P')
|
|
||||||
self.menuFile.insertAction(self.actionSaveResults, self.actionClearPictureCache)
|
|
||||||
self.actionClearPictureCache.triggered.connect(self.clearPictureCacheTriggered)
|
|
||||||
|
|
||||||
def clearPictureCacheTriggered(self):
|
|
||||||
title = tr("Clear Picture Cache")
|
|
||||||
msg = tr("Do you really want to remove all your cached picture analysis?")
|
|
||||||
if self.app.confirm(title, msg, QMessageBox.No):
|
|
||||||
self.app.model.scanner.clear_picture_cache()
|
|
||||||
QMessageBox.information(self, title, tr("Picture cache cleared."))
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
|||||||
# Created On: 2011-11-27
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from qtlib.column import Column
|
from qtlib.column import Column
|
||||||
from ..base.results_model import ResultsModel as ResultsModelBase
|
from ..results_model import ResultsModel as ResultsModelBase
|
||||||
|
|
||||||
class ResultsModel(ResultsModelBase):
|
class ResultsModel(ResultsModelBase):
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
|
@ -7,15 +7,11 @@
|
|||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from hscommon import trans
|
from hscommon import trans
|
||||||
|
from core.app import AppMode
|
||||||
|
from core.scanner import ScanType
|
||||||
from qtlib.preferences import Preferences as PreferencesBase
|
from qtlib.preferences import Preferences as PreferencesBase
|
||||||
|
|
||||||
class Preferences(PreferencesBase):
|
class Preferences(PreferencesBase):
|
||||||
DEFAULT_SCAN_TYPE = None # edition-specific
|
|
||||||
|
|
||||||
def _load_specific(self, settings):
|
|
||||||
# load prefs specific to the dg edition
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _load_values(self, settings):
|
def _load_values(self, settings):
|
||||||
get = self.get_value
|
get = self.get_value
|
||||||
self.filter_hardness = get('FilterHardness', self.filter_hardness)
|
self.filter_hardness = get('FilterHardness', self.filter_hardness)
|
||||||
@ -37,11 +33,17 @@ class Preferences(PreferencesBase):
|
|||||||
self.recentResults = get('RecentResults', self.recentResults)
|
self.recentResults = get('RecentResults', self.recentResults)
|
||||||
self.recentFolders = get('RecentFolders', self.recentFolders)
|
self.recentFolders = get('RecentFolders', self.recentFolders)
|
||||||
|
|
||||||
self._load_specific(settings)
|
self.word_weighting = get('WordWeighting', self.word_weighting)
|
||||||
|
self.match_similar = get('MatchSimilar', self.match_similar)
|
||||||
def _reset_specific(self):
|
self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files)
|
||||||
# reset prefs specific to the dg edition
|
self.small_file_threshold = get('SmallFileThreshold', self.small_file_threshold)
|
||||||
pass
|
self.scan_tag_track = get('ScanTagTrack', self.scan_tag_track)
|
||||||
|
self.scan_tag_artist = get('ScanTagArtist', self.scan_tag_artist)
|
||||||
|
self.scan_tag_album = get('ScanTagAlbum', self.scan_tag_album)
|
||||||
|
self.scan_tag_title = get('ScanTagTitle', self.scan_tag_title)
|
||||||
|
self.scan_tag_genre = get('ScanTagGenre', self.scan_tag_genre)
|
||||||
|
self.scan_tag_year = get('ScanTagYear', self.scan_tag_year)
|
||||||
|
self.match_scaled = get('MatchScaled', self.match_scaled)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.filter_hardness = 95
|
self.filter_hardness = 95
|
||||||
@ -61,11 +63,17 @@ class Preferences(PreferencesBase):
|
|||||||
self.recentResults = []
|
self.recentResults = []
|
||||||
self.recentFolders = []
|
self.recentFolders = []
|
||||||
|
|
||||||
self._reset_specific()
|
self.word_weighting = True
|
||||||
|
self.match_similar = False
|
||||||
def _save_specific(self, settings):
|
self.ignore_small_files = True
|
||||||
# save prefs specific to the dg edition
|
self.small_file_threshold = 10 # KB
|
||||||
pass
|
self.scan_tag_track = False
|
||||||
|
self.scan_tag_artist = True
|
||||||
|
self.scan_tag_album = True
|
||||||
|
self.scan_tag_title = True
|
||||||
|
self.scan_tag_genre = False
|
||||||
|
self.scan_tag_year = False
|
||||||
|
self.match_scaled = False
|
||||||
|
|
||||||
def _save_values(self, settings):
|
def _save_values(self, settings):
|
||||||
set_ = self.set_value
|
set_ = self.set_value
|
||||||
@ -86,13 +94,31 @@ class Preferences(PreferencesBase):
|
|||||||
set_('RecentResults', self.recentResults)
|
set_('RecentResults', self.recentResults)
|
||||||
set_('RecentFolders', self.recentFolders)
|
set_('RecentFolders', self.recentFolders)
|
||||||
|
|
||||||
self._save_specific(settings)
|
set_('WordWeighting', self.word_weighting)
|
||||||
|
set_('MatchSimilar', self.match_similar)
|
||||||
|
set_('IgnoreSmallFiles', self.ignore_small_files)
|
||||||
|
set_('SmallFileThreshold', self.small_file_threshold)
|
||||||
|
set_('ScanTagTrack', self.scan_tag_track)
|
||||||
|
set_('ScanTagArtist', self.scan_tag_artist)
|
||||||
|
set_('ScanTagAlbum', self.scan_tag_album)
|
||||||
|
set_('ScanTagTitle', self.scan_tag_title)
|
||||||
|
set_('ScanTagGenre', self.scan_tag_genre)
|
||||||
|
set_('ScanTagYear', self.scan_tag_year)
|
||||||
|
set_('MatchScaled', self.match_scaled)
|
||||||
|
|
||||||
# scan_type is special because we save it immediately when we set it.
|
# scan_type is special because we save it immediately when we set it.
|
||||||
@property
|
def get_scan_type(self, app_mode):
|
||||||
def scan_type(self):
|
if app_mode == AppMode.Picture:
|
||||||
return self.get_value('ScanType', self.DEFAULT_SCAN_TYPE)
|
return self.get_value('ScanTypePicture', ScanType.FuzzyBlock)
|
||||||
|
elif app_mode == AppMode.Music:
|
||||||
|
return self.get_value('ScanTypeMusic', ScanType.Tag)
|
||||||
|
else:
|
||||||
|
return self.get_value('ScanTypeStandard', ScanType.Contents)
|
||||||
|
|
||||||
@scan_type.setter
|
def set_scan_type(self, app_mode, value):
|
||||||
def scan_type(self, value):
|
if app_mode == AppMode.Picture:
|
||||||
self.set_value('ScanType', value)
|
self.set_value('ScanTypePicture', value)
|
||||||
|
elif app_mode == AppMode.Music:
|
||||||
|
self.set_value('ScanTypeMusic', value)
|
||||||
|
else:
|
||||||
|
self.set_value('ScanTypeStandard', value)
|
@ -10,11 +10,12 @@ from PyQt5.QtWidgets import (
|
|||||||
QSlider, QSizePolicy, QSpacerItem, QCheckBox, QLineEdit, QMessageBox, QSpinBox
|
QSlider, QSizePolicy, QSpacerItem, QCheckBox, QLineEdit, QMessageBox, QSpinBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from hscommon.plat import ISOSX, ISLINUX
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from qtlib.util import horizontalWrap
|
from qtlib.util import horizontalWrap
|
||||||
from qtlib.preferences import get_langnames
|
from qtlib.preferences import get_langnames
|
||||||
|
|
||||||
|
from .preferences import Preferences
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
SUPPORTED_LANGUAGES = [
|
SUPPORTED_LANGUAGES = [
|
||||||
@ -123,9 +124,6 @@ class PreferencesDialogBase(QDialog):
|
|||||||
self.buttonBox = QDialogButtonBox(self)
|
self.buttonBox = QDialogButtonBox(self)
|
||||||
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.RestoreDefaults)
|
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.RestoreDefaults)
|
||||||
self.mainVLayout.addWidget(self.buttonBox)
|
self.mainVLayout.addWidget(self.buttonBox)
|
||||||
if (not ISOSX) and (not ISLINUX):
|
|
||||||
self.mainVLayout.removeWidget(self.ignoreHardlinkMatches)
|
|
||||||
self.ignoreHardlinkMatches.setHidden(True)
|
|
||||||
|
|
||||||
def _load(self, prefs, setchecked):
|
def _load(self, prefs, setchecked):
|
||||||
# Edition-specific
|
# Edition-specific
|
||||||
@ -177,6 +175,9 @@ class PreferencesDialogBase(QDialog):
|
|||||||
self.app.prefs.language = lang
|
self.app.prefs.language = lang
|
||||||
self._save(prefs, ischecked)
|
self._save(prefs, ischecked)
|
||||||
|
|
||||||
|
def resetToDefaults(self):
|
||||||
|
self.load(Preferences())
|
||||||
|
|
||||||
#--- Events
|
#--- Events
|
||||||
def buttonClicked(self, button):
|
def buttonClicked(self, button):
|
||||||
role = self.buttonBox.buttonRole(button)
|
role = self.buttonBox.buttonRole(button)
|
@ -9,7 +9,7 @@
|
|||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QSpacerItem, QSizePolicy,
|
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QSpacerItem, QSizePolicy,
|
||||||
QLabel, QTableView, QAbstractItemView, QApplication
|
QLabel, QTableView, QAbstractItemView
|
||||||
)
|
)
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
@ -63,12 +63,3 @@ class ProblemDialog(QDialog):
|
|||||||
self.horizontalLayout.addWidget(self.closeButton)
|
self.horizontalLayout.addWidget(self.closeButton)
|
||||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
from ..testapp import TestApp
|
|
||||||
app = QApplication([])
|
|
||||||
dgapp = TestApp()
|
|
||||||
dialog = ProblemDialog(None, dgapp)
|
|
||||||
dialog.show()
|
|
||||||
sys.exit(app.exec_())
|
|
@ -16,9 +16,13 @@ from hscommon.trans import trget
|
|||||||
from qtlib.util import moveToScreenCenter, horizontalWrap, createActions
|
from qtlib.util import moveToScreenCenter, horizontalWrap, createActions
|
||||||
from qtlib.search_edit import SearchEdit
|
from qtlib.search_edit import SearchEdit
|
||||||
|
|
||||||
|
from core.app import AppMode
|
||||||
from .results_model import ResultsView
|
from .results_model import ResultsView
|
||||||
from .stats_label import StatsLabel
|
from .stats_label import StatsLabel
|
||||||
from .prioritize_dialog import PrioritizeDialog
|
from .prioritize_dialog import PrioritizeDialog
|
||||||
|
from .se.results_model import ResultsModel as ResultsModelStandard
|
||||||
|
from .me.results_model import ResultsModel as ResultsModelMusic
|
||||||
|
from .pe.results_model import ResultsModel as ResultsModelPicture
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
@ -27,7 +31,13 @@ class ResultWindow(QMainWindow):
|
|||||||
super().__init__(parent, **kwargs)
|
super().__init__(parent, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
self._setupUi()
|
self._setupUi()
|
||||||
self.resultsModel = app.RESULT_MODEL_CLASS(self.app, self.resultsView)
|
if app.model.app_mode == AppMode.Picture:
|
||||||
|
MODEL_CLASS = ResultsModelPicture
|
||||||
|
elif app.model.app_mode == AppMode.Music:
|
||||||
|
MODEL_CLASS = ResultsModelMusic
|
||||||
|
else:
|
||||||
|
MODEL_CLASS = ResultsModelStandard
|
||||||
|
self.resultsModel = MODEL_CLASS(self.app, self.resultsView)
|
||||||
self.stats = StatsLabel(app.model.stats_label, self.statusLabel)
|
self.stats = StatsLabel(app.model.stats_label, self.statusLabel)
|
||||||
self._update_column_actions_status()
|
self._update_column_actions_status()
|
||||||
|
|
@ -17,8 +17,12 @@ class ResultsModel(Table):
|
|||||||
model = app.model.result_table
|
model = app.model.result_table
|
||||||
super().__init__(model, view, **kwargs)
|
super().__init__(model, view, **kwargs)
|
||||||
view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
|
view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
|
||||||
|
font = view.font()
|
||||||
|
font.setPointSize(app.prefs.tableFontSize)
|
||||||
|
self.view.setFont(font)
|
||||||
|
fm = QFontMetrics(font)
|
||||||
|
view.verticalHeader().setDefaultSectionSize(fm.height()+2)
|
||||||
|
|
||||||
app.prefsChanged.connect(self.appPrefsChanged)
|
|
||||||
app.willSavePrefs.connect(self.appWillSavePrefs)
|
app.willSavePrefs.connect(self.appWillSavePrefs)
|
||||||
|
|
||||||
def _getData(self, row, column, role):
|
def _getData(self, row, column, role):
|
||||||
@ -85,13 +89,6 @@ class ResultsModel(Table):
|
|||||||
self.model.delta_values = value
|
self.model.delta_values = value
|
||||||
|
|
||||||
#--- Events
|
#--- Events
|
||||||
def appPrefsChanged(self, prefs):
|
|
||||||
font = self.view.font()
|
|
||||||
font.setPointSize(prefs.tableFontSize)
|
|
||||||
self.view.setFont(font)
|
|
||||||
fm = QFontMetrics(font)
|
|
||||||
self.view.verticalHeader().setDefaultSectionSize(fm.height()+2)
|
|
||||||
|
|
||||||
def appWillSavePrefs(self):
|
def appWillSavePrefs(self):
|
||||||
self.model.columns.save_columns()
|
self.model.columns.save_columns()
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -13,16 +13,12 @@ from PyQt5.QtCore import QCoreApplication, QSettings
|
|||||||
from PyQt5.QtGui import QIcon, QPixmap
|
from PyQt5.QtGui import QIcon, QPixmap
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from hscommon.plat import ISWINDOWS
|
|
||||||
from hscommon.trans import install_gettext_trans_under_qt
|
from hscommon.trans import install_gettext_trans_under_qt
|
||||||
from qtlib.error_report_dialog import install_excepthook
|
from qtlib.error_report_dialog import install_excepthook
|
||||||
from qtlib.util import setupQtLogging
|
from qtlib.util import setupQtLogging
|
||||||
from qt.base import dg_rc
|
from qt import dg_rc
|
||||||
from qt.base.platform import BASE_PATH
|
from qt.platform import BASE_PATH
|
||||||
from core_{edition} import __version__, __appname__
|
from core import __version__, __appname__
|
||||||
|
|
||||||
if ISWINDOWS:
|
|
||||||
import qt.base.cxfreeze_fix
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
@ -36,7 +32,7 @@ def main():
|
|||||||
install_gettext_trans_under_qt(locale_folder, lang)
|
install_gettext_trans_under_qt(locale_folder, lang)
|
||||||
# Many strings are translated at import time, so this is why we only import after the translator
|
# Many strings are translated at import time, so this is why we only import after the translator
|
||||||
# has been installed
|
# has been installed
|
||||||
from qt.{edition}.app import DupeGuru
|
from qt.app import DupeGuru
|
||||||
app.setWindowIcon(QIcon(QPixmap(":/{0}".format(DupeGuru.LOGO_NAME))))
|
app.setWindowIcon(QIcon(QPixmap(":/{0}".format(DupeGuru.LOGO_NAME))))
|
||||||
dgapp = DupeGuru()
|
dgapp = DupeGuru()
|
||||||
install_excepthook('https://github.com/hsoft/dupeguru/issues')
|
install_excepthook('https://github.com/hsoft/dupeguru/issues')
|
||||||
|
50
qt/se/app.py
50
qt/se/app.py
@ -1,50 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core_se import __appname__
|
|
||||||
from core_se.app import DupeGuru as DupeGuruModel
|
|
||||||
from core.directories import Directories as DirectoriesBase, DirectoryState
|
|
||||||
|
|
||||||
from ..base.app import DupeGuru as DupeGuruBase
|
|
||||||
from .details_dialog import DetailsDialog
|
|
||||||
from .results_model import ResultsModel
|
|
||||||
from .preferences import Preferences
|
|
||||||
from .preferences_dialog import PreferencesDialog
|
|
||||||
|
|
||||||
class Directories(DirectoriesBase):
|
|
||||||
ROOT_PATH_TO_EXCLUDE = frozenset(['windows', 'program files'])
|
|
||||||
|
|
||||||
def _default_state_for_path(self, path):
|
|
||||||
result = DirectoriesBase._default_state_for_path(self, path)
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
if len(path) == 2 and path[1].lower() in self.ROOT_PATH_TO_EXCLUDE:
|
|
||||||
return DirectoryState.Excluded
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
MODELCLASS = DupeGuruModel
|
|
||||||
EDITION = 'se'
|
|
||||||
LOGO_NAME = 'logo_se'
|
|
||||||
NAME = __appname__
|
|
||||||
|
|
||||||
DETAILS_DIALOG_CLASS = DetailsDialog
|
|
||||||
RESULT_MODEL_CLASS = ResultsModel
|
|
||||||
PREFERENCES_CLASS = Preferences
|
|
||||||
PREFERENCES_DIALOG_CLASS = PreferencesDialog
|
|
||||||
|
|
||||||
def _setup(self):
|
|
||||||
self.directories = Directories()
|
|
||||||
DupeGuruBase._setup(self)
|
|
||||||
|
|
||||||
def _update_options(self):
|
|
||||||
DupeGuruBase._update_options(self)
|
|
||||||
self.model.options['min_match_percentage'] = self.prefs.filter_hardness
|
|
||||||
self.model.options['scan_type'] = self.prefs.scan_type
|
|
||||||
self.model.options['word_weighting'] = self.prefs.word_weighting
|
|
||||||
self.model.options['match_similar_words'] = self.prefs.match_similar
|
|
||||||
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
|
|
||||||
self.model.options['size_threshold'] = threshold * 1024 # threshold is in KB. the scanner wants bytes
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2009-05-24
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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
|
||||||
@ -10,8 +8,8 @@ from PyQt5.QtCore import QSize
|
|||||||
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView
|
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView
|
||||||
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from ..base.details_dialog import DetailsDialog as DetailsDialogBase
|
from ..details_dialog import DetailsDialog as DetailsDialogBase
|
||||||
from ..base.details_table import DetailsTable
|
from ..details_table import DetailsTable
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<DOCUMENT Type="Advanced Installer" CreateVersion="4.4.1" version="8.0.2" Modules="professional" RootPath="." Language="en">
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
|
|
||||||
<ROW Property="ALLUSERS" Value="2"/>
|
|
||||||
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
|
|
||||||
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
|
|
||||||
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
|
|
||||||
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru/"/>
|
|
||||||
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru/"/>
|
|
||||||
<ROW Property="BannerBitmap" MultiBuildValue="DefaultBuild:banner_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="CTRLS" Value="2"/>
|
|
||||||
<ROW Property="DialogBitmap" MultiBuildValue="DefaultBuild:dialog_image.jpg" Type="1"/>
|
|
||||||
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductCode" Value="1033:{D1E765C2-98C4-49AF-80DA-A5F803EB4FC3} " Type="16"/>
|
|
||||||
<ROW Property="ProductLanguage" Value="1033"/>
|
|
||||||
<ROW Property="ProductName" Value="dupeGuru" ValueLocId="*"/>
|
|
||||||
<ROW Property="ProductVersion" Value="2.7.0"/>
|
|
||||||
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
|
|
||||||
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
|
|
||||||
<ROW Property="UpgradeCode" Value="{33E0D6C8-D7C6-46ED-B1A9-ECFE409EC9D5}"/>
|
|
||||||
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsType9XDisplay" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows NT 4.0, Windows NT 4.0 Service Pack 1, Windows NT 4.0 Service Pack 2, Windows NT 4.0 Service Pack 3, Windows NT 4.0 Service Pack 4, Windows NT 4.0 Service Pack 5, Windows NT 4.0 Service Pack 6" ValueLocId="-"/>
|
|
||||||
<ROW Property="WindowsTypeNTDisplay" MultiBuildValue="DefaultBuild:Windows NT 4.0" ValueLocId="-"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
|
|
||||||
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
|
|
||||||
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
|
|
||||||
<ROW Component="AIShRegAnswer" ComponentId="{775090B3-2E56-40F5-9DD8-24A2F82DA601}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer"/>
|
|
||||||
<ROW Component="CurrentVersion" ComponentId="{398CD484-F093-4CEC-BE84-1BD0DD461942}" Directory_="APPDIR" Attributes="4" KeyPath="CurrentVersion"/>
|
|
||||||
<ROW Component="SHORTCUTDIR" ComponentId="{D4E1AAB3-42FD-4997-A393-19949091495D}" Directory_="SHORTCUTDIR" Attributes="0"/>
|
|
||||||
<ROW Component="dupeGuru.exe" ComponentId="{A8FFC84F-B54B-4883-B9FD-5C545AF0E51C}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru.exe"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
|
|
||||||
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="dupeGuru.exe AIShRegAnswer CurrentVersion SHORTCUTDIR"/>
|
|
||||||
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
|
|
||||||
<ROW File="dupeGuru.exe" Component_="dupeGuru.exe" FileName="dupeGuru.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="dist\dupeGuru.exe" SelfReg="false" Sequence="1"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
|
|
||||||
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="install" PackageFileName="dupeguru_win64_[|ProductVersion]" Languages="en" InstallationType="4" CreateMd5="true" ExtUI="true" MsiPackageType="x64"/>
|
|
||||||
<ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
|
|
||||||
<ROW Path="<AI_DICTS>ui.ail"/>
|
|
||||||
<ROW Path="<AI_DICTS>ui_en.ail"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
|
|
||||||
<ROW Fragment="CommonUI.aip" Path="<AI_FRAGS>CommonUI.aip"/>
|
|
||||||
<ROW Fragment="FolderDlg.aip" Path="<AI_THEMES>classic\fragments\FolderDlg.aip"/>
|
|
||||||
<ROW Fragment="SequenceDialogs.aip" Path="<AI_THEMES>classic\fragments\SequenceDialogs.aip"/>
|
|
||||||
<ROW Fragment="Sequences.aip" Path="<AI_FRAGS>Sequences.aip"/>
|
|
||||||
<ROW Fragment="ShortcutsDlg.aip" Path="<AI_THEMES>classic\fragments\ShortcutsDlg.aip"/>
|
|
||||||
<ROW Fragment="StaticUIStrings.aip" Path="<AI_FRAGS>StaticUIStrings.aip"/>
|
|
||||||
<ROW Fragment="UI.aip" Path="<AI_THEMES>classic\fragments\UI.aip"/>
|
|
||||||
<ROW Fragment="Validation.aip" Path="<AI_FRAGS>Validation.aip"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
|
|
||||||
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
|
|
||||||
<ROW Name="aicustact.dll" SourcePath="<AI_CUSTACTS>aicustact.dll"/>
|
|
||||||
<ROW Name="banner_image.jpg" SourcePath="<AI_THEMES>classic\resources\banner-image.jpg"/>
|
|
||||||
<ROW Name="dialog_image.jpg" SourcePath="<AI_THEMES>classic\resources\dialog-image.jpg"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
|
|
||||||
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="StartmenuShortcutsCheckBox" Action="Show" Condition="(Not Installed)"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed) AND (VersionNT<"601")"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
|
|
||||||
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT<"601")@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartmenuShortcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
|
|
||||||
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="CustomizeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_MAINT" Ordering="1"/>
|
|
||||||
<ROW Dialog_="MaintenanceTypeDlg" Control_="ChangeButton" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="301"/>
|
|
||||||
<ROW Dialog_="ResumeDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_RESUME" Ordering="299"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_MAINT" Ordering="197"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="198"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="199"/>
|
|
||||||
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="201"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
|
|
||||||
<ROW Directory_="SHORTCUTDIR" Component_="SHORTCUTDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
|
|
||||||
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
|
|
||||||
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru.exe]"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
|
|
||||||
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[ProgramFiles64Folder][Manufacturer]\[ProductName]"/>
|
|
||||||
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
|
|
||||||
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
|
|
||||||
<ROW Name="SystemFolder_msiexec.exe" SourcePath="<AI_RES>uninstall.ico" Index="0"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
|
|
||||||
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel <> 5)" Sequence="210"/>
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
|
|
||||||
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE="No" AND (Not Installed)" Sequence="1300"/>
|
|
||||||
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE="ALL")" Sequence="1449"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
|
|
||||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
|
||||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
|
|
||||||
<ROW Condition="( Version9X OR VersionNT64 OR ( VersionNT AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel >= 1))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 1))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 2))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 3))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 4))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 5))) AND ((VersionNT <> 400) OR ((VersionNT = 400) AND (ServicePackLevel <> 6))) ) )" Description="[ProductName] cannot be installed on the following Windows versions: [WindowsTypeNTDisplay]" DescriptionLocId="AI.LaunchCondition.NoSpecificNT" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]" DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
|
|
||||||
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
|
|
||||||
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
|
|
||||||
<ROW Registry="CurrentVersion" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="CurrentVersion" Value="[ProductVersion]" Component_="CurrentVersion"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
|
|
||||||
<ROW Shortcut="Uninstall_dupeGuru" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru" Component_="AIShRegAnswer" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
|
|
||||||
<ROW Shortcut="dupeGuru" Directory_="DesktopFolder" Name="dupeGuru" Component_="dupeGuru.exe" Target="[#dupeGuru.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
<ROW Shortcut="dupeGuru_1" Directory_="SHORTCUTDIR" Name="dupeGuru" Component_="dupeGuru.exe" Target="[#dupeGuru.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
|
|
||||||
<ATTRIBUTE name="UsedTheme" value="classic"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
|
|
||||||
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
|
|
||||||
</COMPONENT>
|
|
||||||
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
|
|
||||||
<ROW Directory_="APPDIR" SourcePath="dist" Feature="MainFeature" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.vshost.*" ExcludeFlags="6"/>
|
|
||||||
</COMPONENT>
|
|
||||||
</DOCUMENT>
|
|
@ -1,34 +0,0 @@
|
|||||||
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from core.scanner import ScanType
|
|
||||||
|
|
||||||
from ..base.preferences import Preferences as PreferencesBase
|
|
||||||
|
|
||||||
class Preferences(PreferencesBase):
|
|
||||||
DEFAULT_SCAN_TYPE = ScanType.Contents
|
|
||||||
|
|
||||||
def _load_specific(self, settings):
|
|
||||||
get = self.get_value
|
|
||||||
self.word_weighting = get('WordWeighting', self.word_weighting)
|
|
||||||
self.match_similar = get('MatchSimilar', self.match_similar)
|
|
||||||
self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files)
|
|
||||||
self.small_file_threshold = get('SmallFileThreshold', self.small_file_threshold)
|
|
||||||
|
|
||||||
def _reset_specific(self):
|
|
||||||
self.filter_hardness = 80
|
|
||||||
self.word_weighting = True
|
|
||||||
self.match_similar = False
|
|
||||||
self.ignore_small_files = True
|
|
||||||
self.small_file_threshold = 10 # KB
|
|
||||||
|
|
||||||
def _save_specific(self, settings):
|
|
||||||
set_ = self.set_value
|
|
||||||
set_('WordWeighting', self.word_weighting)
|
|
||||||
set_('MatchSimilar', self.match_similar)
|
|
||||||
set_('IgnoreSmallFiles', self.ignore_small_files)
|
|
||||||
set_('SmallFileThreshold', self.small_file_threshold)
|
|
||||||
|
|
@ -13,10 +13,10 @@ from hscommon.plat import ISWINDOWS, ISLINUX
|
|||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from hscommon.util import tryint
|
from hscommon.util import tryint
|
||||||
|
|
||||||
|
from core.app import AppMode
|
||||||
from core.scanner import ScanType
|
from core.scanner import ScanType
|
||||||
|
|
||||||
from ..base.preferences_dialog import PreferencesDialogBase
|
from ..preferences_dialog import PreferencesDialogBase
|
||||||
from . import preferences
|
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class PreferencesDialog(PreferencesDialogBase):
|
|||||||
self.sizeThresholdEdit.setText(str(prefs.small_file_threshold))
|
self.sizeThresholdEdit.setText(str(prefs.small_file_threshold))
|
||||||
|
|
||||||
# Update UI state based on selected scan type
|
# Update UI state based on selected scan type
|
||||||
scan_type = prefs.scan_type
|
scan_type = prefs.get_scan_type(AppMode.Standard)
|
||||||
word_based = scan_type == ScanType.Filename
|
word_based = scan_type == ScanType.Filename
|
||||||
self.filterHardnessSlider.setEnabled(word_based)
|
self.filterHardnessSlider.setEnabled(word_based)
|
||||||
self.matchSimilarBox.setEnabled(word_based)
|
self.matchSimilarBox.setEnabled(word_based)
|
||||||
@ -93,6 +93,3 @@ class PreferencesDialog(PreferencesDialogBase):
|
|||||||
prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox)
|
prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox)
|
||||||
prefs.small_file_threshold = tryint(self.sizeThresholdEdit.text())
|
prefs.small_file_threshold = tryint(self.sizeThresholdEdit.text())
|
||||||
|
|
||||||
def resetToDefaults(self):
|
|
||||||
self.load(preferences.Preferences())
|
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# Created On: 2011-11-27
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# 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.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from qtlib.column import Column
|
from qtlib.column import Column
|
||||||
from ..base.results_model import ResultsModel as ResultsModelBase
|
from ..results_model import ResultsModel as ResultsModelBase
|
||||||
|
|
||||||
class ResultsModel(ResultsModelBase):
|
class ResultsModel(ResultsModelBase):
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2010-10-04
|
|
||||||
# Copyright 2015 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 qtlib.recent import Recent
|
|
||||||
|
|
||||||
from .se.app import DupeGuru
|
|
||||||
|
|
||||||
class TestApp(DupeGuru):
|
|
||||||
# Use this for as a mock for UI testing.
|
|
||||||
def mustShowNag(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _setup(self):
|
|
||||||
self.prefs = self._create_preferences()
|
|
||||||
self.prefs.load()
|
|
||||||
self.recentResults = Recent(self, 'recentResults')
|
|
||||||
self._setupActions()
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user