mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-09 21:24:36 +00:00
Merge core_se.app into core.app
This commit is contained in:
parent
9a25670552
commit
773f6651e6
141
core/app.py
141
core/app.py
@ -9,7 +9,6 @@ import os.path as op
|
||||
import logging
|
||||
import subprocess
|
||||
import re
|
||||
import time
|
||||
import shutil
|
||||
|
||||
from send2trash import send2trash
|
||||
@ -18,14 +17,26 @@ from hscommon.notify import Broadcaster
|
||||
from hscommon.path import Path
|
||||
from hscommon.conflict import smart_move, smart_copy
|
||||
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.plat import ISWINDOWS
|
||||
from hscommon import desktop
|
||||
|
||||
from . import directories, results, export, fs
|
||||
import core_se.fs
|
||||
import core_se.result_table
|
||||
import core_se.scanner
|
||||
import core_me.fs
|
||||
import core_me.prioritize
|
||||
import core_me.result_table
|
||||
import core_me.scanner
|
||||
import core_pe.photo
|
||||
import core_pe.prioritize
|
||||
import core_pe.result_table
|
||||
import core_pe.scanner
|
||||
from core_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 .scanner import ScanType, Scanner
|
||||
from .scanner import ScanType
|
||||
from .gui.deletion_options import DeletionOptions
|
||||
from .gui.details_panel import DetailsPanel
|
||||
from .gui.directory_tree import DirectoryTree
|
||||
@ -67,53 +78,6 @@ JOBID2TITLE = {
|
||||
JobType.Copy: tr("Copying"),
|
||||
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):
|
||||
"""Holds everything together.
|
||||
@ -160,8 +124,7 @@ class DupeGuru(Broadcaster):
|
||||
# select_dest_folder(prompt: str) --> str
|
||||
# select_dest_file(prompt: str, ext: str) --> str
|
||||
|
||||
PROMPT_NAME = "dupeGuru"
|
||||
SCANNER_CLASS = Scanner
|
||||
NAME = PROMPT_NAME = "dupeGuru"
|
||||
|
||||
def __init__(self, view):
|
||||
if view.get_default(DEBUG_MODE_PREFERENCE):
|
||||
@ -185,6 +148,7 @@ class DupeGuru(Broadcaster):
|
||||
'clean_empty_dirs': False,
|
||||
'ignore_hardlink_matches': False,
|
||||
'copymove_dest_type': DestType.Relative,
|
||||
'cache_path': op.join(self.appdata, 'cached_pictures.db'),
|
||||
}
|
||||
self.selected_dupes = []
|
||||
self.details_panel = DetailsPanel(self)
|
||||
@ -199,15 +163,25 @@ class DupeGuru(Broadcaster):
|
||||
for child in children:
|
||||
child.connect()
|
||||
|
||||
#--- Virtual
|
||||
def _prioritization_categories(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _create_result_table(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
#--- Private
|
||||
def _create_result_table(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.result_table.ResultTable(self)
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.result_table.ResultTable(self)
|
||||
else:
|
||||
return core_se.result_table.ResultTable(self)
|
||||
|
||||
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':
|
||||
return self.results.is_marked(dupe)
|
||||
if key == 'percentage':
|
||||
@ -227,6 +201,10 @@ class DupeGuru(Broadcaster):
|
||||
return result
|
||||
|
||||
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':
|
||||
return group.percentage
|
||||
if key == 'dupe_count':
|
||||
@ -354,6 +332,15 @@ class DupeGuru(Broadcaster):
|
||||
self.selected_dupes = dupes
|
||||
self.notify('dupes_selected')
|
||||
|
||||
#--- Protected
|
||||
def _prioritization_categories(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.prioritize.all_categories()
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.prioritize.all_categories()
|
||||
else:
|
||||
return prioritize.all_categories()
|
||||
|
||||
#--- Public
|
||||
def add_directory(self, d):
|
||||
"""Adds folder ``d`` to :attr:`directories`.
|
||||
@ -767,7 +754,7 @@ class DupeGuru(Broadcaster):
|
||||
def do(j):
|
||||
j.set_progress(0, tr("Collecting files to scan"))
|
||||
if scanner.scan_type == ScanType.Folders:
|
||||
files = list(self.directories.get_folders(folderclass=self.folderclass, j=j))
|
||||
files = list(self.directories.get_folders(folderclass=core_se.fs.folder, j=j))
|
||||
else:
|
||||
files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j))
|
||||
if self.options['ignore_hardlink_matches']:
|
||||
@ -816,3 +803,33 @@ class DupeGuru(Broadcaster):
|
||||
result = tr("%s (%d discarded)") % (result, self.discarded_file_count)
|
||||
return result
|
||||
|
||||
@property
|
||||
def fileclasses(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return [core_pe.photo.PLAT_SPECIFIC_PHOTO_CLASS]
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return [core_me.fs.MusicFile]
|
||||
else:
|
||||
return [core_se.fs.File]
|
||||
|
||||
@property
|
||||
def SCANNER_CLASS(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.scanner.ScannerPE
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.scanner.ScannerME
|
||||
else:
|
||||
return core_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']
|
||||
|
||||
|
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
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
from hsaudiotag import auto
|
||||
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
|
||||
|
||||
TAG_FIELDS = {
|
||||
|
@ -7,7 +7,7 @@
|
||||
import logging
|
||||
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 . import exif
|
||||
|
||||
|
@ -1,95 +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, AppMode, cmp_value
|
||||
from core import prioritize
|
||||
import core_me.fs
|
||||
import core_me.prioritize
|
||||
import core_me.result_table
|
||||
import core_me.scanner
|
||||
import core_pe.photo
|
||||
import core_pe.prioritize
|
||||
import core_pe.result_table
|
||||
import core_pe.scanner
|
||||
from core_pe.photo import get_delta_dimensions
|
||||
from . import __appname__, fs, scanner
|
||||
from .result_table import ResultTable
|
||||
|
||||
class DupeGuru(DupeGuruBase):
|
||||
NAME = __appname__
|
||||
|
||||
def __init__(self, view):
|
||||
DupeGuruBase.__init__(self, view)
|
||||
self.folderclass = fs.Folder
|
||||
self.options['cache_path'] = op.join(self.appdata, 'cached_pictures.db')
|
||||
|
||||
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)
|
||||
return DupeGuruBase._get_dupe_sort_key(self, dupe, get_group, key, delta)
|
||||
|
||||
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()
|
||||
return DupeGuruBase._get_group_sort_key(self, group, key)
|
||||
|
||||
def _prioritization_categories(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.prioritize.all_categories()
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.prioritize.all_categories()
|
||||
else:
|
||||
return prioritize.all_categories()
|
||||
|
||||
def _create_result_table(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.result_table.ResultTable(self)
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.result_table.ResultTable(self)
|
||||
else:
|
||||
return ResultTable(self)
|
||||
|
||||
@property
|
||||
def fileclasses(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return [core_pe.photo.PLAT_SPECIFIC_PHOTO_CLASS]
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return [core_me.fs.MusicFile]
|
||||
else:
|
||||
return [fs.File]
|
||||
|
||||
@property
|
||||
def SCANNER_CLASS(self):
|
||||
if self.app_mode == AppMode.Picture:
|
||||
return core_pe.scanner.ScannerPE
|
||||
elif self.app_mode == AppMode.Music:
|
||||
return core_me.scanner.ScannerME
|
||||
else:
|
||||
return 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']
|
||||
|
@ -1,15 +1,15 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2013-07-14
|
||||
# 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
|
||||
#
|
||||
# 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.util import format_size
|
||||
|
||||
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):
|
||||
size = dupe.size
|
||||
@ -39,9 +39,9 @@ def get_display_info(dupe, group, delta):
|
||||
class File(fs.File):
|
||||
def get_display_info(self, group, delta):
|
||||
return get_display_info(self, group, delta)
|
||||
|
||||
|
||||
|
||||
class Folder(fs.Folder):
|
||||
def get_display_info(self, group, delta):
|
||||
return get_display_info(self, group, delta)
|
||||
|
||||
|
||||
|
@ -19,8 +19,7 @@ from qtlib.recent import Recent
|
||||
from qtlib.util import createActions
|
||||
from qtlib.progress_window import ProgressWindow
|
||||
|
||||
from core.app import AppMode
|
||||
from core_se.app import DupeGuru as DupeGuruModel
|
||||
from core.app import AppMode, DupeGuru as DupeGuruModel
|
||||
import core_pe.photo
|
||||
from . import platform
|
||||
from .preferences import Preferences
|
||||
|
Loading…
x
Reference in New Issue
Block a user