Merge core_se.app into core.app

This commit is contained in:
Virgil Dupras 2016-05-31 21:43:24 -04:00
parent 9a25670552
commit 773f6651e6
7 changed files with 144 additions and 167 deletions

View File

@ -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
View 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

View File

@ -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 = {

View File

@ -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

View File

@ -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']

View File

@ -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)

View File

@ -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