summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog88
-rw-r--r--rapid/INSTALL2
-rw-r--r--rapid/backupfile.py179
-rw-r--r--rapid/config.py4
-rw-r--r--rapid/copyfiles.py161
-rw-r--r--rapid/downloadtracker.py136
-rw-r--r--rapid/filemodify.py159
-rw-r--r--rapid/glade3/about.ui4
-rw-r--r--rapid/glade3/prefs.ui976
-rw-r--r--rapid/glade3/rapid.ui110
-rwxr-xr-xrapid/metadataexiftool.py52
-rw-r--r--rapid/preferencesdialog.py705
-rw-r--r--rapid/prefsrapid.py177
-rwxr-xr-xrapid/problemnotification.py186
-rwxr-xr-xrapid/rapid.py228
-rw-r--r--rapid/rpdfile.py4
-rwxr-xr-xrapid/scan.py2
-rw-r--r--rapid/subfolderfile.py233
-rw-r--r--rapid/thumbnail.py91
19 files changed, 2122 insertions, 1375 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog
index 8269afb..5bac39c 100644
--- a/rapid/ChangeLog
+++ b/rapid/ChangeLog
@@ -1,3 +1,81 @@
+Version 0.4.10
+--------------
+
+2014-02-23
+
+Updated Catalan and Portuguese translations.
+
+Fixed bug in translations for term "Back up".
+
+
+Version 0.4.9
+-------------
+
+2014-01-21
+
+Updated Catalan and Spanish translations.
+
+Fixed occasional incorrect use of term "backup".
+
+
+Version 0.4.9 Beta 3
+--------------------
+
+2014-01-20
+
+Fixed packaging bug.
+
+
+Version 0.4.9 Beta 2
+--------------------
+
+2014-01-20
+
+Added file verification of downloaded and backed up files.
+
+Updated Dutch, Hungarian, Italian, Polish, Serbian, Spanish and Swedish
+translations. Added Catalan translation.
+
+
+Version 0.4.9 Beta 1
+--------------------
+
+2014-01-16
+
+Fixed bugs #1025908 and #1186955: Finalize fix for severe performance problems
+and crashes that arose from the combination of Gnome's GIO file functionality
+and python's multiprocessing. The solution was to remove GIO and replace it with
+regular python file processing. A nice side effect is that the program now runs
+faster than ever before.
+
+Fixed bug #1268291: Handle cases where filesystem metadata (e.g. file
+permissions) could not be copied when writing to certain file systems such as
+NTFS. The program will now consider a file is copied succesfully even if the
+filesystem metadata could not be updated.
+
+Fixed bug #1269032: When Sync RAW + JPEG sequence numbers is enabled, the
+program fails to properly deal with photos with corrupt EXIF metadata.
+
+Fixed bug #1269079: Download failure when folder exists for only one of photo or
+video on auto detected back devices.
+
+Updated Norwegian and Serbian translations.
+
+
+Version 0.4.8
+-------------
+
+2013-12-31
+
+Fixed bug #1263237: Added support for MPO files (3D images). Thanks to Jan
+Kaluza for reporting it.
+
+Fixed bug #1263483: Some terms in the user interface are not being translated.
+Thanks to Jose Luis Tirado for alerting me to the problem, which has probably
+existed for some time.
+
+Updated Dutch, French Italian, Polish and Spanish translations.
+
Version 0.4.7
-------------
@@ -205,10 +283,10 @@ You can now specify paths never to scan for photos or videos. By default, any
path ending in .Trash or .thumbnails is ignored. Advanced users can specify
paths to never scan using python-style regular expressions.
-Fixed bug #774488: added manual backup path for videos, in addition to photos -
+Fixed bug #774488: added manual back up path for videos, in addition to photos -
-You can now manually specify a path specifically in which to backup videos. This
-can be the same as or different than the path in which to backup photos.
+You can now manually specify a path specifically in which to back up videos. This
+can be the same as or different than the path in which to back up photos.
Fixed bug #838722: wrong file types may be backed up to external devices -
@@ -217,12 +295,12 @@ type might be backed up. For instance, if the backup device is only meant to
store videos, and the download contains photos, photos would incorrectly be
backed up to the device in addition to videos.
-Fixed bug #815727: Backup errors and warnings incorrectly displayed in log
+Fixed bug #815727: Back up errors and warnings incorrectly displayed in log
window -
Fixed a bug that occurred when backing up errors are encountered, the log window
did not display them correctly, although they were correctly outputted to the
-terminal window. This only occurred when more than one backup device was being
+terminal window. This only occurred when more than one back up device was being
used during a download.
Fixed bug #859242: Crash when displaying a preview of file without an extracted
diff --git a/rapid/INSTALL b/rapid/INSTALL
index dd0ac2b..5d3dffa 100644
--- a/rapid/INSTALL
+++ b/rapid/INSTALL
@@ -1,6 +1,6 @@
Rapid Photo Downloader requires the following software:
-* Python 2.6 or 2.7
+* Python 2.7
* Pyexiv2 0.3.0 or higher
* python-gnome2 2.28 or higher
* python-gtk2 2.17 or higher
diff --git a/rapid/backupfile.py b/rapid/backupfile.py
index 7c91a19..0247346 100644
--- a/rapid/backupfile.py
+++ b/rapid/backupfile.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011 - 2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011 - 2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -21,9 +21,11 @@
import multiprocessing
import tempfile
import os
+import errno
+import hashlib
-import gio
import shutil
+import io
import logging
logger = multiprocessing.get_logger()
@@ -38,6 +40,7 @@ VIDEO_BACKUP = 2
PHOTO_VIDEO_BACKUP = 3
from gettext import gettext as _
+from copyfiles import copy_file_metadata
class BackupFiles(multiprocessing.Process):
@@ -48,13 +51,11 @@ class BackupFiles(multiprocessing.Process):
self.results_pipe = results_pipe
self.terminate_queue = terminate_queue
self.batch_size_bytes = batch_size_MB * 1048576 # * 1024 * 1024
+ self.io_buffer = 1048576
self.path = path
self.mount_name = name
self.run_event = run_event
- # As of Ubuntu 12.10 / Fedora 18, the file move/rename command is running agonisingly slowly
- # A hackish workaround is to replace it with the standard python function
- self.use_gnome_file_operations = False
def check_termination_request(self):
"""
@@ -69,63 +70,39 @@ class BackupFiles(multiprocessing.Process):
def update_progress(self, amount_downloaded, total):
- # first check if process is being terminated
self.amount_downloaded = amount_downloaded
- if not self.terminate_queue.empty():
- # it is - cancel the current copy
- self.cancel_copy.cancel()
- else:
- if not self.total_reached:
- chunk_downloaded = amount_downloaded - self.bytes_downloaded
- if (chunk_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
- self.bytes_downloaded = amount_downloaded
-
- if amount_downloaded == total:
- # this function is called a couple of times when total is reached
- self.total_reached = True
-
- self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.pid, self.total_downloaded + amount_downloaded, chunk_downloaded))))
- if amount_downloaded == total:
- self.bytes_downloaded = 0
-
- def progress_callback(self, amount_downloaded, total):
- self.update_progress(amount_downloaded, total)
-
- def progress_callback_no_update(self, amount_downloaded, total):
- """called when copying very small files"""
- pass
+ chunk_downloaded = amount_downloaded - self.bytes_downloaded
+ if (chunk_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
+ self.bytes_downloaded = amount_downloaded
+ self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.pid, self.total_downloaded + amount_downloaded, chunk_downloaded))))
+ if amount_downloaded == total:
+ self.bytes_downloaded = 0
def backup_additional_file(self, dest_dir, full_file_name):
"""Backs up small files like XMP or THM files"""
- source = gio.File(full_file_name)
dest_name = os.path.join(dest_dir, os.path.split(full_file_name)[1])
- if self.use_gnome_file_operations:
+ try:
logger.debug("Backing up additional file %s...", dest_name)
- dest=gio.File(dest_name)
- try:
- source.copy(dest, self.progress_callback_no_update, cancellable=None)
- logger.debug("...backing up additional file %s succeeded", dest_name)
- except gio.Error, inst:
- logger.error("Failed to backup file %s: %s", full_file_name, inst)
- else:
- try:
- logger.debug("Using python to back up additional file %s...", dest_name)
- shutil.copy(full_file_name, dest_name)
- logger.debug("...backing up additional file %s succeeded", dest_name)
- except:
- logger.error("Backup of %s failed", full_file_name)
+ shutil.copyfile(full_file_name, dest_name)
+ logger.debug("...backing up additional file %s succeeded", dest_name)
+ except:
+ logger.error("Backup of %s failed", full_file_name)
+
+ try:
+ copy_file_metadata(full_file_name, dest_name, logger)
+ except:
+ logger.error("Unknown error updating filesystem metadata when copying %s", full_file_name)
def run(self):
- self.cancel_copy = gio.Cancellable()
self.bytes_downloaded = 0
self.total_downloaded = 0
while True:
self.amount_downloaded = 0
- move_succeeded, rpd_file, path_suffix, backup_duplicate_overwrite, download_count = self.results_pipe.recv()
+ move_succeeded, do_backup, rpd_file, path_suffix, backup_duplicate_overwrite, verify_file, download_count = self.results_pipe.recv()
if rpd_file is None:
# this is a termination signal
return None
@@ -138,11 +115,9 @@ class BackupFiles(multiprocessing.Process):
backup_succeeded = False
self.scan_pid = rpd_file.scan_pid
- if move_succeeded:
+ if move_succeeded and do_backup:
self.total_reached = False
- source = gio.File(path=rpd_file.download_full_file_name)
-
if path_suffix is None:
dest_base_dir = self.path
else:
@@ -154,63 +129,107 @@ class BackupFiles(multiprocessing.Process):
dest_dir,
rpd_file.download_name)
- subfolder = gio.File(path=dest_dir)
- if not subfolder.query_exists(cancellable=None):
+ if not os.path.isdir(dest_dir):
# create the subfolders on the backup path
try:
logger.debug("Creating subfolder %s on backup device %s...", dest_dir, self.mount_name)
- subfolder.make_directory_with_parents(cancellable=gio.Cancellable())
+ os.makedirs(dest_dir)
logger.debug("...backup subfolder created")
- except gio.Error, inst:
+ except IOError as inst:
# There is a tiny chance directory may have been created by
# another process between the time it takes to query and
# the time it takes to create a new directory.
# Ignore such errors.
- if inst.code <> gio.ERROR_EXISTS:
+ if inst.errno <> errno.EEXIST:
logger.error("Failed to create backup subfolder: %s", dest_dir)
- logger.error(inst)
+ msg = "%s %s", inst.errno, inst.strerror
+ logger.error(msg)
rpd_file.add_problem(None, pn.BACKUP_DIRECTORY_CREATION, self.mount_name)
- rpd_file.add_extra_detail('%s%s' % (pn.BACKUP_DIRECTORY_CREATION, self.mount_name), inst)
+ rpd_file.add_extra_detail('%s%s' % (pn.BACKUP_DIRECTORY_CREATION, self.mount_name), msg)
rpd_file.error_title = _('Backing up error')
rpd_file.error_msg = \
_("Destination directory could not be created: %(directory)s\n") % \
- {'directory': subfolder, } + \
+ {'directory': dest_dir, } + \
_("Source: %(source)s\nDestination: %(destination)s") % \
{'source': rpd_file.download_full_file_name,
'destination': backup_full_file_name} + "\n" + \
- _("Error: %(inst)s") % {'inst': inst}
+ _("Error: %(inst)s") % {'inst': msg}
- dest = gio.File(path=backup_full_file_name)
- if backup_duplicate_overwrite:
- flags = gio.FILE_COPY_OVERWRITE
- else:
- flags = gio.FILE_COPY_NONE
- if self.use_gnome_file_operations:
+ backup_already_exists = os.path.exists(backup_full_file_name)
+ if backup_already_exists:
+ if backup_duplicate_overwrite:
+ rpd_file.add_problem(None, pn.BACKUP_EXISTS_OVERWRITTEN, self.mount_name)
+ msg = _("Backup %(file_type)s overwritten") % {'file_type': rpd_file.title}
+ else:
+ rpd_file.add_problem(None, pn.BACKUP_EXISTS, self.mount_name)
+ msg = _("%(file_type)s not backed up") % {'file_type': rpd_file.title_capitalized}
+
+ rpd_file.error_title = _("Backup of %(file_type)s already exists") % {'file_type': rpd_file.title}
+ rpd_file.error_msg = \
+ _("Source: %(source)s\nDestination: %(destination)s") % \
+ {'source': rpd_file.download_full_file_name, 'destination': backup_full_file_name} + "\n" + msg
+
+ if backup_already_exists and not backup_duplicate_overwrite:
+ logger.warning(msg)
+ else:
try:
logger.debug("Backing up file %s on device %s...", download_count, self.mount_name)
- source.copy(dest, self.progress_callback, flags,
- cancellable=self.cancel_copy)
+
+ dest = io.open(backup_full_file_name, 'wb', self.io_buffer)
+ src = io.open(rpd_file.download_full_file_name, 'rb', self.io_buffer)
+ total = rpd_file.size
+ amount_downloaded = 0
+ while True:
+ # first check if process is being terminated
+ if self.check_termination_request():
+ logger.debug("Closing partially written temporary file")
+ dest.close()
+ src.close()
+ return None
+ else:
+ chunk = src.read(self.io_buffer)
+ if chunk:
+ dest.write(chunk)
+ amount_downloaded += len(chunk)
+ self.update_progress(amount_downloaded, total)
+ else:
+ break
+ dest.close()
+ src.close()
backup_succeeded = True
+ if verify_file:
+ md5 = hashlib.md5(open(backup_full_file_name).read()).hexdigest()
+ if md5 <> rpd_file.md5:
+ backup_succeeded = False
+ logger.critical("%s file verification FAILED", rpd_file.name)
+ logger.critical("The %s did not back up correctly!", rpd_file.title)
+ rpd_file.add_problem(None, pn.BACKUP_VERIFICATION_FAILED, self.mount_name)
+ rpd_file.error_title = rpd_file.problem.get_title()
+ rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
+ {'problem': rpd_file.problem.get_problems(),
+ 'file': rpd_file.download_full_file_name}
+
logger.debug("...backing up file %s on device %s succeeded", download_count, self.mount_name)
- except gio.Error, inst:
- fileNotBackedUpMessageDisplayed = True
+ if backup_already_exists:
+ logger.warning(msg)
+ except (IOError, OSError) as inst:
+ logger.error("Backup of %s failed", backup_full_file_name)
+ msg = "%s %s", inst.errno, inst.strerror
rpd_file.add_problem(None, pn.BACKUP_ERROR, self.mount_name)
- rpd_file.add_extra_detail('%s%s' % (pn.BACKUP_ERROR, self.mount_name), inst)
+ rpd_file.add_extra_detail('%s%s' % (pn.BACKUP_ERROR, self.mount_name), msg)
rpd_file.error_title = _('Backing up error')
rpd_file.error_msg = \
_("Source: %(source)s\nDestination: %(destination)s") % \
{'source': rpd_file.download_full_file_name, 'destination': backup_full_file_name} + "\n" + \
- _("Error: %(inst)s") % {'inst': inst}
+ _("Error: %(inst)s") % {'inst': msg}
logger.error("%s:\n%s", rpd_file.error_title, rpd_file.error_msg)
- else:
- try:
- logger.debug("Using python to back up file %s on device %s...", download_count, self.mount_name)
- shutil.copy(rpd_file.download_full_file_name, backup_full_file_name)
- backup_succeeded = True
- logger.debug("...backing up file %s on device %s succeeded", download_count, self.mount_name)
- except:
- logger.error("Backup of %s failed", backup_full_file_name)
+
+ if backup_succeeded:
+ try:
+ copy_file_metadata(rpd_file.download_full_file_name, backup_full_file_name, logger)
+ except:
+ logger.error("Unknown error updating filesystem metadata when copying %s", rpd_file.download_full_file_name)
if not backup_succeeded:
@@ -232,11 +251,11 @@ class BackupFiles(multiprocessing.Process):
self.total_downloaded += rpd_file.size
bytes_not_downloaded = rpd_file.size - self.amount_downloaded
- if bytes_not_downloaded:
+ if bytes_not_downloaded and do_backup:
self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.pid, self.total_downloaded, bytes_not_downloaded))))
self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_FILE,
- (backup_succeeded, rpd_file))))
+ (backup_succeeded, do_backup, rpd_file))))
diff --git a/rapid/config.py b/rapid/config.py
index 1a12ea7..631bf47 100644
--- a/rapid/config.py
+++ b/rapid/config.py
@@ -1,5 +1,5 @@
# -*- coding: latin1 -*-
-### Copyright (C) 2007 - 2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2007 - 2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
### USA
-version = '0.4.7'
+version = '0.4.10'
GCONF_KEY="/apps/rapid-photo-downloader"
diff --git a/rapid/copyfiles.py b/rapid/copyfiles.py
index 5bc243c..c63e60f 100644
--- a/rapid/copyfiles.py
+++ b/rapid/copyfiles.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import os
import random
import string
-import gio
-
import logging
logger = multiprocessing.get_logger()
@@ -34,16 +32,51 @@ import rpdfile
import problemnotification as pn
import config
import thumbnail as tn
-
+import io
+import shutil
+import stat
+import hashlib
from gettext import gettext as _
+def copy_file_metadata(src, dst, logger=None):
+ """Copy all stat info (mode bits, atime, mtime, flags) from src to dst.
+
+ Adapated from python's shutil.copystat.
+
+ Necessary because with some NTFS file systems, there can be problems
+ with setting filesystem metadata like permissions and modification time"""
+
+ st = os.stat(src)
+ mode = stat.S_IMODE(st.st_mode)
+ try:
+ os.utime(dst, (st.st_atime, st.st_mtime))
+ except OSError as inst:
+ if logger:
+ logger.warning("Couldn't adjust file modification time when copying %s. %s: %s", src, inst.errno, inst.strerror)
+ try:
+ os.chmod(dst, mode)
+ except OSError as inst:
+ if logger:
+ logger.warning("Couldn't adjust file permissions when copying %s. %s: %s", src, inst.errno, inst.strerror)
+
+ if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
+ try:
+ os.chflags(dst, st.st_flags)
+ except OSError as inst:
+ for err in 'EOPNOTSUPP', 'ENOTSUP':
+ if hasattr(errno, err) and inst.errno == getattr(errno, err):
+ break
+ else:
+ raise
+
class CopyFiles(multiprocessing.Process):
"""
Copies files from source to temporary directory, giving them a random name
"""
def __init__(self, photo_download_folder, video_download_folder, files,
+ verify_file,
modify_files_during_download, modify_pipe,
scan_pid,
batch_size_MB, results_pipe, terminate_queue,
@@ -52,15 +85,18 @@ class CopyFiles(multiprocessing.Process):
self.results_pipe = results_pipe
self.terminate_queue = terminate_queue
self.batch_size_bytes = batch_size_MB * 1048576 # * 1024 * 1024
+ self.io_buffer = 1048576
self.photo_download_folder = photo_download_folder
self.video_download_folder = video_download_folder
self.files = files
+ self.verify_file = verify_file
self.modify_files_during_download = modify_files_during_download
self.modify_pipe = modify_pipe
self.scan_pid = scan_pid
self.no_files= len(self.files)
self.run_event = run_event
+
def check_termination_request(self):
"""
Check to see this process has not been requested to immediately terminate
@@ -74,29 +110,13 @@ class CopyFiles(multiprocessing.Process):
def update_progress(self, amount_downloaded, total):
- # first check if process is being terminated
- if not self.terminate_queue.empty():
- # it is - cancel the current copy
- self.cancel_copy.cancel()
- else:
- if not self.total_reached:
- chunk_downloaded = amount_downloaded - self.bytes_downloaded
- if (chunk_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
- self.bytes_downloaded = amount_downloaded
- if amount_downloaded == total:
- # this function is called a couple of times when total is reached
- self.total_reached = True
-
- self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.total_downloaded + amount_downloaded, chunk_downloaded))))
- if amount_downloaded == total:
- self.bytes_downloaded = 0
-
- def progress_callback(self, amount_downloaded, total):
- self.update_progress(amount_downloaded, total)
- def thm_progress_callback(self, amount_downloaded, total):
- # we don't care about tracking download progress for tiny THM files!
- pass
+ chunk_downloaded = amount_downloaded - self.bytes_downloaded
+ if (chunk_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
+ self.bytes_downloaded = amount_downloaded
+ self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.total_downloaded + amount_downloaded, chunk_downloaded))))
+ if amount_downloaded == total:
+ self.bytes_downloaded = 0
def run(self):
@@ -108,8 +128,6 @@ class CopyFiles(multiprocessing.Process):
self.bytes_downloaded = 0
self.total_downloaded = 0
- self.cancel_copy = gio.Cancellable()
-
self.create_temp_dirs()
# Send the location of both temporary directories, so they can be
@@ -126,7 +144,6 @@ class CopyFiles(multiprocessing.Process):
for i in range(self.no_files):
rpd_file = self.files[i]
- self.total_reached = False
# pause if instructed by the caller
self.run_event.wait()
@@ -134,8 +151,6 @@ class CopyFiles(multiprocessing.Process):
if self.check_termination_request():
return None
- source = gio.File(path=rpd_file.full_file_name)
-
#generate temporary name 5 digits long, no extension
temp_name = ''.join(random.choice(filename_characters) for i in xrange(5))
@@ -143,19 +158,42 @@ class CopyFiles(multiprocessing.Process):
self._get_dest_dir(rpd_file.file_type),
temp_name)
rpd_file.temp_full_file_name = temp_full_file_name
- dest = gio.File(path=temp_full_file_name)
copy_succeeded = False
+
+ src_bytes = ''
+
try:
- source.copy(dest, self.progress_callback, cancellable=self.cancel_copy)
+ dest = io.open(temp_full_file_name, 'wb', self.io_buffer)
+ src = io.open(rpd_file.full_file_name, 'rb', self.io_buffer)
+ total = rpd_file.size
+ amount_downloaded = 0
+ while True:
+ # first check if process is being terminated
+ if self.check_termination_request():
+ logger.debug("Closing partially written temporary file")
+ dest.close()
+ src.close()
+ return None
+ else:
+ chunk = src.read(self.io_buffer)
+ if chunk:
+ dest.write(chunk)
+ src_bytes += chunk
+ amount_downloaded += len(chunk)
+ self.update_progress(amount_downloaded, total)
+ else:
+ break
+ dest.close()
+ src.close()
copy_succeeded = True
- except gio.Error, inst:
+ except (IOError, OSError) as inst:
rpd_file.add_problem(None,
pn.DOWNLOAD_COPYING_ERROR_W_NO,
{'filetype': rpd_file.title})
rpd_file.add_extra_detail(
pn.DOWNLOAD_COPYING_ERROR_W_NO_DETAIL,
- {'errorno': inst.code, 'strerror': inst.message})
+ {'errorno': inst.errno, 'strerror': inst.strerror})
rpd_file.status = config.STATUS_DOWNLOAD_FAILED
@@ -167,38 +205,68 @@ class CopyFiles(multiprocessing.Process):
logger.error("Failed to download file: %s", rpd_file.full_file_name)
logger.error(inst)
self.update_progress(rpd_file.size, rpd_file.size)
+ except:
+ rpd_file.add_problem(None,
+ pn.DOWNLOAD_COPYING_ERROR,
+ {'filetype': rpd_file.title})
+ rpd_file.add_extra_detail(
+ pn.DOWNLOAD_COPYING_ERROR_DETAIL,
+ _("An unknown error occurred"))
+
+ rpd_file.status = config.STATUS_DOWNLOAD_FAILED
+
+ rpd_file.error_title = rpd_file.problem.get_title()
+ rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
+ {'problem': rpd_file.problem.get_problems(),
+ 'file': rpd_file.full_file_name}
+
+ logger.error("Failed to download file: %s", rpd_file.full_file_name)
+ self.update_progress(rpd_file.size, rpd_file.size)
# increment this amount regardless of whether the copy actually
# succeeded or not. It's neccessary to keep the user informed.
self.total_downloaded += rpd_file.size
+ try:
+ copy_file_metadata(rpd_file.full_file_name, temp_full_file_name, logger)
+ except:
+ logger.error("Unknown error updating filesystem metadata when copying %s", rpd_file.full_file_name)
+
# copy THM (video thumbnail file) if there is one
if copy_succeeded and rpd_file.thm_full_name:
- source = gio.File(path=rpd_file.thm_full_name)
# reuse video's file name
temp_thm_full_name = temp_full_file_name + '__rpd__thm'
- dest = gio.File(path=temp_thm_full_name)
try:
- source.copy(dest, self.thm_progress_callback, cancellable=self.cancel_copy)
+ shutil.copyfile(rpd_file.thm_full_name, temp_thm_full_name)
rpd_file.temp_thm_full_name = temp_thm_full_name
logger.debug("Copied video THM file %s", rpd_file.temp_thm_full_name)
- except gio.Error, inst:
+ except (IOError, OSError) as inst:
logger.error("Failed to download video THM file: %s", rpd_file.thm_full_name)
+ logger.error("%s: %s", inst.errno, inst.strerror)
+ try:
+ copy_file_metadata(rpd_file.thm_full_name, temp_thm_full_name. logger)
+ except:
+ logger.error("Unknown error updating filesystem metadata when copying %s", rpd_file.thm_full_name)
+
else:
temp_thm_full_name = None
#copy audio file if there is one
if copy_succeeded and rpd_file.audio_file_full_name:
- source = gio.File(path=rpd_file.audio_file_full_name)
# reuse photo's file name
temp_audio_full_name = temp_full_file_name + '__rpd__audio'
- dest = gio.File(path=temp_audio_full_name)
try:
- source.copy(dest, self.thm_progress_callback, cancellable=self.cancel_copy)
+ shutil.copyfile(rpd_file.audio_file_full_name, temp_audio_full_name)
rpd_file.temp_audio_full_name = temp_audio_full_name
logger.debug("Copied audio file %s", rpd_file.temp_audio_full_name)
- except gio.Error, inst:
+ except (IOError, OSError) as inst:
logger.error("Failed to download audio file: %s", rpd_file.audio_file_full_name)
+ logger.error("%s: %s", inst.errno, inst.strerror)
+ try:
+ copy_file_metadata(rpd_file.audio_file_full_name, temp_audio_full_name, logger)
+ except:
+ logger.error("Unknown error updating filesystem metadata when copying %s", rpd_file.audio_file_full_name)
+
if copy_succeeded and rpd_file.generate_thumbnail:
@@ -211,6 +279,9 @@ class CopyFiles(multiprocessing.Process):
thumbnail = None
thumbnail_icon = None
+ if copy_succeeded and self.verify_file:
+ rpd_file.md5 = hashlib.md5(src_bytes).hexdigest()
+
if rpd_file.metadata is not None:
rpd_file.metadata = None
@@ -254,8 +325,10 @@ class CopyFiles(multiprocessing.Process):
self.photo_temp_dir = self.video_temp_dir = None
if self.photo_download_folder is not None:
self.photo_temp_dir = self._create_temp_dir(self.photo_download_folder)
+ logger.debug("Photo temporary directory: %s", self.photo_temp_dir)
if self.video_download_folder is not None:
- self.video_temp_dir = self._create_temp_dir(self.photo_download_folder)
+ self.video_temp_dir = self._create_temp_dir(self.video_download_folder)
+ logger.debug("Video temporary directory: %s", self.video_temp_dir)
diff --git a/rapid/downloadtracker.py b/rapid/downloadtracker.py
index 5a9e452..e489903 100644
--- a/rapid/downloadtracker.py
+++ b/rapid/downloadtracker.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -37,7 +37,7 @@ class DownloadTracker:
def __init__(self):
self.file_types_present_by_scan_pid = dict()
self._refresh_values()
-
+
def _refresh_values(self):
""" these values are reset when a download is completed"""
self.size_of_download_in_bytes_by_scan_pid = dict()
@@ -50,8 +50,8 @@ class DownloadTracker:
self.no_files_in_download_by_scan_pid = dict()
self.no_photos_in_download_by_scan_pid = dict()
self.no_videos_in_download_by_scan_pid = dict()
-
-
+
+
# 'Download count' tracks the index of the file being downloaded
# into the list of files that need to be downloaded -- much like
# a counter in a for loop, e.g. 'for i in list', where i is the counter
@@ -72,19 +72,19 @@ class DownloadTracker:
self.total_bytes_to_download = 0
self.backups_performed_by_unique_id = dict()
self.auto_delete = dict()
-
+
def set_no_backup_devices(self, no_photo_backup_devices, no_video_backup_devices):
self.no_photo_backup_devices = no_photo_backup_devices
self.no_video_backup_devices = no_video_backup_devices
- self.no_backup_devices = no_photo_backup_devices + no_video_backup_devices
-
- def get_no_backup_devices(self):
- """
- Returns how many devices are being used to backup files of each type
- Return value is an integer tuple: photo and video
- """
- return (self.no_photo_backup_devices, self.no_video_backup_devices)
-
+ #~ self.no_backup_devices = no_photo_backup_devices + no_video_backup_devices
+#~
+ #~ def get_no_backup_devices(self):
+ #~ """
+ #~ Returns how many devices are being used to backup files of each type
+ #~ Return value is an integer tuple: photo and video
+ #~ """
+ #~ return (self.no_photo_backup_devices, self.no_video_backup_devices)
+
def init_stats(self, scan_pid, photo_size_in_bytes, video_size_in_bytes, no_photos_to_download, no_videos_to_download):
no_files = no_photos_to_download + no_videos_to_download
self.no_files_in_download_by_scan_pid[scan_pid] = no_files
@@ -95,7 +95,7 @@ class DownloadTracker:
bytes = photo_size_in_bytes + video_size_in_bytes
# rename_chunk is used to account for the time it takes to rename a file
# it is arbitrarily set to 10% of the time it takes to copy it
- # this makes a difference to the user when they're downloading from a
+ # this makes a difference to the user when they're downloading from a
# a high speed source
self.rename_chunk[scan_pid] = bytes / 10 / no_files
self.size_of_download_in_bytes_by_scan_pid[scan_pid] = bytes + self.rename_chunk[scan_pid] * no_files
@@ -108,34 +108,34 @@ class DownloadTracker:
self.video_failures[scan_pid] = 0
self.warnings[scan_pid] = 0
self.total_bytes_backed_up_by_scan_pid[scan_pid] = 0
-
+
def get_no_files_in_download(self, scan_pid):
return self.no_files_in_download_by_scan_pid[scan_pid]
-
+
def get_no_files_downloaded(self, scan_pid, file_type):
if file_type == FILE_TYPE_PHOTO:
return self.photos_downloaded.get(scan_pid, 0)
else:
return self.videos_downloaded.get(scan_pid, 0)
-
+
def get_no_files_failed(self, scan_pid, file_type):
if file_type == FILE_TYPE_PHOTO:
return self.photo_failures.get(scan_pid, 0)
else:
return self.video_failures.get(scan_pid, 0)
-
+
def get_no_warnings(self, scan_pid):
return self.warnings.get(scan_pid, 0)
-
+
def add_to_auto_delete(self, rpd_file):
if rpd_file.scan_pid in self.auto_delete:
self.auto_delete[rpd_file.scan_pid].append(rpd_file.full_file_name)
else:
self.auto_delete[rpd_file.scan_pid] = [rpd_file.full_file_name,]
-
+
def get_files_to_auto_delete(self, scan_pid):
return self.auto_delete[scan_pid]
-
+
def clear_auto_delete(self, scan_pid):
if scan_pid in self.auto_delete:
del self.auto_delete[scan_pid]
@@ -143,7 +143,7 @@ class DownloadTracker:
def file_backed_up(self, unique_id):
self.backups_performed_by_unique_id[unique_id] = \
self.backups_performed_by_unique_id.get(unique_id, 0) + 1
-
+
def all_files_backed_up(self, unique_id, file_type):
if unique_id in self.backups_performed_by_unique_id:
if file_type == FILE_TYPE_PHOTO:
@@ -154,10 +154,10 @@ class DownloadTracker:
logger.critical("Unexpected unique_id in self.backups_performed_by_unique_id")
return True
-
+
def file_downloaded_increment(self, scan_pid, file_type, status):
self.files_downloaded[scan_pid] += 1
-
+
if status <> STATUS_DOWNLOAD_FAILED and status <> STATUS_DOWNLOAD_AND_BACKUP_FAILED:
if file_type == FILE_TYPE_PHOTO:
self.photos_downloaded[scan_pid] += 1
@@ -165,7 +165,7 @@ class DownloadTracker:
else:
self.videos_downloaded[scan_pid] += 1
self.total_videos_downloaded += 1
-
+
if status == STATUS_DOWNLOADED_WITH_WARNING or status == STATUS_BACKUP_PROBLEM:
self.warnings[scan_pid] += 1
self.total_warnings += 1
@@ -176,66 +176,66 @@ class DownloadTracker:
else:
self.video_failures[scan_pid] += 1
self.total_video_failures += 1
-
+
def get_percent_complete(self, scan_pid):
"""
Returns a float representing how much of the download
- has been completed
+ has been completed
"""
-
- # when calculating the percentage, there are three components:
+
+ # when calculating the percentage, there are three components:
# copy (download), rename ('rename_chunk'), and backup
percent_complete = (((float(
- self.total_bytes_copied_by_scan_pid[scan_pid])
+ self.total_bytes_copied_by_scan_pid[scan_pid])
+ self.rename_chunk[scan_pid] * self.files_downloaded[scan_pid])
+ self.total_bytes_backed_up_by_scan_pid[scan_pid])
- / (self.size_of_download_in_bytes_by_scan_pid[scan_pid] +
+ / (self.size_of_download_in_bytes_by_scan_pid[scan_pid] +
self.size_of_photo_backup_in_bytes_by_scan_pid[scan_pid] +
self.size_of_video_backup_in_bytes_by_scan_pid[scan_pid]
)) * 100
-
+
return percent_complete
-
+
def get_overall_percent_complete(self):
total = 0
for scan_pid in self.total_bytes_copied_by_scan_pid:
total += (self.total_bytes_copied_by_scan_pid[scan_pid] +
- (self.rename_chunk[scan_pid] *
+ (self.rename_chunk[scan_pid] *
self.files_downloaded[scan_pid]))
-
+
percent_complete = float(total) / self.total_bytes_to_download
return percent_complete
-
+
def set_total_bytes_copied(self, scan_pid, total_bytes):
self.total_bytes_copied_by_scan_pid[scan_pid] = total_bytes
-
+
def increment_bytes_backed_up(self, scan_pid, chunk_downloaded):
self.total_bytes_backed_up_by_scan_pid[scan_pid] += chunk_downloaded
-
+
def set_download_count_for_file(self, unique_id, download_count):
self.download_count_for_file_by_unique_id[unique_id] = download_count
-
+
def get_download_count_for_file(self, unique_id):
return self.download_count_for_file_by_unique_id[unique_id]
-
+
def set_download_count(self, scan_pid, download_count):
self.download_count_by_scan_pid[scan_pid] = download_count
-
+
def get_file_types_present(self, scan_pid):
return self.file_types_present_by_scan_pid[scan_pid]
-
+
def set_file_types_present(self, scan_pid, file_types_present):
self.file_types_present_by_scan_pid[scan_pid] = file_types_present
-
+
def no_errors_or_warnings(self):
"""
Return True if there were no errors or warnings in the download
else return False
"""
return (self.total_warnings == 0 and
- self.total_photo_failures == 0 and
+ self.total_photo_failures == 0 and
self.total_video_failures == 0)
-
+
def purge(self, scan_pid):
del self.no_files_in_download_by_scan_pid[scan_pid]
del self.size_of_download_in_bytes_by_scan_pid[scan_pid]
@@ -246,7 +246,7 @@ class DownloadTracker:
del self.photo_failures[scan_pid]
del self.video_failures[scan_pid]
del self.warnings[scan_pid]
-
+
def purge_all(self):
self._refresh_values()
@@ -255,42 +255,42 @@ class DownloadTracker:
class TimeCheck:
"""
Record times downloads commmence and pause - used in calculating time
- remaining.
-
+ remaining.
+
Also tracks and reports download speed.
-
+
Note: This is completely independent of the file / subfolder naming
preference "download start time"
"""
-
+
def __init__(self):
- # set the number of seconds gap with which to measure download time remaing
+ # set the number of seconds gap with which to measure download time remaing
self.download_time_gap = 3
-
+
self.reset()
-
+
def reset(self):
self.mark_set = False
self.total_downloaded_so_far = 0
self.total_download_size = 0
self.size_mark = 0
-
+
def increment(self, bytes_downloaded):
self.total_downloaded_so_far += bytes_downloaded
-
+
def set_download_mark(self):
if not self.mark_set:
self.mark_set = True
- self.time_mark = time.time()
-
+ self.time_mark = time.time()
+
def pause(self):
self.mark_set = False
def check_for_update(self):
now = time.time()
update = now > (self.download_time_gap + self.time_mark)
-
+
if update:
amt_time = now - self.time_mark
self.time_mark = now
@@ -299,9 +299,9 @@ class TimeCheck:
download_speed = "%1.1f" % (amt_downloaded / 1048576 / amt_time) +_("MB/s")
else:
download_speed = None
-
+
return (update, download_speed)
-
+
class TimeForDownload:
# used to store variables, see below
pass
@@ -313,7 +313,7 @@ class TimeRemaining:
gap = 3
def __init__(self):
self.clear()
-
+
def set(self, scan_pid, size):
t = TimeForDownload()
t.time_remaining = None
@@ -322,7 +322,7 @@ class TimeRemaining:
t.size_mark = 0
t.time_mark = time.time()
self.times[scan_pid] = t
-
+
def update(self, scan_pid, bytes_downloaded):
if scan_pid in self.times:
self.times[scan_pid].downloaded += bytes_downloaded
@@ -337,21 +337,21 @@ class TimeRemaining:
amt_to_download = float(self.times[scan_pid].size) - self.times[scan_pid].downloaded
if timefraction:
self.times[scan_pid].time_remaining = amt_to_download / timefraction
-
+
def _time_estimates(self):
for t in self.times:
yield self.times[t].time_remaining
-
+
def time_remaining(self):
return max(self._time_estimates())
def set_time_mark(self, scan_pid):
if scan_pid in self.times:
self.times[scan_pid].time_mark = time.time()
-
+
def clear(self):
- self.times = {}
-
+ self.times = {}
+
def remove(self, scan_pid):
if scan_pid in self.times:
del self.times[scan_pid]
diff --git a/rapid/filemodify.py b/rapid/filemodify.py
index 98921ae..49a4d24 100644
--- a/rapid/filemodify.py
+++ b/rapid/filemodify.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
import os.path, fractions
import subprocess
+import hashlib
import multiprocessing
import logging
logger = multiprocessing.get_logger()
@@ -28,9 +29,14 @@ import rpdmultiprocessing as rpdmp
import rpdfile
import metadataxmp as mxmp
import subfolderfile
+import config
+import problemnotification as pn
+
+from gettext import gettext as _
WRITE_XMP_INPLACE = rpdfile.NON_RAW_IMAGE_EXTENSIONS + ['dng']
+
def lossless_rotate(jpeg):
"""using exiftran, performs a lossless, inplace translation of a jpeg, preserving time stamps"""
try:
@@ -40,17 +46,20 @@ def lossless_rotate(jpeg):
except OSError:
v = None
return v
-
+
class FileModify(multiprocessing.Process):
- def __init__(self, auto_rotate_jpeg, focal_length, results_pipe, terminate_queue,
+ def __init__(self, auto_rotate_jpeg, focal_length, verify_file,
+ refresh_md5_on_file_change, results_pipe, terminate_queue,
run_event):
multiprocessing.Process.__init__(self)
self.results_pipe = results_pipe
self.terminate_queue = terminate_queue
self.run_event = run_event
-
+
self.auto_rotate_jpeg = auto_rotate_jpeg
self.focal_length = focal_length
+ self.verify_file = verify_file
+ self.refresh_md5_on_file_change = refresh_md5_on_file_change
def check_termination_request(self):
"""
@@ -61,17 +70,17 @@ class FileModify(multiprocessing.Process):
# terminate immediately
return True
return False
-
+
def create_rational(self, value):
return '%s/%s' % (value.numerator, value.denominator)
-
+
def run(self):
-
+
download_count = 0
copy_finished = False
- while not copy_finished:
+ while not copy_finished:
logger.debug("Finished %s. Getting next task.", download_count)
-
+
data = self.results_pipe.recv()
if len(data) > 2:
rpd_file, download_count, temp_full_file_name, thumbnail_icon, thumbnail, copy_finished = data
@@ -83,61 +92,89 @@ class FileModify(multiprocessing.Process):
return None
# pause if instructed by the caller
self.run_event.wait()
-
+
if self.check_termination_request():
return None
-
- if self.auto_rotate_jpeg and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
- if rpd_file.extension in rpdfile.JPEG_EXTENSIONS:
- lossless_rotate(rpd_file.temp_full_file_name)
-
- xmp_sidecar = None
- # check to see if focal length and aperture data should be manipulated
- if self.focal_length is not None and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
- if subfolderfile.load_metadata(rpd_file, temp_file=True):
- a = rpd_file.metadata.aperture()
- if a == '0.0':
- logger.info("Adjusting focal length and aperture for %s (%s)", rpd_file.temp_full_file_name, rpd_file.name)
-
- new_focal_length = fractions.Fraction(self.focal_length,1)
- new_aperture = fractions.Fraction(8,1)
- if rpd_file.extension in WRITE_XMP_INPLACE:
- try:
- rpd_file.metadata["Exif.Photo.FocalLength"] = new_focal_length
- rpd_file.metadata["Exif.Photo.FNumber"] = new_aperture
- rpd_file.metadata.write(preserve_timestamps=True)
- logger.debug("Wrote new focal length and aperture to %s (%s)", rpd_file.temp_full_file_name, rpd_file.name)
- except:
- logger.error("failed to write new focal length and aperture to %s (%s)!", rpd_file.temp_full_file_name, rpd_file.name)
- else:
- # write to xmp sidecar
- xmp_sidecar = mxmp.XmpMetadataSidecar(rpd_file.temp_full_file_name)
- xmp_sidecar.set_exif_value('FocalLength', self.create_rational(new_focal_length))
- xmp_sidecar.set_exif_value('FNumber', self.create_rational(new_aperture))
- # store values in rpd_file, so they can be used in the subfolderfile process
- rpd_file.new_focal_length = new_focal_length
- rpd_file.new_aperture = new_aperture
-
- if False:
- xmp_sidecar.set_contact_url('http://www.website.net')
- xmp_sidecar.set_contact_email('user@email.com')
-
- if xmp_sidecar is not None:
- # need to write out xmp sidecar
- o = xmp_sidecar.write_xmp_sidecar()
- logger.debug("Wrote XMP sidecar file")
- logger.debug("exiv2 output: %s", o)
- rpd_file.temp_xmp_full_name = rpd_file.temp_full_file_name + '.xmp'
-
-
+
copy_succeeded = True
+ redo_md5 = False
+
+ if self.verify_file:
+ logger.debug("Verifying file %s....", rpd_file.name)
+ md5 = hashlib.md5(open(temp_full_file_name).read()).hexdigest()
+ if md5 <> rpd_file.md5:
+ logger.critical("%s file verification FAILED", rpd_file.name)
+ logger.critical("The %s did not download correctly!", rpd_file.title)
+
+ rpd_file.status = config.STATUS_DOWNLOAD_FAILED
+
+ rpd_file.add_problem(None, pn.FILE_VERIFICATION_FAILED,
+ {'filetype': rpd_file.title})
+ rpd_file.error_title = rpd_file.problem.get_title()
+ rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
+ {'problem': rpd_file.problem.get_problems(),
+ 'file': rpd_file.full_file_name}
+ copy_succeeded = False
+ else:
+ logger.debug("....file %s verified", rpd_file.name)
+
+
+ if copy_succeeded:
+ if self.auto_rotate_jpeg and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
+ if rpd_file.extension in rpdfile.JPEG_EXTENSIONS:
+ lossless_rotate(rpd_file.temp_full_file_name)
+ redo_md5 = True
+
+ xmp_sidecar = None
+ # check to see if focal length and aperture data should be manipulated
+ if self.focal_length is not None and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
+ if subfolderfile.load_metadata(rpd_file, temp_file=True):
+ a = rpd_file.metadata.aperture()
+ if a == '0.0':
+ logger.info("Adjusting focal length and aperture for %s (%s)", rpd_file.temp_full_file_name, rpd_file.name)
+
+ new_focal_length = fractions.Fraction(self.focal_length,1)
+ new_aperture = fractions.Fraction(8,1)
+ if rpd_file.extension in WRITE_XMP_INPLACE:
+ try:
+ rpd_file.metadata["Exif.Photo.FocalLength"] = new_focal_length
+ rpd_file.metadata["Exif.Photo.FNumber"] = new_aperture
+ rpd_file.metadata.write(preserve_timestamps=True)
+ redo_md5 = True
+ logger.debug("Wrote new focal length and aperture to %s (%s)", rpd_file.temp_full_file_name, rpd_file.name)
+ except:
+ logger.error("failed to write new focal length and aperture to %s (%s)!", rpd_file.temp_full_file_name, rpd_file.name)
+ else:
+ # write to xmp sidecar
+ xmp_sidecar = mxmp.XmpMetadataSidecar(rpd_file.temp_full_file_name)
+ xmp_sidecar.set_exif_value('FocalLength', self.create_rational(new_focal_length))
+ xmp_sidecar.set_exif_value('FNumber', self.create_rational(new_aperture))
+ # store values in rpd_file, so they can be used in the subfolderfile process
+ rpd_file.new_focal_length = new_focal_length
+ rpd_file.new_aperture = new_aperture
+
+
+
+ if xmp_sidecar is not None:
+ # need to write out xmp sidecar
+ o = xmp_sidecar.write_xmp_sidecar()
+ logger.debug("Wrote XMP sidecar file")
+ logger.debug("exiv2 output: %s", o)
+ rpd_file.temp_xmp_full_name = rpd_file.temp_full_file_name + '.xmp'
+
+ if self.refresh_md5_on_file_change and redo_md5:
+ rpd_file.md5 = hashlib.md5(open(temp_full_file_name).read()).hexdigest()
+
+
+
rpd_file.metadata = None #purge metadata, as it cannot be pickled
-
- self.results_pipe.send((rpdmp.CONN_PARTIAL,
+
+
+ self.results_pipe.send((rpdmp.CONN_PARTIAL,
(copy_succeeded, rpd_file, download_count,
- temp_full_file_name,
+ temp_full_file_name,
thumbnail_icon, thumbnail)))
-
- self.results_pipe.send((rpdmp.CONN_COMPLETE, None))
-
-
+
+ self.results_pipe.send((rpdmp.CONN_COMPLETE, None))
+
+
diff --git a/rapid/glade3/about.ui b/rapid/glade3/about.ui
index 0a82e74..c9a775a 100644
--- a/rapid/glade3/about.ui
+++ b/rapid/glade3/about.ui
@@ -9,7 +9,7 @@
<property name="icon">rapid-photo-downloader.svg</property>
<property name="type_hint">dialog</property>
<property name="program_name">Rapid Photo Downloader</property>
- <property name="copyright">Copyright Damon Lynch 2007-12</property>
+ <property name="copyright">Copyright Damon Lynch 2007-14</property>
<property name="comments" translatable="yes">Import your photos and videos efficiently and reliably</property>
<property name="website">http://www.damonlynch.net/rapid</property>
<property name="license">Rapid Photo Downloader is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
@@ -21,6 +21,7 @@ You should have received a copy of the GNU General Public License along with Rap
<property name="translator_credits">Anton Alyab'ev &lt;subeditor@dolgopa.org&gt;
Lőrincz András &lt;level.andrasnak@gmail.com&gt;
Michel Ange &lt;michelange@wanadoo.fr&gt;
+Adolfo Jayme Barrientos &lt;fitoschido@gmail.com&gt;
Alain J. Baudrez &lt;a.baudrez@gmail.com&gt;
Kevin Brubeck Unhammer &lt;unhammer@fsfe.org&gt;
Bert &lt;crinbert@yahoo.com&gt;
@@ -31,6 +32,7 @@ Tauno Erik &lt;tauno.erik@gmail.com&gt;
Sergiy Gavrylov &lt;sergiovana@bigmir.net&gt;
Emanuele Grande &lt;caccolangrifata@gmail.com&gt;
Torben Gundtofte-Bruun &lt;torben@g-b.dk&gt;
+Joachim Johansson &lt;joachim.j@gmail.com&gt;
Miroslav Matejaš &lt;silverspace@ubuntu-hr.org&gt;
Nicolás M. Zahlut &lt;nzahlut@live.com&gt;
Erik M
diff --git a/rapid/glade3/prefs.ui b/rapid/glade3/prefs.ui
index 2e0e53c..d4e6229 100644
--- a/rapid/glade3/prefs.ui
+++ b/rapid/glade3/prefs.ui
@@ -1,4 +1,4 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
@@ -13,6 +13,7 @@
<property name="page_increment">10</property>
</object>
<object class="GtkDialog" id="preferencesdialog">
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Preferences: Rapid Photo Downloader</property>
@@ -21,15 +22,61 @@
<property name="default_height">500</property>
<property name="icon">rapid-photo-downloader.svg</property>
<property name="type_hint">dialog</property>
- <signal name="destroy" handler="on_preferencesdialog_destroy"/>
- <signal name="response" handler="on_preferencesdialog_response"/>
+ <signal name="destroy" handler="on_preferencesdialog_destroy" swapped="no"/>
+ <signal name="response" handler="on_preferencesdialog_response" swapped="no"/>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="help_button1">
+ <property name="label">gtk-help</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label">gtk-close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
<child>
<object class="GtkHBox" id="hbox7">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
@@ -44,11 +91,13 @@
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="headers_visible">False</property>
- <signal name="cursor_changed" handler="on_treeview_cursor_changed"/>
+ <signal name="cursor-changed" handler="on_treeview_cursor_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
@@ -61,27 +110,33 @@
<child>
<object class="GtkVBox" id="folder_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox6">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-directory</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Photo Download Folders&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
@@ -93,15 +148,19 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -115,24 +174,47 @@
<child>
<object class="GtkHBox" id="hbox9">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label16">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="download_folder_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">7</property>
<property name="n_columns">3</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkLabel" id="example_photo_download_path_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;Example: /home/user/Pictures&lt;/i&gt;</property>
<property name="use_markup">True</property>
@@ -148,6 +230,7 @@
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Download Subfolders&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -162,6 +245,7 @@
<child>
<object class="GtkLabel" id="lblPhotos1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Download folder:</property>
</object>
@@ -177,6 +261,7 @@
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">Choose the download folder. Subfolders for the downloaded photos will be automatically created in this folder using the structure specified below.</property>
@@ -194,6 +279,7 @@
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Download Folder&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -206,6 +292,7 @@
<child>
<object class="GtkLabel" id="photo_subfolder_warning_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="wrap">True</property>
@@ -222,6 +309,7 @@
<child>
<object class="GtkVBox" id="subfolder_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<placeholder/>
</child>
@@ -234,40 +322,28 @@
<property name="y_padding">12</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label23">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -280,6 +356,7 @@
<child type="tab">
<object class="GtkLabel" id="download_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Photo Folders</property>
</object>
<packing>
@@ -289,46 +366,57 @@
<child>
<object class="GtkVBox" id="rename_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox7">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox10">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-convert</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Photo Rename&lt;/span&gt; </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -342,28 +430,34 @@
<child>
<object class="GtkHBox" id="hbox12">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label24">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="rename_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label42">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Photo Rename&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -375,11 +469,13 @@
<child>
<object class="GtkViewport" id="viewport2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="resize_mode">queue</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkVBox" id="rename_table_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<placeholder/>
</child>
@@ -389,17 +485,21 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="rename_example_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">3</property>
<child>
<object class="GtkLabel" id="label17">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes"> </property>
</object>
<packing>
@@ -411,6 +511,7 @@
<child>
<object class="GtkLabel" id="label15">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes"> </property>
</object>
<packing>
@@ -423,6 +524,7 @@
<child>
<object class="GtkLabel" id="new_name_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label">translators please ignore this</property>
@@ -439,6 +541,7 @@
<child>
<object class="GtkLabel" id="original_name_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label">translators please ignore this</property>
</object>
@@ -453,6 +556,7 @@
<child>
<object class="GtkLabel" id="label21">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;New:&lt;/i&gt;</property>
@@ -470,6 +574,7 @@
<child>
<object class="GtkLabel" id="label20">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;Original:&lt;/i&gt;</property>
<property name="use_markup">True</property>
@@ -486,6 +591,7 @@
<child>
<object class="GtkLabel" id="label14">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Example&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -504,20 +610,26 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label25">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -531,6 +643,7 @@
<child type="tab">
<object class="GtkLabel" id="rename_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Photo Rename</property>
</object>
<packing>
@@ -541,27 +654,33 @@
<child>
<object class="GtkVBox" id="folder_tab1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox12">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox11">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-directory</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label46">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Video Download Folders&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
@@ -573,15 +692,19 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator8">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -594,8 +717,10 @@
</child>
<child>
<object class="GtkHBox" id="folder_videos_cannot_be_downloaded_hbox">
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="folder_videos_cannot_be_downloaded_label">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="ypad">10</property>
@@ -604,6 +729,8 @@
<property name="wrap">True</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -617,24 +744,47 @@
<child>
<object class="GtkHBox" id="video_folders_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label57">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="video_download_folder_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">7</property>
<property name="n_columns">3</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkLabel" id="lblPhotos2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Download folder:</property>
</object>
@@ -650,6 +800,7 @@
<child>
<object class="GtkLabel" id="label59">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">Choose the download folder. Subfolders for the downloaded videos will be automatically created in this folder using the structure specified below.</property>
@@ -667,6 +818,7 @@
<child>
<object class="GtkLabel" id="label61">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Download Folder&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -679,6 +831,7 @@
<child>
<object class="GtkLabel" id="label62">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Download Subfolders&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -693,6 +846,7 @@
<child>
<object class="GtkLabel" id="example_video_download_path_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;Example: /home/user/Pictures&lt;/i&gt;</property>
<property name="use_markup">True</property>
@@ -708,6 +862,7 @@
<child>
<object class="GtkLabel" id="video_subfolder_warning_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="wrap">True</property>
@@ -724,6 +879,7 @@
<child>
<object class="GtkVBox" id="video_subfolder_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<placeholder/>
</child>
@@ -736,40 +892,28 @@
<property name="y_padding">12</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label63">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">2</property>
</packing>
@@ -782,6 +926,7 @@
<child type="tab">
<object class="GtkLabel" id="video_download_folder">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Video Folders</property>
</object>
<packing>
@@ -792,46 +937,57 @@
<child>
<object class="GtkVBox" id="video_rename_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox10">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox13">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-convert</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label32">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Video Rename&lt;/span&gt; </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -845,22 +1001,27 @@
<child>
<object class="GtkHBox" id="hbox14">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label44">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="rename_vbox1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="videos_cannot_be_downloaded_label">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Sorry, video downloading functionality disabled. To download videos, please install either the &lt;i&gt;hachoir metadata&lt;/i&gt; and &lt;i&gt;kaa metadata&lt;/i&gt; packages for python, or &lt;i&gt;exiftool&lt;/i&gt;.</property>
<property name="use_markup">True</property>
@@ -880,11 +1041,13 @@
<child>
<object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="resize_mode">queue</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkVBox" id="video_rename_table_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<placeholder/>
</child>
@@ -894,17 +1057,21 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="video_rename_example_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">3</property>
<child>
<object class="GtkLabel" id="label55">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes"> </property>
</object>
<packing>
@@ -916,6 +1083,7 @@
<child>
<object class="GtkLabel" id="label56">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes"> </property>
</object>
<packing>
@@ -928,6 +1096,7 @@
<child>
<object class="GtkLabel" id="video_new_name_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label">translators please ignore this</property>
@@ -944,6 +1113,7 @@
<child>
<object class="GtkLabel" id="video_original_name_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label">translators please ignore this</property>
</object>
@@ -958,6 +1128,7 @@
<child>
<object class="GtkLabel" id="new_video_filename_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;New:&lt;/i&gt;</property>
@@ -975,6 +1146,7 @@
<child>
<object class="GtkLabel" id="original_video_filename_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;i&gt;Original:&lt;/i&gt;</property>
<property name="use_markup">True</property>
@@ -991,6 +1163,7 @@
<child>
<object class="GtkLabel" id="example_video_filename_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Example&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -1009,20 +1182,26 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label60">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -1035,6 +1214,7 @@
<child type="tab">
<object class="GtkLabel" id="video_rename_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Video Rename</property>
</object>
<packing>
@@ -1045,46 +1225,57 @@
<child>
<object class="GtkVBox" id="rename_options_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox14">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox18">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="icon_name">input-keyboard</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Rename Options&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator5">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1098,9 +1289,11 @@
<child>
<object class="GtkVBox" id="reame_options_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="sequence_number_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">&lt;b&gt;Sequence Numbers&lt;/b&gt;</property>
@@ -1108,112 +1301,140 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="sequence_number_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="spacer_seq_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xpad">12</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="seq_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label47">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Specify the time in 24 hour format at which the &lt;i&gt;Downloads today&lt;/i&gt; sequence number should be reset.</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox23">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label49">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Day start:</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label51">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Downloads today:</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label52">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Stored number:</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label54">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xpad">6</property>
<property name="label" translatable="yes"> </property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="sequence_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox22">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkSpinButton" id="hour_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">2</property>
- <property name="invisible_char">&#x2022;</property>
+ <property name="invisible_char">•</property>
<property name="width_chars">2</property>
<property name="xalign">1</property>
<property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
<property name="adjustment">hour_adjustment</property>
<property name="numeric">True</property>
- <signal name="value_changed" handler="on_hour_spinbutton_value_changed"/>
+ <signal name="value-changed" handler="on_hour_spinbutton_value_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1224,6 +1445,7 @@
<child>
<object class="GtkLabel" id="label50">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">:</property>
</object>
<packing>
@@ -1237,13 +1459,17 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">2</property>
- <property name="invisible_char">&#x2022;</property>
+ <property name="invisible_char">•</property>
<property name="width_chars">2</property>
<property name="xalign">1</property>
<property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
<property name="adjustment">minute_adjustment</property>
<property name="numeric">True</property>
- <signal name="value_changed" handler="on_minute_spinbutton_value_changed"/>
+ <signal name="value-changed" handler="on_minute_spinbutton_value_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1254,17 +1480,20 @@
<child>
<object class="GtkLabel" id="label53">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes"> hh:mm</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -1277,44 +1506,55 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="synchronize_raw_jpg_checkbutton">
<property name="label" translatable="yes">Synchronize RAW + JPEG sequence numbers</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_synchronize_raw_jpg_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_synchronize_raw_jpg_checkbutton_toggled" swapped="no"/>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label48">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -1322,6 +1562,7 @@
<child>
<object class="GtkLabel" id="compatibility_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">&lt;b&gt;Compatibility with Other Operating Systems&lt;/b&gt;</property>
@@ -1329,31 +1570,37 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="compatibility_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="compatibility_spacer_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xpad">12</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="compatibility_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Specify whether photo, video and folder names should have any characters removed that are not allowed by other operating systems.</property>
<property name="wrap">True</property>
@@ -1365,12 +1612,13 @@
<child>
<object class="GtkCheckButton" id="strip_characters_checkbutton">
<property name="label" translatable="yes">Strip incompatible characters</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_strip_characters_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_strip_characters_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">2</property>
@@ -1380,27 +1628,34 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label33">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -1413,6 +1668,7 @@
<child type="tab">
<object class="GtkLabel" id="rename_options_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Rename Options</property>
</object>
<packing>
@@ -1423,46 +1679,57 @@
<child>
<object class="GtkVBox" id="job_codes_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="job_codes_header_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox188">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image77">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">rapid-photo-downloader-jobcode</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1340">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Job Codes&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator44">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1476,11 +1743,14 @@
<child>
<object class="GtkVBox" id="job_codes_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="job_code_vbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="job_code_label">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">&lt;b&gt;Job Codes&lt;/b&gt;</property>
@@ -1488,19 +1758,23 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="job_code_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="job_code_spacer_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -1521,25 +1795,29 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="job_code_button_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVButtonBox" id="job_code_vbuttonbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="add_job_code_button">
<property name="label" translatable="yes">_Add...</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="on_add_job_code_button_clicked"/>
+ <signal name="clicked" handler="on_add_job_code_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1550,11 +1828,12 @@
<child>
<object class="GtkButton" id="remove_job_code_button">
<property name="label">gtk-remove</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
- <signal name="clicked" handler="on_remove_job_code_button_clicked"/>
+ <signal name="clicked" handler="on_remove_job_code_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1565,11 +1844,12 @@
<child>
<object class="GtkButton" id="remove_all_job_code_button">
<property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">R_emove All</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="on_remove_all_job_code_button_clicked"/>
+ <signal name="clicked" handler="on_remove_all_job_code_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1579,17 +1859,22 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1598,11 +1883,15 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1614,6 +1903,7 @@
<child type="tab">
<object class="GtkLabel" id="job_codes_tab_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Job Codes</property>
</object>
<packing>
@@ -1624,46 +1914,57 @@
<child>
<object class="GtkVBox" id="device_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox15">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="icon_name">drive-removable-media</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label22">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Devices&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator6">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -1677,9 +1978,11 @@
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label41">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Devices</property>
@@ -1689,12 +1992,14 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Devices are from where to download photos and videos, such as cameras, memory cards or Portable Storage Devices.
@@ -1706,36 +2011,49 @@ You can download from multiple devices simultaneously, or you can specify a loca
<property name="wrap">True</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox16">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label26">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xpad">3</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="devices_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="row_spacing">3</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkCheckButton" id="autodetect_psd_checkbutton">
<property name="label" translatable="yes">Automatically detect Portable Storage Devices</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_autodetect_psd_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_autodetect_psd_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -1747,12 +2065,13 @@ You can download from multiple devices simultaneously, or you can specify a loca
<child>
<object class="GtkCheckButton" id="autodetect_device_checkbutton">
<property name="label" translatable="yes">Automatically detect devices</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_autodetect_device_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_autodetect_device_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">2</property>
@@ -1761,6 +2080,7 @@ You can download from multiple devices simultaneously, or you can specify a loca
<child>
<object class="GtkLabel" id="autodetect_image_devices_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">If you enable automatic detection of Portable Storage Devices, the entire device will be scanned. On large devices, this could take some time.
@@ -1775,44 +2095,49 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="bottom_attach">3</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label28">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox17">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkTable" id="devices2_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkLabel" id="device_location_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Location:</property>
@@ -1827,6 +2152,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="device_location_explanation_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
@@ -1838,11 +2164,10 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="right_attach">2</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -1852,21 +2177,25 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label30">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -1879,6 +2208,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child type="tab">
<object class="GtkLabel" id="device_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Devices</property>
</object>
<packing>
@@ -1889,57 +2219,69 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="device_options_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="exclusions_header_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image10">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-preferences</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="exclusionlabel">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Device Options&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox15">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="remebered_devices_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Remembered Paths</property>
@@ -1949,12 +2291,14 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="remembered_devices_explanation_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Remembered paths are those associated with devices that you have chosen to always scan or ignore when automatic detection of Portable Storage Devices is enabled.</property>
@@ -1962,19 +2306,23 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="remembered_devices_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="remembered_devices_spacer_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -1993,25 +2341,29 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="remembered_devices_button_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVButtonBox" id="remembered_devices_vbuttonbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="remove_remembered_device_button">
<property name="label">gtk-remove</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
- <signal name="clicked" handler="on_remove_remembered_device_button_clicked"/>
+ <signal name="clicked" handler="on_remove_remembered_device_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2022,11 +2374,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkButton" id="remove_all_remembered_device_button">
<property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">R_emove All</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="on_remove_all_remembered_device_button_clicked"/>
+ <signal name="clicked" handler="on_remove_all_remembered_device_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2036,22 +2389,29 @@ When this option is enabled, and a potential device is detected, you will be pro
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">0</property>
</packing>
@@ -2059,10 +2419,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="ignored_paths_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Ignored Paths</property>
@@ -2072,12 +2434,14 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ignored_paths_explanation_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Specify the ending portion of any paths you want ignored when scanning devices for photos or videos. Any path ending with the values below will not be scanned.</property>
@@ -2085,19 +2449,23 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="ignored_paths_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="ignored_paths_spacer_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -2116,25 +2484,29 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="ignored_paths_button_hbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVButtonBox" id="ignored_paths_vbuttonbox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="add_ignored_path_button">
<property name="label" translatable="yes">_Add...</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="on_add_ignored_path_button_clicked"/>
+ <signal name="clicked" handler="on_add_ignored_path_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2145,11 +2517,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkButton" id="remove_ignored_path_button">
<property name="label">gtk-remove</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
- <signal name="clicked" handler="on_remove_ignored_path_button_clicked"/>
+ <signal name="clicked" handler="on_remove_ignored_path_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2160,11 +2533,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkButton" id="remove_all_ignored_paths_button">
<property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">Re_move All</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
- <signal name="clicked" handler="on_remove_all_ignored_paths_button_clicked"/>
+ <signal name="clicked" handler="on_remove_all_ignored_paths_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2174,35 +2548,43 @@ When this option is enabled, and a potential device is detected, you will be pro
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="ignored_paths_use_re_checkbutton">
<property name="label" translatable="yes">Use _python-style regular expressions</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_ignored_paths_use_re_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_ignored_paths_use_re_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">0</property>
</packing>
@@ -2210,16 +2592,21 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
@@ -2231,6 +2618,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child type="tab">
<object class="GtkLabel" id="device_options_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Device Options</property>
</object>
<packing>
@@ -2241,45 +2629,56 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="backup_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox8">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox19">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image8">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="icon_name">drive-removable-media</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label27">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Backup&lt;/span&gt; </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator7">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -2293,8 +2692,10 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="vbox9">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label43">
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">&lt;b&gt;Backup&lt;/b&gt;</property>
@@ -2302,30 +2703,78 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox20">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label38">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="backup_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">10</property>
<property name="n_columns">4</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkLabel" id="backup_location_explanation_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
@@ -2343,12 +2792,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_detect_backup_checkbutton">
<property name="label" translatable="yes">Automatically detect backup devices</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_detect_backup_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_detect_backup_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -2361,6 +2811,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">You can have your photos and videos backed up to multiple locations as they are downloaded, e.g. external hard drives.</property>
@@ -2373,13 +2824,14 @@ When this option is enabled, and a potential device is detected, you will be pro
</child>
<child>
<object class="GtkCheckButton" id="backup_checkbutton">
- <property name="label" translatable="yes">Backup photos and videos when downloading</property>
+ <property name="label" translatable="yes">Back up photos and videos when downloading</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_backup_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_backup_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">4</property>
@@ -2391,6 +2843,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="backup_identifier_explanation_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">Specify the folder in which backups are stored on the device.
@@ -2409,6 +2862,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="backup_location_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Photo backup location:</property>
</object>
@@ -2424,6 +2878,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="backup_identifier_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Photo backup folder name:</property>
</object>
@@ -2440,6 +2895,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="backup_example_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="ypad">6</property>
@@ -2457,6 +2913,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="example_backup_path_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="ypad">6</property>
@@ -2475,8 +2932,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="invisible_char">&#x2022;</property>
- <signal name="changed" handler="on_backup_identifier_entry_changed"/>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="on_backup_identifier_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">3</property>
@@ -2490,6 +2951,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="video_backup_identifier_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Video backup folder name:</property>
</object>
@@ -2508,8 +2970,12 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="invisible_char">&#x2022;</property>
- <signal name="changed" handler="on_video_backup_identifier_entry_changed"/>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="on_video_backup_identifier_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">3</property>
@@ -2523,6 +2989,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="backup_video_location_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Video backup location:</property>
</object>
@@ -2535,70 +3002,35 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="y_options"></property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label39">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -2611,6 +3043,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child type="tab">
<object class="GtkLabel" id="backup_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">Backup</property>
</object>
<packing>
@@ -2621,45 +3054,56 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="automation_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox11">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox21">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-execute</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label31">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Miscellaneous&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator9">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -2673,9 +3117,11 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="vbox13">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label45">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">&lt;b&gt;Program Automation&lt;/b&gt;</property>
@@ -2683,35 +3129,48 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox24">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
- <object class="GtkLabel" id="label34"/>
+ <object class="GtkLabel" id="label34">
+ <property name="can_focus">False</property>
+ </object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="automation_table">
<property name="visible">True</property>
- <property name="n_rows">6</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">7</property>
<property name="n_columns">3</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkCheckButton" id="auto_unmount_checkbutton">
<property name="label" translatable="yes">Unmount ("eject") device upon download completion</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_unmount_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_unmount_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">3</property>
@@ -2722,13 +3181,14 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_startup_checkbutton">
<property name="label" translatable="yes">Start downloading at program startup</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_startup_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_startup_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">3</property>
@@ -2737,13 +3197,14 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_insertion_checkbutton">
<property name="label" translatable="yes">Start downloading upon device insertion</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_insertion_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_insertion_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">3</property>
@@ -2754,12 +3215,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_exit_checkbutton">
<property name="label" translatable="yes">Exit program when download completes</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_exit_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_exit_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">3</property>
@@ -2770,12 +3232,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_exit_force_checkbutton">
<property name="label" translatable="yes">Exit program even if download had warnings or errors</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_exit_force_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_exit_force_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -2787,12 +3250,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkCheckButton" id="auto_rotate_checkbutton">
<property name="label" translatable="yes">Automatically rotate JPEG images</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_auto_rotate_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_auto_rotate_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="right_attach">3</property>
@@ -2801,13 +3265,25 @@ When this option is enabled, and a potential device is detected, you will be pro
</packing>
</child>
<child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
+ <object class="GtkCheckButton" id="verify_file_checkbutton">
+ <property name="label" translatable="yes" comments="Checks files using MD5 to ensure they have downloaded and been saved correctly">Verify files as they are downloaded</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_verify_file_checkbutton_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">24</property>
<property name="position">1</property>
</packing>
@@ -2815,15 +3291,18 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label35">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -2831,6 +3310,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Performance</property>
@@ -2840,42 +3320,69 @@ When this option is enabled, and a potential device is detected, you will be pro
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox25">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
- <object class="GtkLabel" id="label19"/>
+ <object class="GtkLabel" id="label19">
+ <property name="can_focus">False</property>
+ </object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_columns">3</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkCheckButton" id="generate_thumbnails_checkbutton">
<property name="label" translatable="yes">Generate thumbnails (slower)</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_generate_thumbnails_checkbutton_toggled"/>
+ <signal name="toggled" handler="on_generate_thumbnails_checkbutton_toggled" swapped="no"/>
</object>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">24</property>
<property name="position">1</property>
</packing>
@@ -2883,21 +3390,26 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -2910,6 +3422,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child type="tab">
<object class="GtkLabel" id="miscillaneous_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Miscellaneous</property>
</object>
@@ -2921,46 +3434,57 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkVBox" id="error_tab">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox16">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkHBox" id="hbox26">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image9">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="stock">gtk-dialog-error</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label40">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Error Handling&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator10">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -2974,24 +3498,86 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkHBox" id="hbox27">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label36">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="error_table">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="n_rows">14</property>
<property name="n_columns">2</property>
<child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
<object class="GtkLabel" id="label13">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes"> </property>
</object>
@@ -3005,6 +3591,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label37">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Photo and Video Name Conflicts&lt;/b&gt;</property>
<property name="use_markup">True</property>
@@ -3017,13 +3604,14 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkRadioButton" id="add_identifier_radiobutton">
<property name="label" translatable="yes">Add unique identifier</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_add_identifier_radiobutton_toggled"/>
+ <signal name="toggled" handler="on_add_identifier_radiobutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -3036,6 +3624,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkRadioButton" id="skip_download_radiobutton">
<property name="label" translatable="yes">Skip download</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -3054,6 +3643,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label58">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">When a photo or video of the same name has already been downloaded, choose whether to skip downloading the file, or to add a unique indentifier.</property>
@@ -3070,6 +3660,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkLabel" id="label64">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">When backing up, choose whether to overwrite a file on the backup device that has the same name, or skip backing it up.</property>
@@ -3086,12 +3677,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkRadioButton" id="backup_duplicate_overwrite_radiobutton">
<property name="label" translatable="yes">Overwrite</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">backup_duplicate_skip_radiobutton</property>
- <signal name="toggled" handler="on_backup_duplicate_overwrite_radiobutton_toggled"/>
+ <signal name="toggled" handler="on_backup_duplicate_overwrite_radiobutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -3103,12 +3695,13 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<object class="GtkRadioButton" id="backup_duplicate_skip_radiobutton">
<property name="label" translatable="yes">Skip</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_backup_duplicate_skip_radiobutton_toggled"/>
+ <signal name="toggled" handler="on_backup_duplicate_skip_radiobutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -3117,80 +3710,28 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="bottom_attach">7</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label65">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">12</property>
<property name="position">1</property>
</packing>
@@ -3203,6 +3744,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<child type="tab">
<object class="GtkLabel" id="error_label">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Error Handling</property>
</object>
@@ -3213,12 +3755,16 @@ When this option is enabled, and a potential device is detected, you will be pro
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">6</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
@@ -3226,46 +3772,6 @@ When this option is enabled, and a potential device is detected, you will be pro
<child>
<placeholder/>
</child>
- <child internal-child="action_area">
- <object class="GtkHButtonBox" id="dialog-action_area2">
- <property name="visible">True</property>
- <property name="layout_style">end</property>
- <child>
- <object class="GtkButton" id="help_button1">
- <property name="label">gtk-help</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_stock">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- <property name="secondary">True</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="close_button">
- <property name="label">gtk-close</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_stock">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
</object>
</child>
<action-widgets>
diff --git a/rapid/glade3/rapid.ui b/rapid/glade3/rapid.ui
index 3ec67f4..75f145e 100644
--- a/rapid/glade3/rapid.ui
+++ b/rapid/glade3/rapid.ui
@@ -1,40 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.20"/>
+ <!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAction" id="about_action">
- <property name="label">About...</property>
- <property name="stock_id">gtk-about</property>
+ <property name="label" translatable="yes">About...</property>
<signal name="activate" handler="on_about_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="check_all_action">
- <property name="label">Check All</property>
+ <property name="label" translatable="yes">_Check All</property>
<signal name="activate" handler="on_check_all_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="check_all_photos_action">
- <property name="label">Check All Photos</property>
+ <property name="label" translatable="yes">Check All Photos</property>
<signal name="activate" handler="on_check_all_photos_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="check_all_videos_action">
- <property name="label">Check All Videos</property>
+ <property name="label" translatable="yes">Check All Videos</property>
<signal name="activate" handler="on_check_all_videos_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="donate_action">
- <property name="label">Make a Donation...</property>
+ <property name="label" translatable="yes">_Make a Donation...</property>
<signal name="activate" handler="on_donate_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="download_action">
- <property name="label">Download</property>
+ <property name="label" translatable="yes">Download</property>
<property name="icon_name">system-run</property>
<property name="sensitive">False</property>
<signal name="activate" handler="on_download_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="get_help_action">
- <property name="label">Get Help Online...</property>
- <property name="stock_id">gtk-help</property>
+ <property name="label" translatable="yes">_Get Help Online...</property>
<signal name="activate" handler="on_get_help_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="help_action">
- <property name="label">Help</property>
+ <property name="label" translatable="yes">Help</property>
<signal name="activate" handler="on_help_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="next_image_action">
@@ -42,7 +41,7 @@
<signal name="activate" handler="on_next_image_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="preferences_action">
- <property name="label">Preferences</property>
+ <property name="label" translatable="yes">Preferences</property>
<signal name="activate" handler="on_preferences_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="prev_image_action">
@@ -50,17 +49,15 @@
<signal name="activate" handler="on_prev_image_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="quit_action">
- <property name="label">Quit</property>
- <property name="stock_id">gtk-quit</property>
+ <property name="label" translatable="yes">Quit</property>
<signal name="activate" handler="on_quit_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="refresh_action">
- <property name="label">Refresh</property>
- <property name="stock_id">gtk-refresh</property>
+ <property name="label" translatable="yes">Refresh</property>
<signal name="activate" handler="on_refresh_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="report_problem_action">
- <property name="label">Report a Problem...</property>
+ <property name="label" translatable="yes">_Report a Problem...</property>
<property name="stock_id">gtk-dialog-warning</property>
<signal name="activate" handler="on_report_problem_action_activate" swapped="no"/>
</object>
@@ -73,11 +70,11 @@
<signal name="activate" handler="on_show_thumbnails_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="translate_action">
- <property name="label">Translate this Application...</property>
+ <property name="label" translatable="yes">Translate this Application...</property>
<signal name="activate" handler="on_translate_action_activate" swapped="no"/>
</object>
<object class="GtkAction" id="uncheck_all_action">
- <property name="label">Uncheck All</property>
+ <property name="label" translatable="yes">_Uncheck All</property>
<signal name="activate" handler="on_uncheck_all_action_activate" swapped="no"/>
</object>
<object class="GtkWindow" id="rapidapp">
@@ -97,9 +94,9 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkMenuItem" id="menuitem7">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property>
<child type="submenu">
@@ -108,18 +105,18 @@
<child>
<object class="GtkImageMenuItem" id="menu_download_pause">
<property name="label" translatable="yes">Download / Pause</property>
+ <property name="related_action">download_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">download_action</property>
<property name="use_stock">False</property>
<accelerator key="Return" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu_refresh">
+ <property name="related_action">refresh_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">refresh_action</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -128,9 +125,9 @@
<child>
<object class="GtkImageMenuItem" id="menu_preferences">
<property name="label">gtk-preferences</property>
+ <property name="related_action">preferences_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">preferences_action</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -139,9 +136,9 @@
<child>
<object class="GtkImageMenuItem" id="menu_quit">
<property name="label">gtk-quit</property>
+ <property name="related_action">quit_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">quit_action</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<accelerator key="q" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -153,9 +150,9 @@
</child>
<child>
<object class="GtkMenuItem" id="select_menuitem">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Select</property>
<property name="use_underline">True</property>
<child type="submenu">
@@ -163,33 +160,33 @@
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menu_check_all">
+ <property name="related_action">check_all_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">check_all_action</property>
<accelerator key="a" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_check_all_photos">
+ <property name="related_action">check_all_photos_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">check_all_photos_action</property>
<accelerator key="t" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_check_all_videos">
+ <property name="related_action">check_all_videos_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">check_all_videos_action</property>
<accelerator key="d" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_uncheck_all">
+ <property name="related_action">uncheck_all_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">uncheck_all_action</property>
<accelerator key="l" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
@@ -201,9 +198,9 @@
</child>
<child>
<object class="GtkMenuItem" id="menu_select_all_without_job_code">
+ <property name="use_action_appearance">False</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Select All Without _Job Code</property>
<property name="use_underline">True</property>
<accelerator key="j" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -211,9 +208,9 @@
</child>
<child>
<object class="GtkMenuItem" id="menu_select_all_with_job_code">
+ <property name="use_action_appearance">False</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Select All Wit_h Job Code</property>
<property name="use_underline">True</property>
<accelerator key="h" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -225,9 +222,9 @@
</child>
<child>
<object class="GtkMenuItem" id="menuitem10">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use_underline">True</property>
<child type="submenu">
@@ -236,21 +233,21 @@
<child>
<object class="GtkImageMenuItem" id="menu_zoom_in">
<property name="label">gtk-zoom-in</property>
+ <property name="use_action_appearance">False</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
- <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="equal" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu_zoom_out">
<property name="label">gtk-zoom-out</property>
+ <property name="use_action_appearance">False</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
- <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<accelerator key="minus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
@@ -264,9 +261,9 @@
</child>
<child>
<object class="GtkCheckMenuItem" id="menu_log_window">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Error Log</property>
<property name="use_underline">True</property>
<signal name="toggled" handler="on_menu_log_window_toggled" swapped="no"/>
@@ -274,9 +271,9 @@
</child>
<child>
<object class="GtkMenuItem" id="menu_clear">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Clear Completed Downloads</property>
<property name="use_underline">True</property>
</object>
@@ -289,20 +286,20 @@
</child>
<child>
<object class="GtkMenuItem" id="previous_image_menuitem">
+ <property name="use_action_appearance">False</property>
+ <property name="related_action">prev_image_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">prev_image_action</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Previous File</property>
<accelerator key="bracketleft" signal="activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="next_file_menuitem">
+ <property name="use_action_appearance">False</property>
+ <property name="related_action">next_image_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">next_image_action</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Next File</property>
<accelerator key="bracketright" signal="activate"/>
</object>
@@ -313,9 +310,9 @@
</child>
<child>
<object class="GtkMenuItem" id="help_menuitem">
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_underline">True</property>
<child type="submenu">
@@ -323,9 +320,9 @@
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="menu_get_help_online">
+ <property name="related_action">get_help_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">get_help_action</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<accelerator key="F1" signal="activate"/>
@@ -333,25 +330,25 @@
</child>
<child>
<object class="GtkMenuItem" id="menu_report_problem">
+ <property name="related_action">report_problem_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">report_problem_action</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_donate">
+ <property name="related_action">donate_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">donate_action</property>
<property name="label" translatable="yes">_Make a Donation...</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_translate">
+ <property name="related_action">translate_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">translate_action</property>
<property name="label" translatable="yes">_Translate this Application...</property>
<property name="use_underline">True</property>
</object>
@@ -364,11 +361,9 @@
</child>
<child>
<object class="GtkImageMenuItem" id="menu_about">
- <property name="label">gtk-about</property>
+ <property name="related_action">about_action</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="related_action">about_action</property>
- <property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
@@ -529,10 +524,10 @@
</child>
<child>
<object class="GtkButton" id="preview_button">
+ <property name="related_action">show_image_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">show_image_action</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
</object>
@@ -544,11 +539,10 @@
</child>
<child>
<object class="GtkButton" id="check_all_button">
- <property name="label" translatable="yes">_Check All</property>
+ <property name="related_action">check_all_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">check_all_action</property>
<property name="relief">none</property>
<property name="use_underline">True</property>
<property name="focus_on_click">False</property>
@@ -562,10 +556,10 @@
<child>
<object class="GtkButton" id="uncheck_all_button">
<property name="label" translatable="yes">_Uncheck All</property>
+ <property name="related_action">uncheck_all_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">uncheck_all_action</property>
<property name="relief">none</property>
<property name="use_underline">True</property>
<property name="focus_on_click">False</property>
@@ -638,10 +632,10 @@
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="next_image_button">
+ <property name="related_action">next_image_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">next_image_action</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<accelerator key="bracketright" signal="activate"/>
@@ -658,10 +652,10 @@
</child>
<child>
<object class="GtkButton" id="prev_image_button">
+ <property name="related_action">prev_image_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">prev_image_action</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<accelerator key="bracketleft" signal="activate"/>
@@ -691,10 +685,10 @@
<property name="can_focus">False</property>
<child>
<object class="GtkButton" id="thumbnails_button">
+ <property name="related_action">show_thumbnails_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">show_thumbnails_action</property>
<property name="relief">none</property>
<property name="use_underline">True</property>
<property name="focus_on_click">False</property>
@@ -723,10 +717,10 @@
<child>
<object class="GtkCheckButton" id="download_this_checkbutton">
<property name="label" translatable="yes">_Include in download</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
- <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<accelerator key="i" signal="activate"/>
@@ -813,10 +807,10 @@
<child>
<object class="GtkButton" id="help_button">
<property name="label">_Help</property>
+ <property name="related_action">help_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="related_action">help_action</property>
<property name="use_underline">True</property>
</object>
<packing>
@@ -828,12 +822,12 @@
<child>
<object class="GtkButton" id="download_button">
<property name="label" translatable="yes">_Download</property>
+ <property name="related_action">download_action</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
- <property name="related_action">download_action</property>
<property name="use_underline">True</property>
</object>
<packing>
diff --git a/rapid/metadataexiftool.py b/rapid/metadataexiftool.py
index 9be5ea7..2524da1 100755
--- a/rapid/metadataexiftool.py
+++ b/rapid/metadataexiftool.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -36,7 +36,7 @@ def version_info():
except OSError:
v = None
return v
-
+
EXIFTOOL_VERSION = version_info()
class ExifToolMetaData:
@@ -52,9 +52,9 @@ class ExifToolMetaData:
self.metadata_string_format = None
self.exiftool_error = "Error encountered using exiftool with file %s"
self.exiftool_output = "Unexpected output from exiftool with file %s"
-
+
def _get(self, key, missing):
-
+
if key == "VideoStreamType" or "FileNumber":
# special case: want exiftool's string formatting
if self.metadata_string_format is None:
@@ -69,13 +69,13 @@ class ExifToolMetaData:
except:
logger.error(self.exiftool_output, self.filename)
return missing
-
+
try:
v = self.metadata_string_format[0][key]
except:
return missing
return v
-
+
elif self.metadata is None:
# note: exiftool's string formatting is OFF (-n switch)
try:
@@ -89,22 +89,22 @@ class ExifToolMetaData:
except:
logger.error(self.exiftool_output, self.filename)
return missing
-
+
try:
v = self.metadata[0][key]
except:
return missing
return v
-
-
+
+
def date_time(self, missing=''):
- """
- Returns in python datetime format the date and time the image was
+ """
+ Returns in python datetime format the date and time the image was
recorded.
-
+
Trys to get value from key "DateTimeOriginal"
If that fails, tries "CreateDate"
-
+
Returns missing either metadata value is not present.
"""
d = self._get('DateTimeOriginal', None)
@@ -121,11 +121,11 @@ class ExifToolMetaData:
except:
logger.error("Error reading date metadata with file %s", self.filename)
return missing
-
+
return dt
else:
return missing
-
+
def time_stamp(self, missing=''):
"""
Returns a float value representing the time stamp, if it exists
@@ -137,32 +137,32 @@ class ExifToolMetaData:
else:
v = missing
return v
-
+
def file_number(self, missing=''):
v = self._get("FileNumber", None)
if v is not None:
return str(v)
else:
return missing
-
+
def width(self, missing=''):
v = self._get('ImageWidth', None)
if v is not None:
return str(v)
else:
return missing
-
+
def height(self, missing=''):
v = self._get('ImageHeight', None)
if v is not None:
return str(v)
else:
return missing
-
+
def length(self, missing=''):
"""
return the duration (length) of the video, rounded to the nearest second, in string format
- """
+ """
v = self._get("Duration", None)
if v is not None:
try:
@@ -181,7 +181,7 @@ class ExifToolMetaData:
v = self._get("FrameRate", None)
if v is None:
v = self._get("VideoFrameRate", None)
-
+
if v is None:
return missing
try:
@@ -189,7 +189,7 @@ class ExifToolMetaData:
except:
return missing
return v
-
+
def codec(self, stream=0, missing=''):
"""
value stream is ignored (kept for compatibilty with code calling kaa)
@@ -201,17 +201,17 @@ class ExifToolMetaData:
return v
else:
return missing
-
+
def fourcc(self, stream=0, missing=''):
"""
value stream is ignored (kept for compatibilty with code calling kaa)
"""
return self._get("CompressorID", missing)
-
+
if __name__ == '__main__':
import sys
-
-
+
+
if (len(sys.argv) != 2):
print 'Usage: ' + sys.argv[0] + ' path/to/video/containing/metadata'
sys.exit(0)
diff --git a/rapid/preferencesdialog.py b/rapid/preferencesdialog.py
index 295b6e7..a0767af 100644
--- a/rapid/preferencesdialog.py
+++ b/rapid/preferencesdialog.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2007-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2007-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -58,16 +58,16 @@ class PrefError(Exception):
"""
Make the preferences presentable to the user
"""
-
+
s = ''
for i in l:
if i <> ORDER_KEY:
s += "'" + i + "', "
return s[:-2]
- def __str__(self):
+ def __str__(self):
return self.msg
-
+
class PrefKeyError(PrefError):
def __init__(self, error):
value = error[0]
@@ -80,13 +80,13 @@ class PrefValueInvalidError(PrefKeyError):
def __init__(self, error):
value = error[0]
self.msg = "Preference value '%(value)s' is invalid" % {'value': value}
-
+
class PrefLengthError(PrefError):
def __init__(self, error):
self.msg = "These preferences are not well formed:" + "\n %s" % self.unpackList(error)
-
+
class PrefValueKeyComboError(PrefError):
- def __init__(self, error):
+ def __init__(self, error):
self.msg = error
@@ -107,7 +107,7 @@ def check_pref_valid(pref_defn, prefs, modulo=3):
else:
for i in range(0, len(prefs), modulo):
_check_pref_valid(pref_defn, prefs[i:i+modulo])
-
+
return True
def _check_pref_valid(pref_defn, prefs):
@@ -117,16 +117,16 @@ def _check_pref_valid(pref_defn, prefs):
if pref_defn.has_key(key):
-
+
next_pref_defn = pref_defn[key]
-
+
if value == None:
# value should never be None, at any time
raise PrefValueInvalidError((None, next_pref_defn))
if next_pref_defn and not value:
raise gn.PrefValueInvalidError((value, next_pref_defn))
-
+
if type(next_pref_defn) == type({}):
return _check_pref_valid(next_pref_defn, prefs[1:])
else:
@@ -174,13 +174,13 @@ def filter_subfolder_prefs(pref_list):
# must exit loop and try again
pref_list = pref_list[:i] + pref_list[i+3:]
break
-
+
return (prefs_changed, pref_list)
class Comboi18n(gtk.ComboBox):
- """ very simple i18n version of the venerable combo box
+ """ very simple i18n version of the venerable combo box
with one column displayed to the user.
-
+
This combo box has two columns:
1. the first contains the actual value and is invisible
2. the second contains the translation of the first column, and this is what
@@ -193,35 +193,35 @@ class Comboi18n(gtk.ComboBox):
self.pack_start(cell, True)
self.add_attribute(cell, 'text', 1)
# must name the combo box on pygtk used in Ubuntu 11.04, Fedora 15, etc.
- self.set_name('GtkComboBox')
-
+ self.set_name('GtkComboBox')
+
def append_text(self, text):
model = self.get_model()
model.append((text, _(text)))
-
+
def get_active_text(self):
model = self.get_model()
active = self.get_active()
if active < 0:
return None
- return model[active][0]
-
+ return model[active][0]
+
class PreferenceWidgets:
-
+
def __init__(self, default_row, default_prefs, pref_defn_L0, pref_list):
self.default_row = default_row
self.default_prefs = default_prefs
self.pref_defn_L0 = pref_defn_L0
self.pref_list = pref_list
-
+
def _create_combo(self, choices):
combobox = Comboi18n()
for text in choices:
combobox.append_text(text)
return combobox
-
+
def get_default_row(self):
- """
+ """
returns a list of default widgets
"""
return self.get_widgets_based_on_user_selection(self.default_row)
@@ -229,11 +229,11 @@ class PreferenceWidgets:
def _get_pref_widgets(self, pref_definition, prefs, widgets):
key = prefs[0]
value = prefs[1]
-
+
# supply a default value if the user has not yet chosen a value!
if not key:
key = pref_definition[ORDER_KEY][0]
-
+
if not key in pref_definition:
raise gn.PrefKeyError((key, pref_definition.keys()))
@@ -243,13 +243,13 @@ class PreferenceWidgets:
# the first widget will always be a combo box
widget0 = self._create_combo(list0)
widget0.set_active(list0.index(key))
-
+
widgets.append(widget0)
-
+
if key == TEXT:
widget1 = gtk.Entry()
widget1.set_text(value)
-
+
widgets.append(widget1)
widgets.append(None)
return
@@ -260,8 +260,8 @@ class PreferenceWidgets:
else:
next_pref_definition = pref_definition[key]
if type(next_pref_definition) == type({}):
- return self._get_pref_widgets(next_pref_definition,
- prefs[1:],
+ return self._get_pref_widgets(next_pref_definition,
+ prefs[1:],
widgets)
else:
if type(next_pref_definition) == type([]):
@@ -272,56 +272,56 @@ class PreferenceWidgets:
widget1.set_active(next_pref_definition.index(value))
except:
raise gn.PrefValueInvalidError((value, next_pref_definition))
-
+
widgets.append(widget1)
else:
widgets.append(None)
-
+
def _get_values_from_list(self):
for i in range(0, len(self.pref_list), 3):
- yield (self.pref_list[i], self.pref_list[i+1], self.pref_list[i+2])
-
+ yield (self.pref_list[i], self.pref_list[i+1], self.pref_list[i+2])
+
def get_widgets_based_on_prefs(self):
- """
+ """
Yields a list of widgets and their callbacks based on the users preferences.
-
- This list is equivalent to one row of preferences when presented to the
+
+ This list is equivalent to one row of preferences when presented to the
user in the Plus Minus Table.
"""
-
+
for L0, L1, L2 in self._get_values_from_list():
prefs = [L0, L1, L2]
widgets = []
self._get_pref_widgets(self.pref_defn_L0, prefs, widgets)
yield widgets
-
+
def get_widgets_based_on_user_selection(self, selection):
"""
Returns a list of widgets and their callbacks based on what the user has selected.
-
+
Selection is the values the user has chosen thus far in comboboxes.
It determines the contents of the widgets returned.
It should be a list of three values, with None for values not chosen.
For values which are None, the first value in the preferences
definition is chosen.
-
+
"""
widgets = []
-
+
self._get_pref_widgets(self.pref_defn_L0, selection, widgets)
return widgets
-
+
def check_prefs_for_validity(self):
"""
Checks preferences validity
"""
-
- return check_pref_valid(self.pref_defn_L0, self.pref_list)
+
+ return check_pref_valid(self.pref_defn_L0, self.pref_list)
class PhotoNamePrefs(PreferenceWidgets):
def __init__(self, pref_list):
- PreferenceWidgets.__init__(self,
+ PreferenceWidgets.__init__(self,
default_row = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE],
default_prefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE],
pref_defn_L0 = DICT_IMAGE_RENAME_L0,
@@ -334,11 +334,11 @@ class VideoNamePrefs(PreferenceWidgets):
default_prefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE],
pref_defn_L0 = DICT_VIDEO_RENAME_L0,
pref_list = pref_list)
-
+
class PhotoSubfolderPrefs(PreferenceWidgets):
def __init__(self, pref_list):
-
+
PreferenceWidgets.__init__(self,
default_row = [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]],
default_prefs = DEFAULT_SUBFOLDER_PREFS,
@@ -349,13 +349,13 @@ class PhotoSubfolderPrefs(PreferenceWidgets):
filtered, pref_list = filter_subfolder_prefs(self.pref_list)
if filtered:
self.pref_list = pref_list
-
+
def check_prefs_for_validity(self):
"""
Checks subfolder preferences validity above and beyond image name checks.
-
+
See parent method for full description.
-
+
Subfolders have additional requirments to that of file names.
"""
v = PreferenceWidgets.check_prefs_for_validity(self)
@@ -364,7 +364,7 @@ class PhotoSubfolderPrefs(PreferenceWidgets):
# 1. do not start with a separator
# 2. do not end with a separator
# 3. do not have two separators in a row
- # these three rules will ensure something else other than a
+ # these three rules will ensure something else other than a
# separator is specified
L1s = []
for i in range(0, len(self.pref_list), 3):
@@ -382,47 +382,47 @@ class PhotoSubfolderPrefs(PreferenceWidgets):
class VideoSubfolderPrefs(PhotoSubfolderPrefs):
def __init__(self, pref_list):
- PreferenceWidgets.__init__(self,
+ PreferenceWidgets.__init__(self,
default_row = [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]],
default_prefs = DEFAULT_VIDEO_SUBFOLDER_PREFS,
pref_defn_L0 = DICT_VIDEO_SUBFOLDER_L0,
pref_list = pref_list)
class QuestionDialog(gtk.Dialog):
- def __init__(self, parent_window, title, question, use_markup=False,
+ def __init__(self, parent_window, title, question, use_markup=False,
default_to_yes=True, post_choice_callback=None):
gtk.Dialog.__init__(self, title, None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_NO, gtk.RESPONSE_CANCEL,
+ (gtk.STOCK_NO, gtk.RESPONSE_CANCEL,
gtk.STOCK_YES, gtk.RESPONSE_OK))
-
- self.post_choice_callback = post_choice_callback
+
+ self.post_choice_callback = post_choice_callback
self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg'))
-
+
prompt_hbox = gtk.HBox()
-
+
icontheme = gtk.icon_theme_get_default()
- icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN)
+ icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN)
if icon:
image = gtk.Image()
image.set_from_pixbuf(icon)
prompt_hbox.pack_start(image, False, False, padding = 6)
-
+
prompt_label = gtk.Label(question)
prompt_label.set_use_markup(use_markup)
prompt_label.set_line_wrap(True)
prompt_hbox.pack_start(prompt_label, False, False, padding=6)
-
+
self.vbox.pack_start(prompt_hbox, padding=6)
self.set_border_width(6)
- self.set_has_separator(False)
-
- if default_to_yes:
+ self.set_has_separator(False)
+
+ if default_to_yes:
self.set_default_response(gtk.RESPONSE_OK)
else:
self.set_default_response(gtk.RESPONSE_CANCEL)
-
+
self.set_transient_for(parent_window)
self.show_all()
@@ -439,31 +439,31 @@ class RemoveAllJobCodeDialog(QuestionDialog):
_('Remove all Job Codes?'),
_('Should all Job Codes be removed?'),
post_choice_callback=post_choice_callback)
-
+
class RemoveAllRemeberedDevicesDialog(QuestionDialog):
def __init__(self, parent_window, post_choice_callback):
QuestionDialog.__init__(self, parent_window,
_('Remove all Remembered Paths?'),
_('Should all remembered paths be removed?'),
post_choice_callback=post_choice_callback)
-
+
class RemoveAllIgnoredPathsDialog(QuestionDialog):
def __init__(self, parent_window, post_choice_callback):
QuestionDialog.__init__(self, parent_window,
_('Remove all Ignored Paths?'),
_('Should all ignored paths be removed?'),
post_choice_callback=post_choice_callback)
-
+
class PhotoRenameTable(tpm.TablePlusMinus):
def __init__(self, preferencesdialog, adjust_scroll_window):
-
+
tpm.TablePlusMinus.__init__(self, 1, 3)
self.preferencesdialog = preferencesdialog
self.adjust_scroll_window = adjust_scroll_window
if not hasattr(self, "error_title"):
self.error_title = _("Error in Photo Rename preferences")
-
+
self.table_type = self.error_title[len("Error in "):]
self.i = 0
@@ -481,17 +481,17 @@ class PhotoRenameTable(tpm.TablePlusMinus):
self.get_preferencesdialog_prefs()
self.setup_prefs_factory()
-
+
try:
self.prefs_factory.check_prefs_for_validity()
-
- except (PrefValueInvalidError, PrefLengthError,
+
+ except (PrefValueInvalidError, PrefLengthError,
PrefValueKeyComboError, PrefKeyError), e:
logger.error(self.error_title)
logger.error("Sorry, these preferences contain an error:")
logger.error(format_pref_list_for_pretty_print(self.prefs_factory.pref_list))
-
+
# the preferences were invalid
# reset them to their default
@@ -502,18 +502,18 @@ class PhotoRenameTable(tpm.TablePlusMinus):
msg = "%s.\n" % e
msg += "Resetting to default values."
logger.error(msg)
-
-
- misc.run_dialog(self.error_title, msg,
+
+
+ misc.run_dialog(self.error_title, msg,
preferencesdialog,
gtk.MESSAGE_ERROR)
-
+
for row in self.prefs_factory.get_widgets_based_on_prefs():
self.append(row)
-
+
def update_preferences(self):
pref_list = []
- for row in self.pm_rows:
+ for row in self.pm_rows:
for col in range(self.pm_no_columns):
widget = row[col]
if widget:
@@ -533,24 +533,24 @@ class PhotoRenameTable(tpm.TablePlusMinus):
self.update_parentapp_prefs()
self.prefs_factory.pref_list = pref_list
self.update_example()
-
-
+
+
def scrollbar_visibility_change(self, widget, event):
if event.state == gtk.gdk.VISIBILITY_UNOBSCURED:
self.have_vertical_scrollbar = True
self.adjust_scroll_window.set_size_request(self.adjust_scroll_window.allocation.width + self.bump, -1)
-
+
def size_adjustment(self, widget, arg2):
"""
Adjust scrolledwindow width in preferences dialog to reflect width of image rename table
-
+
The algorithm is complicated by the need to take into account the presence of a vertical scrollbar,
which might be added as the user adds more rows
-
+
The pygtk code behaves inconsistently depending on the pygtk version
"""
-
+
if self.adjust_scroll_window:
self.have_vertical_scrollbar = self.scroll_bar.allocation.width > 1 or self.have_vertical_scrollbar
if not self.have_vertical_scrollbar:
@@ -560,31 +560,31 @@ class PhotoRenameTable(tpm.TablePlusMinus):
if self.allocation.width > self.adjust_scroll_window.allocation.width - self.bump:
self.adjust_scroll_window.set_size_request(self.allocation.width + self.bump, -1)
self.bump = 0
-
+
def get_preferencesdialog_prefs(self):
self.pref_list = self.preferencesdialog.prefs.image_rename
-
-
+
+
def setup_prefs_factory(self):
self.prefs_factory = PhotoNamePrefs(self.pref_list)
-
+
def update_parentapp_prefs(self):
self.preferencesdialog.prefs.image_rename = self.pref_list
-
+
def update_example_job_code(self):
job_code = self.preferencesdialog.prefs.get_sample_job_code()
if not job_code:
job_code = _('Job code')
#~ self.prefs_factory.setJobCode(job_code)
-
+
def update_example(self):
self.preferencesdialog.update_photo_rename_example()
-
+
def get_default_row(self):
return self.prefs_factory.get_default_row()
-
+
def on_combobox_changed(self, widget, row_position):
-
+
for col in range(self.pm_no_columns):
if self.pm_rows[row_position][col] == widget:
break
@@ -597,13 +597,13 @@ class PhotoRenameTable(tpm.TablePlusMinus):
selection.append(w.get_active_text())
else:
selection.append(w.get_text())
-
+
for i in range(col + 1, self.pm_no_columns):
selection.append('')
-
+
if col <> (self.pm_no_columns - 1):
widgets = self.prefs_factory.get_widgets_based_on_user_selection(selection)
-
+
for i in range(col + 1, self.pm_no_columns):
old_widget = self.pm_rows[row_position][i]
if old_widget:
@@ -618,7 +618,7 @@ class PhotoRenameTable(tpm.TablePlusMinus):
new_widget.show()
self.update_preferences()
-
+
def on_entry_changed(self, widget, row_position):
self.update_preferences()
@@ -627,30 +627,30 @@ class PhotoRenameTable(tpm.TablePlusMinus):
Update preferences, as a row has been added
"""
self.update_preferences()
-
+
# if this was the last row or 2nd to last row, and another has just been added, move vertical scrollbar down
if row_position in range(self.pm_no_rows - 3, self.pm_no_rows - 2):
adjustment = self.preferencesdialog.rename_scrolledwindow.get_vadjustment()
adjustment.set_value(adjustment.upper)
-
+
def on_row_deleted(self, row_position):
"""
Update preferences, as a row has been deleted
"""
- self.update_preferences()
+ self.update_preferences()
class VideoRenameTable(PhotoRenameTable):
- def __init__(self, preferencesdialog, adjust_scroll_window):
+ def __init__(self, preferencesdialog, adjust_scroll_window):
self.error_title = _("Error in Video Rename preferences")
PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window)
def get_preferencesdialog_prefs(self):
self.pref_list = self.preferencesdialog.prefs.video_rename
-
+
def setup_prefs_factory(self):
self.prefs_factory = VideoNamePrefs(self.pref_list)
-
+
def update_parentapp_prefs(self):
self.preferencesdialog.prefs.video_rename = self.pref_list
@@ -662,103 +662,103 @@ class SubfolderTable(PhotoRenameTable):
Table to display photo download subfolder preferences as part of preferences
dialog window.
"""
- def __init__(self, preferencesdialog, adjust_scroll_window):
+ def __init__(self, preferencesdialog, adjust_scroll_window):
self.error_title = _("Error in Photo Download Subfolders preferences")
PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window)
def get_preferencesdialog_prefs(self):
self.pref_list = self.preferencesdialog.prefs.subfolder
-
+
def setup_prefs_factory(self):
self.prefs_factory = PhotoSubfolderPrefs(self.pref_list)
-
+
def update_parentapp_prefs(self):
self.preferencesdialog.prefs.subfolder = self.pref_list
def update_example(self):
self.preferencesdialog.update_photo_download_folder_example()
-
+
class VideoSubfolderTable(PhotoRenameTable):
- def __init__(self, preferencesdialog, adjust_scroll_window):
+ def __init__(self, preferencesdialog, adjust_scroll_window):
self.error_title = _("Error in Video Download Subfolders preferences")
PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window)
def get_preferencesdialog_prefs(self):
self.pref_list = self.preferencesdialog.prefs.video_subfolder
-
+
def setup_prefs_factory(self):
self.prefs_factory = VideoSubfolderPrefs(self.pref_list)
-
+
def update_parentapp_prefs(self):
self.preferencesdialog.prefs.video_subfolder = self.pref_list
def update_example(self):
- self.preferencesdialog.update_video_download_folder_example()
+ self.preferencesdialog.update_video_download_folder_example()
class RemoveAllJobCodeDialog(gtk.Dialog):
def __init__(self, parent_window, post_choice_callback):
gtk.Dialog.__init__(self, _('Remove all Job Codes?'), None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_NO, gtk.RESPONSE_CANCEL,
+ (gtk.STOCK_NO, gtk.RESPONSE_CANCEL,
gtk.STOCK_YES, gtk.RESPONSE_OK))
-
- self.post_choice_callback = post_choice_callback
+
+ self.post_choice_callback = post_choice_callback
self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg'))
-
+
prompt_hbox = gtk.HBox()
-
+
icontheme = gtk.icon_theme_get_default()
- icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN)
+ icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN)
if icon:
image = gtk.Image()
image.set_from_pixbuf(icon)
prompt_hbox.pack_start(image, False, False, padding = 6)
-
+
prompt_label = gtk.Label(_('Should all Job Codes be removed?'))
prompt_label.set_line_wrap(True)
prompt_hbox.pack_start(prompt_label, False, False, padding=6)
-
+
self.vbox.pack_start(prompt_hbox, padding=6)
self.set_border_width(6)
- self.set_has_separator(False)
-
+ self.set_has_separator(False)
+
self.set_default_response(gtk.RESPONSE_OK)
-
+
self.set_transient_for(parent_window)
self.show_all()
self.connect('response', self.on_response)
-
+
def on_response(self, device_dialog, response):
user_selected = response == gtk.RESPONSE_OK
self.post_choice_callback(self, user_selected)
class JobCodeDialog(gtk.Dialog):
""" Dialog prompting for a job code"""
-
+
def __init__(self, parent_window, job_codes, default_job_code, post_job_code_entry_callback, entry_only):
# Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode
gtk.Dialog.__init__(self, _('Enter a Job Code'), None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
-
+
self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg'))
self.post_job_code_entry_callback = post_job_code_entry_callback
-
+
self.combobox = gtk.combo_box_entry_new_text()
for text in job_codes:
self.combobox.append_text(text)
-
+
self.job_code_hbox = gtk.HBox(homogeneous = False)
-
+
if len(job_codes) and not entry_only:
# Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode
task_label = gtk.Label(_('Enter a new Job Code, or select a previous one'))
else:
# Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode
- task_label = gtk.Label(_('Enter a new Job Code'))
+ task_label = gtk.Label(_('Enter a new Job Code'))
task_label.set_line_wrap(True)
task_hbox = gtk.HBox()
task_hbox.pack_start(task_label, False, False, padding=6)
@@ -766,13 +766,13 @@ class JobCodeDialog(gtk.Dialog):
label = gtk.Label(_('Job Code:'))
self.job_code_hbox.pack_start(label, False, False, padding=6)
self.job_code_hbox.pack_start(self.combobox, True, True, padding=6)
-
+
self.set_border_width(6)
self.set_has_separator(False)
# make entry box have entry completion
self.entry = self.combobox.child
-
+
completion = gtk.EntryCompletion()
completion.set_match_func(self.match_func)
completion.connect("match-selected",
@@ -780,32 +780,32 @@ class JobCodeDialog(gtk.Dialog):
completion.set_model(self.combobox.get_model())
completion.set_text_column(0)
self.entry.set_completion(completion)
-
+
# when user hits enter, close the dialog window
self.set_default_response(gtk.RESPONSE_OK)
self.entry.set_activates_default(True)
if default_job_code:
self.entry.set_text(default_job_code)
-
+
self.vbox.pack_start(task_hbox, False, False, padding = 6)
self.vbox.pack_start(self.job_code_hbox, False, False, padding=12)
-
+
self.set_transient_for(parent_window)
self.show_all()
self.connect('response', self.on_job_code_resp)
-
+
def match_func(self, completion, key, iter):
model = completion.get_model()
return model[iter][0].lower().startswith(self.entry.get_text().lower())
-
+
def on_completion_match(self, completion, model, iter):
self.entry.set_text(model[iter][0])
self.entry.set_position(-1)
def get_job_code(self):
return self.combobox.child.get_text()
-
+
def on_job_code_resp(self, jc_dialog, response):
user_chose_code = False
if response == gtk.RESPONSE_OK:
@@ -814,24 +814,24 @@ class JobCodeDialog(gtk.Dialog):
else:
logger.debug("Job Code not entered")
self.post_job_code_entry_callback(self, user_chose_code, self.get_job_code())
-
+
class IgnorePathDialog(gtk.Dialog):
""" Dialog prompting for a path to ignore when scanning devices"""
-
+
def __init__(self, parent_window, post_entry_callback):
gtk.Dialog.__init__(self, _('Enter a Path to Ignore'), None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
-
+
self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg'))
self.post_entry_callback = post_entry_callback
-
+
self.path_entry = gtk.Entry(max=0)
-
+
self.ignored_path_hbox = gtk.HBox(homogeneous = False)
-
- task_label = gtk.Label(_('Specify a path that will never be scanned for photos or videos'))
+
+ task_label = gtk.Label(_('Specify a path that will never be scanned for photos or videos'))
task_label.set_line_wrap(True)
task_hbox = gtk.HBox()
task_hbox.pack_start(task_label, False, False, padding=6)
@@ -839,17 +839,17 @@ class IgnorePathDialog(gtk.Dialog):
label = gtk.Label(_('Path:'))
self.ignored_path_hbox.pack_start(label, False, False, padding=6)
self.ignored_path_hbox.pack_start(self.path_entry, True, True, padding=6)
-
+
self.set_border_width(6)
self.set_has_separator(False)
-
+
# when user hits enter, close the dialog window
self.set_default_response(gtk.RESPONSE_OK)
self.path_entry.set_activates_default(True)
self.vbox.pack_start(task_hbox, False, False, padding = 6)
self.vbox.pack_start(self.ignored_path_hbox, False, False, padding=12)
-
+
self.set_transient_for(parent_window)
self.show_all()
self.connect('response', self.on_ignored_path_resp)
@@ -867,55 +867,55 @@ class IgnorePathDialog(gtk.Dialog):
class PreferencesDialog():
"""
Dialog window to show Rapid Photo Downloader preferences.
-
+
Is tightly integrated into main Rapid Photo Downloader window, i.e.
directly access members in class RapidApp.
"""
-
+
def __init__(self, rapidapp):
self.builder = gtk.Builder()
self.builder.set_translation_domain(config.APP_NAME)
self.builder.add_from_file(paths.share_dir("glade3/prefs.ui"))
self.builder.connect_signals(self)
-
+
self.dialog = self.preferencesdialog
self.widget = self.dialog
self.dialog.set_transient_for(rapidapp.rapidapp)
self.prefs = rapidapp.prefs
-
+
rapidapp.preferences_dialog_displayed = True
-
+
self.pref_dialog_startup = True
-
+
self.rapidapp = rapidapp
self._setup_tab_selector()
-
+
self._setup_control_spacing()
-
+
self.file_types = metadatavideo.file_types_to_download()
self._setup_sample_names()
-
+
# setup tabs
self._setup_photo_download_folder_tab()
self._setup_image_rename_tab()
self._setup_video_download_folder_tab()
- self._setup_video_rename_tab()
+ self._setup_video_rename_tab()
self._setup_rename_options_tab()
self._setup_job_code_tab()
self._setup_device_tab()
- self._setup_device_options_tab()
+ self._setup_device_options_tab()
self._setup_backup_tab()
self._setup_miscellaneous_tab()
self._setup_error_tab()
-
+
if not metadatavideo.DOWNLOAD_VIDEO:
self.disable_video_controls()
self.dialog.realize()
-
+
#set the width of the left column for selecting values
#note: this must be called after self.dialog.realize(), or else the width calculation will fail
width_of_widest_sel_row = self.treeview.get_background_area(1, self.treeview_column)[2]
@@ -929,7 +929,7 @@ class PreferencesDialog():
self.rename_scrolledwindow.set_size_request(self.rename_table.allocation.width + extra, -1)
self.dialog.show()
-
+
self.pref_dialog_startup = False
def __getattr__(self, key):
@@ -940,11 +940,11 @@ class PreferencesDialog():
setattr(self, key, widget)
return widget
raise AttributeError(key)
-
+
def on_preferencesdialog_destroy(self, widget):
""" Delete variables from memory that cause a file descriptor to be created on a mounted media"""
logger.debug("Preference window closing")
-
+
def _setup_tab_selector(self):
self.notebook.set_show_tabs(0)
self.model = gtk.ListStore(type(""))
@@ -959,44 +959,44 @@ class PreferencesDialog():
label = self.notebook.get_tab_label(c).get_text()
if not label.startswith("_"):
self.model.append( (label,) )
-
+
# select the first value in the list store
self.treeview.set_cursor(0,column)
-
+
def on_download_folder_filechooser_button_selection_changed(self, widget):
path = misc.get_folder_selection(widget)
if path:
self.prefs.download_folder = path
self.update_photo_download_folder_example()
-
+
def on_video_download_folder_filechooser_button_selection_changed(self, widget):
path = misc.get_folder_selection(widget)
if path:
self.prefs.video_download_folder = path
self.update_video_download_folder_example()
-
+
def on_backup_folder_filechooser_button_selection_changed(self, widget):
path = misc.get_folder_selection(widget)
- if path:
+ if path:
self.prefs.backup_location = path
self.update_backup_example()
-
+
def on_backup_video_folder_filechooser_button_selection_changed(self, widget):
path = misc.get_folder_selection(widget)
if path:
self.prefs.backup_video_location = path
self.update_backup_example()
-
+
def on_device_location_filechooser_button_selection_changed(self, widget):
path = misc.get_folder_selection(widget)
- if path:
+ if path:
self.prefs.device_location = path
-
+
def on_add_ignored_path_button_clicked(self, widget):
- i = IgnorePathDialog(parent_window = self.dialog,
+ i = IgnorePathDialog(parent_window = self.dialog,
post_entry_callback = self.add_ignored_path)
-
+
def add_ignored_path(self, dialog, user_chose_path, path):
dialog.destroy()
if user_chose_path:
@@ -1009,29 +1009,29 @@ class PreferencesDialog():
#scroll to the top
adjustment = self.ignored_paths_scrolledwindow.get_vadjustment()
adjustment.set_value(adjustment.lower)
-
+
def on_ignored_paths_use_re_checkbutton_toggled(self, checkbutton):
self.prefs.use_re_ignored_paths = checkbutton.get_active()
if self.prefs.use_re_ignored_paths and not self.pref_dialog_startup:
# check for invalid regular expressions
self.update_ignored_paths()
-
+
def on_remove_ignored_path_button_clicked(self, button):
self._remove_from_treeview(self.ignored_paths_treeview)
self.update_ignored_paths()
-
+
def on_remove_all_ignored_paths_button_clicked(self, button):
i = RemoveAllIgnoredPathsDialog(self.dialog, self.remove_all_ignored_paths)
-
+
def remove_all_ignored_paths(self, dialog, user_selected):
dialog.destroy()
if user_selected:
self.ignored_paths_liststore.clear()
- self.update_ignored_paths()
-
+ self.update_ignored_paths()
+
def on_remove_remembered_device_button_clicked(self, button):
"""
- uses remembered devices treeview to delete any removed items from the
+ uses remembered devices treeview to delete any removed items from the
device_whitelist and device_blacklist prefs
"""
blacklist = [i for i in self.prefs.device_blacklist if i]
@@ -1046,7 +1046,7 @@ class PreferencesDialog():
for i in range(0, no):
iter = iters[i]
if i == no - 1:
- path = model.get_path(iter)
+ path = model.get_path(iter)
v = self.remembered_devices_liststore.get_value(iter, 0)
if v in blacklist:
blacklist.remove(v)
@@ -1055,33 +1055,33 @@ class PreferencesDialog():
else:
logger.debug("Unknown remembered device %s", v)
model.remove(iter)
-
- # now that we removed the selection, play nice with
+
+ # now that we removed the selection, play nice with
# the user and select the next item
selection.select_path(path)
-
+
# if there was no selection that meant the user
- # removed the last entry, so we try to select the
+ # removed the last entry, so we try to select the
# last item
if not selection.path_is_selected(path):
row = path[0]-1
# test case for empty lists
if row >= 0:
- selection.select_path((row,))
-
+ selection.select_path((row,))
+
self.prefs.device_blacklist = blacklist
self.prefs.device_whitelist = whitelist
-
+
def on_remove_all_remembered_device_button_clicked(self, button):
r = RemoveAllRemeberedDevicesDialog(self.dialog, self.remove_all_remembered_devices)
-
+
def remove_all_remembered_devices(self, dialog, user_selected):
dialog.destroy()
if user_selected:
self.remembered_devices_liststore.clear()
self.prefs.device_blacklist = []
self.prefs.device_whitelist = []
-
+
def _setup_sample_names(self, use_dummy_data = False):
"""
If use_dummy_data is True, then samples will not attempt to get
@@ -1094,9 +1094,9 @@ class PreferencesDialog():
day_start = self.prefs.day_start,
downloads_today = self.prefs.downloads_today[1],
downloads_today_date = self.prefs.downloads_today[0])
- self.sequences = gn.Sequences(self.downloads_today_tracker,
+ self.sequences = gn.Sequences(self.downloads_today_tracker,
self.prefs.stored_sequence_no)
-
+
# get example photo and video data
if use_dummy_data:
self.sample_photo = None
@@ -1110,12 +1110,12 @@ class PreferencesDialog():
else:
self.sample_photo.sequences = self.sequences
self.sample_photo.download_start_time = datetime.datetime.now()
-
+
if self.sample_photo is None:
self.sample_photo = rpdfile.SamplePhoto(sequences=self.sequences)
-
+
self.sample_photo.job_code = job_code
-
+
self.sample_video = None
if metadatavideo.DOWNLOAD_VIDEO:
if not use_dummy_data:
@@ -1123,38 +1123,38 @@ class PreferencesDialog():
if self.sample_video is not None:
self.sample_video.load_metadata()
self.sample_video.sequences = self.sequences
- self.sample_video.download_start_time = datetime.datetime.now()
+ self.sample_video.download_start_time = datetime.datetime.now()
if self.sample_video is None:
self.sample_video = rpdfile.SampleVideo(sequences=self.sequences)
self.sample_video.job_code = job_code
-
+
def _setup_control_spacing(self):
"""
set spacing of some but not all controls
"""
-
- self._setup_table_spacing(self.download_folder_table)
- self._setup_table_spacing(self.video_download_folder_table)
- self.download_folder_table.set_row_spacing(2,
+
+ self._setup_table_spacing(self.download_folder_table)
+ self._setup_table_spacing(self.video_download_folder_table)
+ self.download_folder_table.set_row_spacing(2,
hd.VERTICAL_CONTROL_SPACE)
- self.video_download_folder_table.set_row_spacing(2,
+ self.video_download_folder_table.set_row_spacing(2,
hd.VERTICAL_CONTROL_SPACE)
self._setup_table_spacing(self.rename_example_table)
self._setup_table_spacing(self.video_rename_example_table)
self.devices_table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE)
self.automation_table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE)
-
+
self._setup_table_spacing(self.backup_table)
self.backup_table.set_col_spacing(1, hd.NESTED_CONTROLS_SPACE)
self.backup_table.set_col_spacing(2, hd.CONTROL_LABEL_SPACE)
self._setup_table_spacing(self.compatibility_table)
- self.compatibility_table.set_row_spacing(0,
- hd.VERTICAL_CONTROL_LABEL_SPACE)
+ self.compatibility_table.set_row_spacing(0,
+ hd.VERTICAL_CONTROL_LABEL_SPACE)
self._setup_table_spacing(self.error_table)
-
-
+
+
def _setup_table_spacing(self, table):
table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE)
table.set_col_spacing(1, hd.CONTROL_LABEL_SPACE)
@@ -1163,7 +1163,7 @@ class PreferencesDialog():
self.subfolder_table = SubfolderTable(self, None)
self.subfolder_vbox.pack_start(self.subfolder_table)
self.subfolder_table.show_all()
-
+
def _setup_video_subfolder_table(self):
self.video_subfolder_table = VideoSubfolderTable(self, None)
self.video_subfolder_vbox.pack_start(self.video_subfolder_table)
@@ -1176,17 +1176,17 @@ class PreferencesDialog():
self.prefs.download_folder)
self.download_folder_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
- self.download_folder_filechooser_button.connect("selection-changed",
+ self.download_folder_filechooser_button.connect("selection-changed",
self.on_download_folder_filechooser_button_selection_changed)
-
+
self.download_folder_table.attach(
- self.download_folder_filechooser_button,
+ self.download_folder_filechooser_button,
2, 3, 2, 3, yoptions = gtk.SHRINK)
- self.download_folder_filechooser_button.show()
+ self.download_folder_filechooser_button.show()
self._setup_subfolder_table()
self.update_photo_download_folder_example()
-
+
def _setup_video_download_folder_tab(self):
self.video_download_folder_filechooser_button = gtk.FileChooserButton(
_("Select a folder to download videos to"))
@@ -1194,16 +1194,16 @@ class PreferencesDialog():
self.prefs.video_download_folder)
self.video_download_folder_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
- self.video_download_folder_filechooser_button.connect("selection-changed",
+ self.video_download_folder_filechooser_button.connect("selection-changed",
self.on_video_download_folder_filechooser_button_selection_changed)
-
+
self.video_download_folder_table.attach(
- self.video_download_folder_filechooser_button,
+ self.video_download_folder_filechooser_button,
2, 3, 2, 3, yoptions = gtk.SHRINK)
- self.video_download_folder_filechooser_button.show()
+ self.video_download_folder_filechooser_button.show()
self._setup_video_subfolder_table()
- self.update_video_download_folder_example()
-
+ self.update_video_download_folder_example()
+
def _setup_image_rename_tab(self):
self.rename_table = PhotoRenameTable(self, self.rename_scrolledwindow)
@@ -1214,7 +1214,7 @@ class PreferencesDialog():
def _setup_photo_original_name(self):
self.original_name_label.set_markup("<i>%s</i>" % self.sample_photo.display_name)
-
+
def _setup_video_rename_tab(self):
self.video_rename_table = VideoRenameTable(self, self.video_rename_scrolledwindow)
@@ -1222,15 +1222,15 @@ class PreferencesDialog():
self.video_rename_table.show_all()
self._setup_video_original_name()
self.update_video_rename_example()
-
+
def _setup_video_original_name(self):
if self.sample_video is not None:
self.video_original_name_label.set_markup("<i>%s</i>" % self.sample_video.display_name)
else:
- self.video_original_name_label.set_markup("")
-
+ self.video_original_name_label.set_markup("")
+
def _setup_rename_options_tab(self):
-
+
# sequence numbers
self.downloads_today_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 0))
self.stored_number_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 1))
@@ -1251,11 +1251,11 @@ class PreferencesDialog():
self.synchronize_raw_jpg_checkbutton.set_active(
self.prefs.synchronize_raw_jpg)
-
+
#compatibility
self.strip_characters_checkbutton.set_active(
self.prefs.strip_characters)
-
+
def _setup_job_code_tab(self):
self.job_code_liststore = gtk.ListStore(str)
column = gtk.TreeViewColumn()
@@ -1270,13 +1270,13 @@ class PreferencesDialog():
self.job_code_treeview.props.model = self.job_code_liststore
for code in self.prefs.job_codes:
self.job_code_liststore.append((code, ))
-
+
# set multiple selections
self.job_code_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
-
+
self.remove_all_job_code_button.set_image(gtk.image_new_from_stock(
gtk.STOCK_CLEAR,
- gtk.ICON_SIZE_BUTTON))
+ gtk.ICON_SIZE_BUTTON))
def _setup_device_options_tab(self):
"""
Setup ignored paths and remembered devices tab in prefs dialog
@@ -1284,7 +1284,7 @@ class PreferencesDialog():
self.ignored_paths_use_re_checkbutton.set_active(
self.prefs.use_re_ignored_paths)
-
+
self.ignored_paths_liststore = gtk.ListStore(str)
column = gtk.TreeViewColumn()
rentext = gtk.CellRendererText()
@@ -1298,17 +1298,17 @@ class PreferencesDialog():
self.ignored_paths_treeview.props.model = self.ignored_paths_liststore
for path in self.prefs.ignored_paths:
self.ignored_paths_liststore.append((path, ))
-
+
self.ignored_paths_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.remove_all_ignored_paths_button.set_image(gtk.image_new_from_stock(
gtk.STOCK_CLEAR,
gtk.ICON_SIZE_BUTTON))
-
+
# Remembered devices are a little different in that they cannot be
- # edited, and they can only added when the user is prompted by the
+ # edited, and they can only added when the user is prompted by the
# program. Moreover, the list the user sees is a combination of two
# lists: device_whitelist and device_blacklist
-
+
self.remembered_devices_liststore = gtk.ListStore(str)
column = gtk.TreeViewColumn()
rentext = gtk.CellRendererText()
@@ -1325,12 +1325,12 @@ class PreferencesDialog():
for device in self.prefs.device_blacklist:
if device:
self.remembered_devices_liststore.append((device, ))
-
+
self.remembered_devices_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
self.remove_all_remembered_device_button.set_image(gtk.image_new_from_stock(
gtk.STOCK_CLEAR,
gtk.ICON_SIZE_BUTTON))
-
+
def _setup_device_tab(self):
self.device_location_filechooser_button = gtk.FileChooserButton(
@@ -1339,8 +1339,8 @@ class PreferencesDialog():
self.prefs.device_location)
self.device_location_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
-
- self.device_location_filechooser_button.connect("selection-changed",
+
+ self.device_location_filechooser_button.connect("selection-changed",
self.on_device_location_filechooser_button_selection_changed)
self.devices2_table.attach(self.device_location_filechooser_button,
@@ -1350,13 +1350,13 @@ class PreferencesDialog():
self.prefs.device_autodetection)
self.autodetect_psd_checkbutton.set_active(
self.prefs.device_autodetection_psd)
-
+
self.update_device_controls()
-
+
def _setup_backup_tab(self):
"""
- Setup and configure backup tab
+ Setup and configure backup tab
"""
#Manual backup location for photos file chooser
self.backup_folder_filechooser_button = gtk.FileChooserButton(
@@ -1365,12 +1365,12 @@ class PreferencesDialog():
self.prefs.backup_location)
self.backup_folder_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
- self.backup_folder_filechooser_button.connect("selection-changed",
+ self.backup_folder_filechooser_button.connect("selection-changed",
self.on_backup_folder_filechooser_button_selection_changed)
self.backup_table.attach(self.backup_folder_filechooser_button,
3, 4, 8, 9, yoptions = gtk.SHRINK)
self.backup_folder_filechooser_button.show()
-
+
#Manual backup location for videos file chooser
self.backup_video_folder_filechooser_button = gtk.FileChooserButton(
_("Select a folder in which to backup videos"))
@@ -1378,7 +1378,7 @@ class PreferencesDialog():
self.prefs.backup_video_location)
self.backup_video_folder_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
- self.backup_video_folder_filechooser_button.connect("selection-changed",
+ self.backup_video_folder_filechooser_button.connect("selection-changed",
self.on_backup_video_folder_filechooser_button_selection_changed)
self.backup_table.attach(self.backup_video_folder_filechooser_button,
3, 4, 9, 10, yoptions = gtk.SHRINK)
@@ -1386,7 +1386,7 @@ class PreferencesDialog():
self.backup_identifier_entry.set_text(self.prefs.backup_identifier)
self.video_backup_identifier_entry.set_text(self.prefs.video_backup_identifier)
-
+
#setup controls for manipulating sensitivity
self._backup_controls0 = [self.auto_detect_backup_checkbutton]
self._backup_controls1 = [self.backup_identifier_explanation_label,
@@ -1397,30 +1397,30 @@ class PreferencesDialog():
self._backup_controls2 = [self.backup_location_label,
self.backup_folder_filechooser_button,
self.backup_location_explanation_label]
-
+
if metadatavideo.DOWNLOAD_VIDEO:
self._backup_controls2 += [self.backup_video_folder_filechooser_button,
self.backup_video_location_label]
else:
self.backup_video_folder_filechooser_button.set_sensitive(False)
self.backup_video_location_label.set_sensitive(False)
-
+
self._backup_controls = self._backup_controls0 + self._backup_controls1 + \
self._backup_controls2
-
+
self._backup_video_controls = [self.video_backup_identifier_label,
self.video_backup_identifier_entry]
-
+
#assign values to checkbuttons only when other controls
#have been setup, because their toggle signal is activated
#when a value is assigned
-
+
self.backup_checkbutton.set_active(self.prefs.backup_images)
self.auto_detect_backup_checkbutton.set_active(
self.prefs.backup_device_autodetection)
self.update_backup_controls()
self.update_backup_example()
-
+
def _setup_miscellaneous_tab(self):
self.auto_startup_checkbutton.set_active(
self.prefs.auto_download_at_startup)
@@ -1436,52 +1436,54 @@ class PreferencesDialog():
self.prefs.generate_thumbnails)
self.auto_rotate_checkbutton.set_active(
self.prefs.auto_rotate_jpeg)
-
+ self.verify_file_checkbutton.set_active(
+ self.prefs.verify_file)
+
self.update_misc_controls()
-
+
def _setup_error_tab(self):
if self.prefs.download_conflict_resolution == config.SKIP_DOWNLOAD:
self.skip_download_radiobutton.set_active(True)
else:
self.add_identifier_radiobutton.set_active(True)
-
+
if self.prefs.backup_duplicate_overwrite:
self.backup_duplicate_overwrite_radiobutton.set_active(True)
else:
self.backup_duplicate_skip_radiobutton.set_active(True)
-
+
def update_example_file_name(self, display_table, rename_table, sample_rpd_file, generator, example_label):
if hasattr(self, display_table) and sample_rpd_file is not None:
sample_rpd_file.download_folder = self.prefs.get_download_folder_for_file_type(sample_rpd_file.file_type)
sample_rpd_file.strip_characters = self.prefs.strip_characters
- sample_rpd_file.initialize_problem()
+ sample_rpd_file.initialize_problem()
name = generator.generate_name(sample_rpd_file)
else:
name = ''
-
+
# since this is markup, escape it
text = "<i>%s</i>" % utilities.escape(name)
-
+
if sample_rpd_file is not None:
if sample_rpd_file.has_problem():
text += "\n"
# Translators: please do not modify or leave out html formatting tags like <i> and <b>. These are used to format the text the users sees
text += _("<i><b>Warning:</b> There is insufficient metadata to fully generate the name. Please use other renaming options.</i>")
- example_label.set_markup(text)
-
+ example_label.set_markup(text)
+
def update_photo_rename_example(self):
- """
- Displays example image name to the user
+ """
+ Displays example image name to the user
"""
generator = gn.PhotoName(self.prefs.image_rename)
- self.update_example_file_name('rename_table', self.rename_table,
- self.sample_photo, generator,
+ self.update_example_file_name('rename_table', self.rename_table,
+ self.sample_photo, generator,
self.new_name_label)
-
+
def update_video_rename_example(self):
"""
Displays example video name to the user
@@ -1490,20 +1492,20 @@ class PreferencesDialog():
generator = gn.VideoName(self.prefs.video_rename)
else:
generator = None
- self.update_example_file_name('video_rename_table',
- self.video_rename_table,
- self.sample_video, generator,
+ self.update_example_file_name('video_rename_table',
+ self.video_rename_table,
+ self.sample_video, generator,
self.video_new_name_label)
-
- def update_download_folder_example(self, display_table, subfolder_table,
- download_folder, sample_rpd_file,
+
+ def update_download_folder_example(self, display_table, subfolder_table,
+ download_folder, sample_rpd_file,
generator,
- example_download_path_label,
+ example_download_path_label,
subfolder_warning_label):
- """
- Displays example subfolder name(s) to the user
"""
-
+ Displays example subfolder name(s) to the user
+ """
+
if hasattr(self, display_table) and sample_rpd_file is not None:
#~ subfolder_table.update_example_job_code()
sample_rpd_file.strip_characters = self.prefs.strip_characters
@@ -1511,7 +1513,7 @@ class PreferencesDialog():
path = generator.generate_name(sample_rpd_file)
else:
path = ''
-
+
text = os.path.join(download_folder, path)
# since this is markup, escape it
path = utilities.escape(text)
@@ -1524,35 +1526,35 @@ class PreferencesDialog():
# Translators: you should not modify or leave out the %s. This is a code used by the programming language python to insert a value that thes user will see
example_download_path_label.set_markup(_("<i>Example: %s</i>") % text)
subfolder_warning_label.set_markup(warning)
-
+
def update_photo_download_folder_example(self):
if hasattr(self, 'subfolder_table'):
generator = gn.PhotoSubfolder(self.prefs.subfolder)
- self.update_download_folder_example('subfolder_table',
+ self.update_download_folder_example('subfolder_table',
self.subfolder_table, self.prefs.download_folder,
- self.sample_photo, generator,
- self.example_photo_download_path_label,
+ self.sample_photo, generator,
+ self.example_photo_download_path_label,
self.photo_subfolder_warning_label)
-
+
def update_video_download_folder_example(self):
if hasattr(self, 'video_subfolder_table'):
if self.sample_video is not None:
generator = gn.VideoSubfolder(self.prefs.video_subfolder)
else:
generator = None
- self.update_download_folder_example('video_subfolder_table',
+ self.update_download_folder_example('video_subfolder_table',
self.video_subfolder_table,
self.prefs.video_download_folder,
- self.sample_video, generator,
- self.example_video_download_path_label,
+ self.sample_video, generator,
+ self.example_video_download_path_label,
self.video_subfolder_warning_label)
-
+
def on_hour_spinbutton_value_changed(self, spinbutton):
hour = spinbutton.get_value_as_int()
minute = self.minute_spinbutton.get_value_as_int()
self.rapidapp.downloads_today_tracker.set_day_start(hour, minute)
self.on_downloads_today_entry_changed(self.downloads_today_entry)
-
+
def on_minute_spinbutton_value_changed(self, spinbutton):
hour = self.hour_spinbutton.get_value_as_int()
minute = spinbutton.get_value_as_int()
@@ -1563,7 +1565,7 @@ class PreferencesDialog():
# do not update value if a download is occurring - it will mess it up!
if self.rapidapp.download_is_occurring():
logger.info("Downloads today value not updated, as a download is currently occurring")
- else:
+ else:
v = entry.get_text()
try:
v = int(v)
@@ -1574,9 +1576,9 @@ class PreferencesDialog():
self.rapidapp.downloads_today_tracker.reset_downloads_today(v)
self.rapidapp.refresh_downloads_today = True
self.update_photo_rename_example()
-
+
def on_stored_number_entry_changed(self, entry):
- # do not update value if a download is occurring - it will mess it up!
+ # do not update value if a download is occurring - it will mess it up!
if self.rapidapp.download_is_occurring():
logger.info("Stored number value not updated, as a download is currently occurring")
else:
@@ -1597,13 +1599,13 @@ class PreferencesDialog():
def _update_video_subfolder_pref_on_error(self, new_pref_list):
self.prefs.video_subfolder = new_pref_list
-
-
+
+
def check_subfolder_values_valid_on_exit(self, users_pref_list, update_pref_function, filetype, default_pref_list):
"""
Checks that the user has not entered in any inappropriate values
-
- If they have, filters out bad values and warns the user
+
+ If they have, filters out bad values and warns the user
"""
filtered, pref_list = filter_subfolder_prefs(users_pref_list)
if filtered:
@@ -1616,7 +1618,7 @@ class PreferencesDialog():
sys.stderr.write(msg + "\n")
misc.run_dialog(PROGRAM_NAME, msg)
update_pref_function(self.prefs.get_default(default_pref_list))
-
+
def on_preferencesdialog_response(self, dialog, arg):
if arg == gtk.RESPONSE_HELP:
webbrowser.open("http://www.damonlynch.net/rapid/documentation")
@@ -1624,22 +1626,22 @@ class PreferencesDialog():
# arg==gtk.RESPONSE_CLOSE, or the user hit the 'x' to close the window
self.prefs.backup_identifier = self.backup_identifier_entry.get_property("text")
self.prefs.video_backup_identifier = self.video_backup_identifier_entry.get_property("text")
-
+
#check subfolder preferences for bad values
self.check_subfolder_values_valid_on_exit(self.prefs.subfolder, self._update_subfolder_pref_on_error, _("photo"), "subfolder")
self.check_subfolder_values_valid_on_exit(self.prefs.video_subfolder, self._update_video_subfolder_pref_on_error, _("video"), "video_subfolder")
-
+
self.dialog.destroy()
self.rapidapp.preferences_dialog_displayed = False
self.rapidapp.post_preference_change()
-
+
def on_add_job_code_button_clicked(self, button):
j = JobCodeDialog(parent_window = self.dialog,
job_codes = self.prefs.job_codes,
- default_job_code = None,
+ default_job_code = None,
post_job_code_entry_callback=self.add_job_code,
entry_only = True)
@@ -1670,36 +1672,36 @@ class PreferencesDialog():
for i in range(0, no):
iter = iters[i]
if i == no - 1:
- path = model.get_path(iter)
+ path = model.get_path(iter)
model.remove(iter)
-
- # now that we removed the selection, play nice with
+
+ # now that we removed the selection, play nice with
# the user and select the next item
selection.select_path(path)
-
+
# if there was no selection that meant the user
- # removed the last entry, so we try to select the
+ # removed the last entry, so we try to select the
# last item
if not selection.path_is_selected(path):
row = path[0]-1
# test case for empty lists
if row >= 0:
selection.select_path((row,))
-
-
+
+
def on_remove_job_code_button_clicked(self, button):
""" remove selected job codes (can be multiple selection)"""
-
+
self._remove_from_treeview(self.job_code_treeview)
self.update_job_codes()
self.update_photo_rename_example()
self.update_video_rename_example()
self.update_photo_download_folder_example()
self.update_video_download_folder_example()
-
+
def on_remove_all_job_code_button_clicked(self, button):
j = RemoveAllJobCodeDialog(self.dialog, self.remove_all_job_code)
-
+
def remove_all_job_code(self, dialog, user_selected):
dialog.destroy()
if user_selected:
@@ -1709,7 +1711,7 @@ class PreferencesDialog():
self.update_video_rename_example()
self.update_photo_download_folder_example()
self.update_video_download_folder_example()
-
+
def on_job_code_edited(self, widget, path, new_text):
iter = self.job_code_liststore.get_iter(path)
self.job_code_liststore.set_value(iter, 0, new_text)
@@ -1723,20 +1725,20 @@ class PreferencesDialog():
replacement_list = []
for row in liststore:
replacement_list.append(row[0])
- return replacement_list
-
+ return replacement_list
+
def update_job_codes(self):
""" update preferences with list of job codes"""
self.prefs.job_codes = self._update_prefs_list(self.job_code_liststore)
-
+
def on_ignored_path_edited(self, widget, path, new_text):
iter = self.ignored_paths_liststore.get_iter(path)
self.ignored_paths_liststore.set_value(iter, 0, new_text)
- self.update_ignored_paths()
-
+ self.update_ignored_paths()
+
def update_ignored_paths(self):
ignored_paths = self._update_prefs_list(self.ignored_paths_liststore)
-
+
# remove any trailing slashes
ignored_paths = [path.rstrip('/') for path in ignored_paths if path]
# remove any blank values from ignored_paths
@@ -1761,21 +1763,24 @@ class PreferencesDialog():
else:
msg = _("This regular expression is invalid, and will be removed unless you correct it:\n %s") % bad_paths
misc.run_dialog(_("Invalid regular expression"), msg, self)
-
+
self.prefs.ignored_paths = ignored_paths
-
+
def on_auto_startup_checkbutton_toggled(self, checkbutton):
self.prefs.auto_download_at_startup = checkbutton.get_active()
-
+
def on_auto_insertion_checkbutton_toggled(self, checkbutton):
self.prefs.auto_download_upon_device_insertion = checkbutton.get_active()
-
+
def on_auto_unmount_checkbutton_toggled(self, checkbutton):
self.prefs.auto_unmount = checkbutton.get_active()
-
+
def on_auto_rotate_checkbutton_toggled(self, checkbutton):
self.prefs.auto_rotate_jpeg = checkbutton.get_active()
+ def on_verify_file_checkbutton_toggled(self, checkbutton):
+ self.prefs.verify_file = checkbutton.get_active()
+
def on_auto_exit_checkbutton_toggled(self, checkbutton):
active = checkbutton.get_active()
self.prefs.auto_exit = active
@@ -1783,45 +1788,45 @@ class PreferencesDialog():
self.prefs.auto_exit_force = False
self.auto_exit_force_checkbutton.set_active(False)
self.update_misc_controls()
-
+
def on_auto_exit_force_checkbutton_toggled(self, checkbutton):
self.prefs.auto_exit_force = checkbutton.get_active()
-
+
def on_autodetect_device_checkbutton_toggled(self, checkbutton):
self.prefs.device_autodetection = checkbutton.get_active()
self.update_device_controls()
def on_autodetect_psd_checkbutton_toggled(self, checkbutton):
self.prefs.device_autodetection_psd = checkbutton.get_active()
-
+
def on_generate_thumbnails_checkbutton_toggled(self, checkbutton):
self.prefs.generate_thumbnails = checkbutton.get_active()
-
+
def on_backup_duplicate_overwrite_radiobutton_toggled(self, widget):
self.prefs.backup_duplicate_overwrite = widget.get_active()
-
+
def on_backup_duplicate_skip_radiobutton_toggled(self, widget):
self.prefs.backup_duplicate_overwrite = not widget.get_active()
-
+
def on_treeview_cursor_changed(self, tree):
path, column = tree.get_cursor()
self.notebook.set_current_page(path[0])
def on_synchronize_raw_jpg_checkbutton_toggled(self, check_button):
self.prefs.synchronize_raw_jpg = check_button.get_active()
-
+
def on_strip_characters_checkbutton_toggled(self, check_button):
self.prefs.strip_characters = check_button.get_active()
self.update_photo_rename_example()
self.update_photo_download_folder_example()
self.update_video_download_folder_example()
-
+
def on_add_identifier_radiobutton_toggled(self, widget):
if widget.get_active():
self.prefs.download_conflict_resolution = config.ADD_UNIQUE_IDENTIFIER
else:
self.prefs.download_conflict_resolution = config.SKIP_DOWNLOAD
-
+
def update_device_controls(self):
"""
@@ -1842,7 +1847,7 @@ class PreferencesDialog():
c.set_sensitive(True)
self.autodetect_psd_checkbutton.set_sensitive(False)
self.autodetect_image_devices_label.set_sensitive(False)
-
+
if not self.pref_dialog_startup:
logger.debug("Resetting sample file photo and video files")
self._setup_sample_names(use_dummy_data = True)
@@ -1852,20 +1857,20 @@ class PreferencesDialog():
self.update_video_download_folder_example()
self._setup_video_original_name()
self.update_video_rename_example()
-
+
def update_misc_controls(self):
"""
Sets sensitivity of miscillaneous controls
"""
-
+
self.auto_exit_force_checkbutton.set_sensitive(self.prefs.auto_exit)
-
-
+
+
def update_backup_controls(self):
"""
Sets sensitivity of backup related widgets
"""
-
+
if not self.backup_checkbutton.get_active():
for c in self._backup_controls + self._backup_video_controls:
c.set_sensitive(False)
@@ -1897,15 +1902,15 @@ class PreferencesDialog():
c.set_sensitive(True)
if metadatavideo.DOWNLOAD_VIDEO:
for c in self._backup_video_controls:
- c.set_sensitive(False)
-
+ c.set_sensitive(False)
+
def disable_video_controls(self):
"""
Disables video preferences if video downloading is disabled
(probably because the appropriate libraries to enable
video metadata extraction are not installed)
- """
- controls = [self.example_video_filename_label,
+ """
+ controls = [self.example_video_filename_label,
self.original_video_filename_label,
self.new_video_filename_label,
self.video_new_name_label,
@@ -1917,27 +1922,27 @@ class PreferencesDialog():
]
for c in controls:
c.set_sensitive(False)
-
+
self.videos_cannot_be_downloaded_label.show()
self.folder_videos_cannot_be_downloaded_label.show()
self.folder_videos_cannot_be_downloaded_hbox.show()
-
+
def on_auto_detect_backup_checkbutton_toggled(self, widget):
self.prefs.backup_device_autodetection = widget.get_active()
self.update_backup_controls_auto()
-
+
def on_backup_checkbutton_toggled(self, widget):
self.prefs.backup_images = self.backup_checkbutton.get_active()
self.update_backup_controls()
def on_backup_identifier_entry_changed(self, widget):
self.update_backup_example()
-
+
def on_video_backup_identifier_entry_changed(self, widget):
self.update_backup_example()
def on_backup_scan_folder_on_entry_changed(self, widget):
- self.update_backup_example()
+ self.update_backup_example()
def update_backup_example(self):
# Translators: this value is used as an example device when automatic backup device detection is enabled. You should translate this.
diff --git a/rapid/prefsrapid.py b/rapid/prefsrapid.py
index b7030c8..05b012a 100644
--- a/rapid/prefsrapid.py
+++ b/rapid/prefsrapid.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2007-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2007-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -44,7 +44,7 @@ def _get_default_location_legacy(options, ignore_missing_dir=False):
if os.path.isdir(path):
return path
return utilities.get_full_path('')
-
+
def _get_default_location_XDG(dir_type):
proc = subprocess.Popen(['xdg-user-dir', dir_type], stdout=subprocess.PIPE)
output = proc.communicate()[0].strip()
@@ -55,51 +55,51 @@ def get_default_photo_location(ignore_missing_dir=False):
return _get_default_location_XDG('PICTURES')
except:
return _get_default_location_legacy(config.DEFAULT_PHOTO_LOCATIONS, ignore_missing_dir)
-
+
def get_default_video_location(ignore_missing_dir=False):
try:
return _get_default_location_XDG('VIDEOS')
- except:
+ except:
return _get_default_location_legacy(config.DEFAULT_VIDEO_LOCATIONS, ignore_missing_dir)
-
+
def get_default_backup_photo_identifier():
return os.path.split(get_default_photo_location(ignore_missing_dir = True))[1]
def get_default_backup_video_identifier():
return os.path.split(get_default_video_location(ignore_missing_dir = True))[1]
-
+
def today():
- return datetime.date.today().strftime('%Y-%m-%d')
-
+ return datetime.date.today().strftime('%Y-%m-%d')
+
class RapidPreferences(prefs.Preferences):
-
+
defaults = {
"program_version": prefs.Value(prefs.STRING, ""),
- "download_folder": prefs.Value(prefs.STRING,
+ "download_folder": prefs.Value(prefs.STRING,
get_default_photo_location()),
- "video_download_folder": prefs.Value(prefs.STRING,
+ "video_download_folder": prefs.Value(prefs.STRING,
get_default_video_location()),
"subfolder": prefs.ListValue(prefs.STRING_LIST, DEFAULT_SUBFOLDER_PREFS),
"video_subfolder": prefs.ListValue(prefs.STRING_LIST, DEFAULT_VIDEO_SUBFOLDER_PREFS),
- "image_rename": prefs.ListValue(prefs.STRING_LIST, [FILENAME,
+ "image_rename": prefs.ListValue(prefs.STRING_LIST, [FILENAME,
NAME_EXTENSION,
ORIGINAL_CASE]),
- "video_rename": prefs.ListValue(prefs.STRING_LIST, [FILENAME,
+ "video_rename": prefs.ListValue(prefs.STRING_LIST, [FILENAME,
NAME_EXTENSION,
ORIGINAL_CASE]),
"device_autodetection": prefs.Value(prefs.BOOL, True),
- "device_location": prefs.Value(prefs.STRING, os.path.expanduser('~')),
+ "device_location": prefs.Value(prefs.STRING, os.path.expanduser('~')),
"device_autodetection_psd": prefs.Value(prefs.BOOL, False),
- "device_whitelist": prefs.ListValue(prefs.STRING_LIST, ['']),
- "device_blacklist": prefs.ListValue(prefs.STRING_LIST, ['']),
+ "device_whitelist": prefs.ListValue(prefs.STRING_LIST, ['']),
+ "device_blacklist": prefs.ListValue(prefs.STRING_LIST, ['']),
"ignored_paths": prefs.ListValue(prefs.STRING_LIST, ['.Trash',
'.thumbnails']),
"use_re_ignored_paths": prefs.Value(prefs.BOOL, False),
"backup_images": prefs.Value(prefs.BOOL, False),
"backup_device_autodetection": prefs.Value(prefs.BOOL, True),
- "backup_identifier": prefs.Value(prefs.STRING,
+ "backup_identifier": prefs.Value(prefs.STRING,
get_default_backup_photo_identifier()),
- "video_backup_identifier": prefs.Value(prefs.STRING,
+ "video_backup_identifier": prefs.Value(prefs.STRING,
get_default_backup_video_identifier()),
"backup_location": prefs.Value(prefs.STRING, os.path.expanduser('~')),
"backup_video_location": prefs.Value(prefs.STRING, os.path.expanduser('~')),
@@ -110,7 +110,7 @@ class RapidPreferences(prefs.Preferences):
"auto_exit": prefs.Value(prefs.BOOL, False),
"auto_exit_force": prefs.Value(prefs.BOOL, False),
"auto_delete": prefs.Value(prefs.BOOL, False),
- "download_conflict_resolution": prefs.Value(prefs.STRING,
+ "download_conflict_resolution": prefs.Value(prefs.STRING,
config.SKIP_DOWNLOAD),
"backup_duplicate_overwrite": prefs.Value(prefs.BOOL, False),
"display_selection": prefs.Value(prefs.BOOL, True),
@@ -121,13 +121,13 @@ class RapidPreferences(prefs.Preferences):
"display_device_column": prefs.Value(prefs.BOOL, False),
"display_preview_folders": prefs.Value(prefs.BOOL, True),
"show_log_dialog": prefs.Value(prefs.BOOL, False),
- "day_start": prefs.Value(prefs.STRING, "03:00"),
- "downloads_today": prefs.ListValue(prefs.STRING_LIST, [today(), '0']),
- "stored_sequence_no": prefs.Value(prefs.INT, 0),
- "job_codes": prefs.ListValue(prefs.STRING_LIST, [_('New York'),
- _('Manila'), _('Prague'), _('Helsinki'), _('Wellington'),
- _('Tehran'), _('Kampala'), _('Paris'), _('Berlin'), _('Sydney'),
- _('Budapest'), _('Rome'), _('Moscow'), _('Delhi'), _('Warsaw'),
+ "day_start": prefs.Value(prefs.STRING, "03:00"),
+ "downloads_today": prefs.ListValue(prefs.STRING_LIST, [today(), '0']),
+ "stored_sequence_no": prefs.Value(prefs.INT, 0),
+ "job_codes": prefs.ListValue(prefs.STRING_LIST, [_('New York'),
+ _('Manila'), _('Prague'), _('Helsinki'), _('Wellington'),
+ _('Tehran'), _('Kampala'), _('Paris'), _('Berlin'), _('Sydney'),
+ _('Budapest'), _('Rome'), _('Moscow'), _('Delhi'), _('Warsaw'),
_('Jakarta'), _('Madrid'), _('Stockholm')]),
"synchronize_raw_jpg": prefs.Value(prefs.BOOL, False),
"vpaned_pos": prefs.Value(prefs.INT, 0),
@@ -138,18 +138,19 @@ class RapidPreferences(prefs.Preferences):
#~ "preview_zoom": prefs.Value(prefs.INT, zoom),
"generate_thumbnails": prefs.Value(prefs.BOOL, True),
"auto_rotate_jpeg": prefs.Value(prefs.BOOL, True),
+ "verify_file": prefs.Value(prefs.BOOL, False),
}
def __init__(self):
prefs.Preferences.__init__(self, config.GCONF_KEY, self.defaults)
-
+
def get_downloads_today_tracker(self):
return DownloadsTodayTracker(downloads_today_date = self.downloads_today[0],
downloads_today = self.downloads_today[1],
day_start = self.day_start
)
-
+
def set_downloads_today_from_tracker(self, downloads_today_tracker):
self.downloads_today = downloads_today_tracker.downloads_today
self.day_start = downloads_today_tracker.day_start
@@ -159,17 +160,17 @@ class RapidPreferences(prefs.Preferences):
return self.job_codes[0]
else:
return ''
-
+
def _get_pref_lists(self):
- return (self.image_rename, self.subfolder, self.video_rename,
+ return (self.image_rename, self.subfolder, self.video_rename,
self.video_subfolder)
-
+
def _pref_list_uses_component(self, pref_list, pref_component, offset):
for i in range(0, len(pref_list), 3):
if pref_list[i+offset] == pref_component:
return True
- return False
-
+ return False
+
def must_synchronize_raw_jpg(self):
"""Returns True if synchronize_raw_jpg is True and photo renaming
uses sequence values"""
@@ -178,60 +179,60 @@ class RapidPreferences(prefs.Preferences):
if self._pref_list_uses_component(self.image_rename, s, 1):
return True
return False
-
+
def any_pref_uses_stored_sequence_no(self):
"""Returns True if any of the pref lists contain a stored sequence no"""
for pref_list in self._get_pref_lists():
if self._pref_list_uses_component(pref_list, STORED_SEQ_NUMBER, 1):
return True
- return False
-
+ return False
+
def any_pref_uses_session_sequece_no(self):
"""Returns True if any of the pref lists contain a session sequence no"""
for pref_list in self._get_pref_lists():
if self._pref_list_uses_component(pref_list, SESSION_SEQ_NUMBER, 1):
return True
- return False
-
+ return False
+
def any_pref_uses_sequence_letter_value(self):
"""Returns True if any of the pref lists contain a sequence letter"""
for pref_list in self._get_pref_lists():
if self._pref_list_uses_component(pref_list, SEQUENCE_LETTER, 1):
return True
- return False
-
+ return False
+
def reset(self):
"""
resets all preferences to default values
"""
-
+
prefs.Preferences.reset(self)
self.program_version = __version__
-
-
+
+
def pref_uses_job_code(self, pref_list):
""" Returns True if the particular preferences contains a job code"""
for i in range(0, len(pref_list), 3):
if pref_list[i] == JOB_CODE:
return True
return False
-
+
def any_pref_uses_job_code(self):
""" Returns True if any of the preferences contain a job code"""
for pref_list in self._get_pref_lists():
if self.pref_uses_job_code(pref_list):
return True
return False
-
+
def most_recent_job_code(self):
if len(self.job_codes) > 0:
return self.job_codes[0]
else:
return None
-
+
def get_pref_lists_by_file_type(self, file_type):
"""
- Returns tuple of subfolder and file rename pref lists for the given
+ Returns tuple of subfolder and file rename pref lists for the given
file type
"""
if file_type == rpdfile.FILE_TYPE_PHOTO:
@@ -246,19 +247,19 @@ class RapidPreferences(prefs.Preferences):
if file_type == rpdfile.FILE_TYPE_PHOTO:
return self.download_folder
else:
- return self.video_download_folder
-
+ return self.video_download_folder
+
class DownloadsTodayTracker:
"""
Handles tracking the number of downloads undertaken on any one day.
-
+
When a day starts is flexible. See http://damonlynch.net/rapid/documentation/#renameoptions
"""
def __init__(self, downloads_today_date, downloads_today, day_start):
self.day_start = day_start # string
self.downloads_today = [downloads_today_date, str(downloads_today)] # two strings
-
+
def get_and_maybe_reset_downloads_today(self):
v = self.get_downloads_today()
if v <= 0:
@@ -266,23 +267,23 @@ class DownloadsTodayTracker:
return v
def get_downloads_today(self):
- """Returns the preference value for the number of downloads performed today
-
+ """Returns the preference value for the number of downloads performed today
+
If value is less than zero, that means the date has changed"""
-
+
hour, minute = self.get_day_start()
try:
- adjusted_today = datetime.datetime.strptime("%s %s:%s" % (self.downloads_today[0], hour, minute), "%Y-%m-%d %H:%M")
+ adjusted_today = datetime.datetime.strptime("%s %s:%s" % (self.downloads_today[0], hour, minute), "%Y-%m-%d %H:%M")
except:
- logger.critical("Failed to calculate date adjustment. Download today values appear to be corrupted: %s %s:%s",
+ logger.critical("Failed to calculate date adjustment. Download today values appear to be corrupted: %s %s:%s",
self.downloads_today[0], hour, minute)
adjusted_today = None
-
+
now = datetime.datetime.today()
-
+
if adjusted_today is None:
return -1
-
+
if now < adjusted_today :
try:
return int(self.downloads_today[1])
@@ -292,7 +293,7 @@ class DownloadsTodayTracker:
return 0
else:
return -1
-
+
def get_raw_downloads_today(self):
"""
Gets value without changing it in any way, except to check for type convesion error.
@@ -304,13 +305,13 @@ class DownloadsTodayTracker:
logger.critical("Downloads today value is corrupted: %s", self.downloads_today[1])
self.downloads_today[1] = '0'
return 0
-
+
def set_raw_downloads_today_from_int(self, downloads_today):
self.downloads_today[1] = str(downloads_today)
-
+
def set_raw_downloads_today_date(self, downloads_today_date):
self.downloads_today[0] = downloads_today_date
-
+
def get_raw_downloads_today_date(self):
return self.downloads_today[0]
@@ -319,7 +320,7 @@ class DownloadsTodayTracker:
Gets value without changing it in any way
"""
return self.day_start
-
+
def get_day_start(self):
try:
t1, t2 = self.day_start.split(":")
@@ -327,8 +328,8 @@ class DownloadsTodayTracker:
except ValueError:
logger.error("'Start of day' preference value %s is corrupted. Resetting to midnight", self.day_start)
self.day_start = "0:0"
- return 0, 0
-
+ return 0, 0
+
def increment_downloads_today(self):
""" returns true if day changed """
v = self.get_downloads_today()
@@ -348,33 +349,33 @@ class DownloadsTodayTracker:
else:
d = datetime.datetime.today() + datetime.timedelta(days=1)
date = d.strftime(('%Y-%m-%d'))
-
- self.set_downloads_today(date, value)
-
+
+ self.set_downloads_today(date, value)
+
def set_downloads_today(self, date, value=0):
self.downloads_today = [date, str(value)]
-
+
def set_day_start(self, hour, minute):
self.day_start = "%s:%s" % (hour, minute)
-
+
def log_vals(self):
logger.info("Date %s Value %s Day start %s", self.downloads_today[0], self.downloads_today[1], self.day_start)
-
-
+
+
def check_prefs_for_validity(prefs):
"""
Checks preferences for validity (called at program startup)
-
+
Returns tuple with two values:
1. true if the passed in preferences are valid, else returns False
2. message if prefs are invalid
"""
-
+
msg = ''
valid = True
- tests = ((prefs.image_rename, pd.PhotoNamePrefs),
+ tests = ((prefs.image_rename, pd.PhotoNamePrefs),
(prefs.subfolder, pd.PhotoSubfolderPrefs),
(prefs.video_rename, pd.VideoNamePrefs),
(prefs.video_subfolder, pd.VideoSubfolderPrefs))
@@ -385,14 +386,14 @@ def check_prefs_for_validity(prefs):
except pd.PrefError as e:
valid = False
msg += e.msg + "\n"
-
+
return (valid, msg)
def insert_pref_lists(prefs, rpd_file):
"""
Convenience function to insert subfolder and file rename pref_lists for
the given file type.
-
+
Returns the modified rpd_file
"""
subfolder_pref_list, name_pref_list = prefs.get_pref_lists_by_file_type(rpd_file.file_type)
@@ -402,31 +403,31 @@ def insert_pref_lists(prefs, rpd_file):
def format_pref_list_for_pretty_print(pref_list):
""" returns a string useful for printing the preferences"""
-
+
v = ''
-
+
for i in range(0, len(pref_list), 3):
if (pref_list[i+1] or pref_list[i+2]):
c = ':'
- else:
+ else:
c = ''
- s = "%s%s " % (pref_list[i], c)
-
+ s = "%s%s " % (pref_list[i], c)
+
if pref_list[i+1]:
s = "%s%s" % (s, pref_list[i+1])
if pref_list[i+2]:
s = "%s (%s)" % (s, pref_list[i+2])
v += s + "\n"
return v
-
-
+
+
def check_and_compile_re(ignored_paths):
"""
configure regular expression to search for, checking to see it is valid
-
+
returns compiled RE, or None if invalid
"""
-
+
pattern = ''
for path in ignored_paths:
# check for validity
@@ -437,7 +438,7 @@ def check_and_compile_re(ignored_paths):
logger.error("Ignoring invalid regular expression: %s", path)
if pattern:
pattern = pattern[:-1]
-
+
logger.debug("Ignored paths regular expression pattern: %s", pattern)
if not pattern:
logger.warning("No regular expression is specified")
diff --git a/rapid/problemnotification.py b/rapid/problemnotification.py
index ee0be5d..5982a6d 100755
--- a/rapid/problemnotification.py
+++ b/rapid/problemnotification.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2010-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2010-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -27,37 +27,38 @@ from gettext import gettext as _
SUBFOLDER_COMPONENT = _('subfolder')
FILENAME_COMPONENT = _('filename')
-# problem categories
-METADATA_PROBLEM = 1
+# problem categories
+METADATA_PROBLEM = 1
FILE_PROBLEM = 2
-GENERATION_PROBLEM = 3
-DOWNLOAD_PROBLEM = 4
-DOWNLOAD_PROBLEM_W_NO = 5
-DIFFERENT_EXIF = 6
-FILE_ALREADY_EXISTS = 7
-UNIQUE_IDENTIFIER_CAT = 8
+GENERATION_PROBLEM = 3
+DOWNLOAD_PROBLEM = 4
+DOWNLOAD_PROBLEM_W_NO = 5
+DIFFERENT_EXIF = 6
+FILE_ALREADY_EXISTS = 7
+UNIQUE_IDENTIFIER_CAT = 8
BACKUP_PROBLEM = 9
BACKUP_OK = 10
FILE_ALREADY_DOWN_CAT = 11
+VERIFICATION_PROBLEM = 12
# problem text
-MISSING_METADATA = 1
-INVALID_DATE_TIME = 2
-MISSING_FILE_EXTENSION = 3
-MISSING_IMAGE_NUMBER = 4
-ERROR_IN_GENERATION = 5
+MISSING_METADATA = 1
+INVALID_DATE_TIME = 2
+MISSING_FILE_EXTENSION = 3
+MISSING_IMAGE_NUMBER = 4
+ERROR_IN_GENERATION = 5
-CANNOT_DOWNLOAD_BAD_METADATA = 6
+CANNOT_DOWNLOAD_BAD_METADATA = 6
ERROR_IN_NAME_GENERATION = 7
-DOWNLOAD_COPYING_ERROR = 8
-DOWNLOAD_COPYING_ERROR_W_NO = 9
+DOWNLOAD_COPYING_ERROR = 8
+DOWNLOAD_COPYING_ERROR_W_NO = 9
-FILE_ALREADY_EXISTS_NO_DOWNLOAD = 10
-UNIQUE_IDENTIFIER_ADDED = 11
-BACKUP_EXISTS = 12
-BACKUP_EXISTS_OVERWRITTEN = 13
+FILE_ALREADY_EXISTS_NO_DOWNLOAD = 10
+UNIQUE_IDENTIFIER_ADDED = 11
+BACKUP_EXISTS = 12
+BACKUP_EXISTS_OVERWRITTEN = 13
NO_BACKUP_PERFORMED = 14
BACKUP_ERROR = 15
BACKUP_DIRECTORY_CREATION = 16
@@ -66,6 +67,9 @@ SAME_FILE_DIFFERENT_EXIF = 17
NO_DOWNLOAD_WAS_BACKED_UP = 18
FILE_ALREADY_DOWNLOADED = 19
+FILE_VERIFICATION_FAILED = 20
+BACKUP_VERIFICATION_FAILED = 21
+
#extra details
UNIQUE_IDENTIFIER = '__1'
EXISTING_FILE = '__2'
@@ -76,30 +80,33 @@ BACKUP_OK_TYPE = '__6'
# category, text, duplicates allowed
problem_definitions = {
-
+
MISSING_METADATA: (METADATA_PROBLEM, "%s", True),
INVALID_DATE_TIME: (METADATA_PROBLEM, _('Date time value %s appears invalid.'), False),
MISSING_FILE_EXTENSION: (METADATA_PROBLEM, _("Filename does not have an extension."), False),
# a number component is something like the 8346 in IMG_8346.JPG
MISSING_IMAGE_NUMBER: (METADATA_PROBLEM, _("Filename does not have a number component."), False),
ERROR_IN_GENERATION: (METADATA_PROBLEM, _("Error generating component %s."), False), # a generic problem
-
+
CANNOT_DOWNLOAD_BAD_METADATA: (FILE_PROBLEM, _("%(filetype)s metadata cannot be read"), False),
-
+
ERROR_IN_NAME_GENERATION: (GENERATION_PROBLEM, _("%(filetype)s %(area)s could not be generated"), False),
-
+
DOWNLOAD_COPYING_ERROR: (DOWNLOAD_PROBLEM, _("An error occurred when copying the %(filetype)s"), False),
DOWNLOAD_COPYING_ERROR_W_NO: (DOWNLOAD_PROBLEM_W_NO, _("An error occurred when copying the %(filetype)s"), False),
-
+
+ FILE_VERIFICATION_FAILED: (VERIFICATION_PROBLEM, _("The %(filetype)s did not download correctly"), False),
+ BACKUP_VERIFICATION_FAILED: (BACKUP_PROBLEM, "%s", True),
+
FILE_ALREADY_EXISTS_NO_DOWNLOAD:(FILE_ALREADY_EXISTS, _("%(filetype)s already exists"), False),
UNIQUE_IDENTIFIER_ADDED: (UNIQUE_IDENTIFIER_CAT, _("%(filetype)s already exists"), False),
- BACKUP_EXISTS: (BACKUP_PROBLEM, "%s", True),
- BACKUP_EXISTS_OVERWRITTEN: (BACKUP_PROBLEM, "%s", True),
+ BACKUP_EXISTS: (BACKUP_PROBLEM, "%s", True),
+ BACKUP_EXISTS_OVERWRITTEN: (BACKUP_PROBLEM, "%s", True),
NO_BACKUP_PERFORMED: (BACKUP_PROBLEM, _("%(filetype)s could not be backed up because no suitable backup locations were found."), False),
BACKUP_ERROR: (BACKUP_PROBLEM, "%s", True),
BACKUP_DIRECTORY_CREATION: (BACKUP_PROBLEM, "%s", True),
NO_DOWNLOAD_WAS_BACKED_UP: (BACKUP_OK, "%s", True),
-
+
SAME_FILE_DIFFERENT_EXIF: (DIFFERENT_EXIF, _("%(image1)s was taken on %(image1_date)s at %(image1_time)s, and %(image2)s on %(image2_date)s at %(image2_time)s."), False),
FILE_ALREADY_DOWNLOADED: (FILE_ALREADY_DOWN_CAT, _('%(filetype)s was already downloaded'), False),
}
@@ -116,23 +123,23 @@ extra_detail_definitions = {
class Problem:
"""
Collect problems with subfolder and filename generation, download errors, and so forth
-
+
Problems are human readable
"""
-
+
def __init__(self):
self.problems = {}
self.categories = {}
self.components = []
self.extra_detail = {}
-
+
def add_problem(self, component, problem_definition, *args):
added = True
if problem_definition not in problem_definitions:
sys.stderr.write("FIXME: unknown problem definition!\n")
else:
category, problem, duplicates_ok = problem_definitions[problem_definition]
-
+
if args:
# check for special case of named arguments in a dictionary
if isinstance(args[0], types.DictType):
@@ -152,12 +159,12 @@ class Problem:
added = False
else:
self.problems[problem_definition] = [problem_details]
-
+
if category not in self.categories or not added:
self.categories[category] = 1
else:
self.categories[category] += 1
-
+
if (component is not None) and (component not in self.components):
self.components.append(component)
@@ -166,7 +173,7 @@ class Problem:
self.extra_detail[extra_detail] = args[0]
else:
detail = extra_detail_definitions[extra_detail]
-
+
if args:
if isinstance(args[0], types.DictType):
extra_details = detail % args[0]
@@ -174,33 +181,37 @@ class Problem:
extra_details = detail % args
else:
extra_details = detail
-
+
self.extra_detail[extra_detail] = extra_details
-
-
+
+
def has_problem(self):
return len(self.problems) > 0
-
+
def get_problems(self):
"""
Returns a string with the problems encountered in downloading the file.
"""
-
+
def get_backup_error_inst(volume):
if ('%s%s' % (BACKUP_ERROR, volume)) in self.extra_detail:
return self.extra_detail['%s%s' % (BACKUP_ERROR, volume)]
else:
return ''
-
+
def get_dir_creation_inst(volume):
return self.extra_detail['%s%s' % (BACKUP_DIRECTORY_CREATION, volume)]
-
+
v = ''
-
+
# special cases
+
+ if VERIFICATION_PROBLEM in self.categories:
+ return _("File verification failed. The downloaded version is different from the original.")
+
if FILE_PROBLEM in self.categories:
return _("The metadata might be corrupt.")
-
+
if FILE_ALREADY_DOWN_CAT in self.categories:
return _("The filename, extension and Exif information indicate it has already been downloaded.")
@@ -208,16 +219,16 @@ class Problem:
if EXISTING_FILE in self.extra_detail:
v = self.extra_detail[EXISTING_FILE]
-
+
if UNIQUE_IDENTIFIER_CAT in self.categories:
v = self.extra_detail[UNIQUE_IDENTIFIER]
-
+
if DOWNLOAD_PROBLEM in self.categories:
v = self.extra_detail[DOWNLOAD_COPYING_ERROR_DETAIL]
-
+
if DOWNLOAD_PROBLEM_W_NO in self.categories:
v = self.extra_detail[DOWNLOAD_COPYING_ERROR_W_NO_DETAIL]
-
+
if BACKUP_OK in self.categories:
details = self.problems[NO_DOWNLOAD_WAS_BACKED_UP]
if len(self.problems[NO_DOWNLOAD_WAS_BACKED_UP]) == 1:
@@ -227,30 +238,30 @@ class Problem:
for d in details[:-1]:
vv += _("%s, ") % d
vv = _("%(volumes)s and %(final_volume)s.") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'final_volume': details[-1]} \
+ ' '
v += vv
if GENERATION_PROBLEM in self.categories:
v = self.extra_detail[NO_DATA_TO_NAME]
-
+
if DIFFERENT_EXIF in self.categories:
- v = self.problems[SAME_FILE_DIFFERENT_EXIF][0]
+ v = self.problems[SAME_FILE_DIFFERENT_EXIF][0]
if METADATA_PROBLEM in self.categories:
v = _('Photos detected with the same filenames, but taken at different times: %(details)s' ) % {'details':v}
-
+
# Problems backing up
if BACKUP_PROBLEM in self.categories:
vv = ''
for p in self.problems:
details = self.problems[p]
-
+
if p == NO_BACKUP_PERFORMED:
vv = details[0]
elif p == BACKUP_ERROR:
-
+
if len(details) == 1:
volume = details[0]
inst = get_backup_error_inst(volume)
@@ -270,16 +281,16 @@ class Problem:
inst = get_backup_error_inst(volume)
if inst:
vv = _("%(volumes)s and %(volume)s (%(inst)s).") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'volume': volume,
'inst': get_inst(volume)}
else:
vv = _("%(volumes)s and %(volume)s.") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'volume': volume} \
+ ' '
-
-
+
+
elif p == BACKUP_EXISTS:
if len(details) == 1:
vv += _("Backup already exists on %(volume)s.") % {'volume': details[0]} + ' '
@@ -288,10 +299,10 @@ class Problem:
for d in details[:-1]:
vv += _("%s, ") % d
vv = _("%(volumes)s and %(final_volume)s.") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'final_volume': details[-1]} \
+ ' '
-
+
elif p == BACKUP_EXISTS_OVERWRITTEN:
if len(details) == 1:
vv += _("Backup overwritten on %(volume)s.") % {'volume': details[0]} + ' '
@@ -300,10 +311,10 @@ class Problem:
for d in details[:-1]:
vv += _("%s, ") % d
vv = _("%(volumes)s and %(final_volume)s.") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'final_volume': details[-1]} \
+ ' '
-
+
elif p == BACKUP_DIRECTORY_CREATION:
if len(details) == 1:
volume = details[0]
@@ -314,24 +325,37 @@ class Problem:
vv += _("%(volume)s (%(inst)s), ") % {'volume': volume, 'inst': get_dir_creation_inst(volume)}
volume = details[-1]
vv = _("%(volumes)s and %(volume)s (%(inst)s).") % \
- {'volumes': vv[:-2],
+ {'volumes': vv[:-2],
'volume': volume,
'inst': get_dir_creation_inst(volume)} \
+ ' '
+ elif p == BACKUP_VERIFICATION_FAILED:
+ if len(details) == 1:
+ vv += _("File verification failed on %(volume)s. The backed up version is different from the downloaded version.") % {'volume': details[0]} + ' '
+ else:
+ vv += _("File verification failed on these devices: ")
+ for d in details[:-1]:
+ vv += _("%s, ") % d
+ vv = _("%(volumes)s and %(final_volume)s.") % \
+ {'volumes': vv[:-2],
+ 'final_volume': details[-1]} \
+ + ' '
+
+
if v:
v = _('%(previousproblem)s Additionally, %(newproblem)s') % {'previousproblem': v, 'newproblem': vv[0].lower() + vv[1:]}
else:
- v = vv
+ v = vv
+
-
if v and METADATA_PROBLEM in self.categories:
vv = self._get_generation_title()
if self.categories[METADATA_PROBLEM] > 1:
v += _(' Furthermore, there were %(problems)s.') % {'problems': vv[0].lower() + vv[1:]}
else:
v += _(' Furthermore, there was a %(problem)s.') % {'problem': vv[0].lower() + vv[1:]}
-
+
# Problems generating file / subfolder names
if METADATA_PROBLEM in self.categories:
for p in self.problems:
@@ -345,18 +369,18 @@ class Problem:
for d in details[:-1]:
vv += ("%s, ") % d
vv = _("%(missing_metadata_elements)s and %(final_missing_metadata_element)s.") % \
- {'missing_metadata_elements': vv[:-2],
+ {'missing_metadata_elements': vv[:-2],
'final_missing_metadata_element': details[-1]}
-
-
+
+
elif p in [MISSING_IMAGE_NUMBER, ERROR_IN_GENERATION, INVALID_DATE_TIME]:
vv = details[0]
v += ' ' + vv
-
+
v = v.strip()
return v
-
+
def _get_generation_title(self):
if self.components:
if len(self.components) > 1:
@@ -374,14 +398,16 @@ class Problem:
def get_title(self):
v = ''
-
+
if BACKUP_OK in self.categories:
if FILE_ALREADY_EXISTS in self.categories:
v = _('%(filetype)s already exists, but it was backed up') % {'filetype': self.extra_detail[BACKUP_OK_TYPE]}
else:
v = _('An error occurred when copying the %(filetype)s, but it was backed up') % {'filetype': self.extra_detail[BACKUP_OK_TYPE]}
-
+
# High priority problems
+ elif VERIFICATION_PROBLEM in self.categories:
+ v = self.problems[FILE_VERIFICATION_FAILED][0]
elif FILE_ALREADY_DOWN_CAT in self.categories:
v = self.problems[FILE_ALREADY_DOWNLOADED][0]
elif DOWNLOAD_PROBLEM in self.categories:
@@ -389,14 +415,14 @@ class Problem:
elif DOWNLOAD_PROBLEM_W_NO in self.categories:
v = self.problems[DOWNLOAD_COPYING_ERROR_W_NO][0]
elif GENERATION_PROBLEM in self.categories:
- v = self.problems[ERROR_IN_NAME_GENERATION][0]
+ v = self.problems[ERROR_IN_NAME_GENERATION][0]
elif FILE_ALREADY_EXISTS in self.categories:
v = self.problems[FILE_ALREADY_EXISTS_NO_DOWNLOAD][0]
elif UNIQUE_IDENTIFIER_CAT in self.categories:
v = self.problems[UNIQUE_IDENTIFIER_ADDED][0]
elif FILE_PROBLEM in self.categories:
v = self.problems[CANNOT_DOWNLOAD_BAD_METADATA][0]
-
+
# Lesser priority
elif len(self.categories) > 1:
v = _('Multiple problems were encountered')
@@ -404,7 +430,7 @@ class Problem:
v = _('Photos detected with the same filenames, but taken at different times')
elif METADATA_PROBLEM in self.categories:
v = self._get_generation_title()
-
+
if BACKUP_PROBLEM in self.categories:
if self.categories[BACKUP_PROBLEM] >1:
vp = _("there were errors backing up")
@@ -413,13 +439,13 @@ class Problem:
vp = _("there was an error backing up")
vv = _("There was an error backing up")
if v:
- # e.g.
+ # e.g.
v = _("%(previousproblem)s, and %(backinguperror)s") % {'previousproblem': v, 'backinguperror':vp}
else:
v = vv
-
+
return v
-
+
if __name__ == '__main__':
diff --git a/rapid/rapid.py b/rapid/rapid.py
index 4f7e6bc..dd67b63 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -743,39 +743,40 @@ class ThumbnailDisplay(gtk.IconView):
self.previews_being_fetched.add(unique_id)
def show_preview(self, unique_id=None, iter=None):
- if unique_id is not None:
- iter = self.get_iter_from_unique_id(unique_id)
- elif iter is not None:
- unique_id = self.get_unique_id_from_iter(iter)
- else:
- # neither an iter or a unique_id were passed
- # use iter from first selected file
- # if none is selected, choose the first file
- selected = self.get_selected_items()
- if selected:
- path = selected[0]
+ if len(self.liststore):
+ if unique_id is not None:
+ iter = self.get_iter_from_unique_id(unique_id)
+ elif iter is not None:
+ unique_id = self.get_unique_id_from_iter(iter)
else:
- path = 0
- iter = self.liststore.get_iter(path)
- unique_id = self.get_unique_id_from_iter(iter)
+ # neither an iter or a unique_id were passed
+ # use iter from first selected file
+ # if none is selected, choose the first file
+ selected = self.get_selected_items()
+ if selected:
+ path = selected[0]
+ else:
+ path = 0
+ iter = self.liststore.get_iter(path)
+ unique_id = self.get_unique_id_from_iter(iter)
- rpd_file = self.rpd_files[unique_id]
+ rpd_file = self.rpd_files[unique_id]
- if unique_id in self.previews:
- preview_image = self.previews[unique_id]
- else:
- # request daemon process to get a full size thumbnail
- self._get_preview(unique_id, rpd_file)
- if unique_id in self.thumbnails:
- preview_image = self.thumbnails[unique_id]
+ if unique_id in self.previews:
+ preview_image = self.previews[unique_id]
else:
- preview_image = self.get_stock_icon(rpd_file.file_type)
+ # request daemon process to get a full size thumbnail
+ self._get_preview(unique_id, rpd_file)
+ if unique_id in self.thumbnails:
+ preview_image = self.thumbnails[unique_id]
+ else:
+ preview_image = self.get_stock_icon(rpd_file.file_type)
- checked = self.liststore.get_value(iter, self.SELECTED_COL)
- include_checkbutton_visible = rpd_file.status == STATUS_NOT_DOWNLOADED
- self.rapid_app.show_preview_image(unique_id, preview_image,
- include_checkbutton_visible, checked)
+ checked = self.liststore.get_value(iter, self.SELECTED_COL)
+ include_checkbutton_visible = rpd_file.status == STATUS_NOT_DOWNLOADED
+ self.rapid_app.show_preview_image(unique_id, preview_image,
+ include_checkbutton_visible, checked)
def _get_next_iter(self, iter):
iter = self.liststore.iter_next(iter)
@@ -1249,12 +1250,14 @@ class CopyFilesManager(TaskManager):
video_download_folder = task[1]
scan_pid = task[2]
files = task[3]
- modify_files_during_download = task[4]
- modify_pipe = task[5]
+ verify_files = task[4]
+ modify_files_during_download = task[5]
+ modify_pipe = task[6]
copy_files = copyfiles.CopyFiles(photo_download_folder,
video_download_folder,
files,
+ verify_files,
modify_files_during_download,
modify_pipe,
scan_pid, self.batch_size,
@@ -1276,7 +1279,7 @@ class ThumbnailManager(TaskManager):
return generator.pid
class FileModifyManager(TaskManager):
- """Handles the modification of downloaded files before they are renamed
+ """Handles the modification or verification of of downloaded files before they are renamed
Duplex, multiprocess, similar to BackupFilesManager
"""
def __init__(self, results_callback):
@@ -1289,8 +1292,11 @@ class FileModifyManager(TaskManager):
scan_pid = task[0]
auto_rotate_jpeg = task[1]
focal_length = task[2]
+ verify_file = task[3]
+ refresh_md5_on_file_change = task[4]
file_modify = filemodify.FileModify(auto_rotate_jpeg, focal_length,
+ verify_file, refresh_md5_on_file_change,
task_process_conn, terminate_queue,
run_event)
file_modify.start()
@@ -1329,7 +1335,7 @@ class BackupFilesManager(TaskManager):
def _send_termination_msg(self, p):
p[1].put(None)
- p[3].send((None, None, None, None, None))
+ p[3].send((None, None, None, None, None, None, None))
def _initiate_task(self, task, task_results_conn, task_process_conn,
terminate_queue, run_event):
@@ -1350,6 +1356,7 @@ class BackupFilesManager(TaskManager):
def backup_file(self, move_succeeded, rpd_file, path_suffix,
backup_duplicate_overwrite,
+ verify_file,
download_count):
if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
@@ -1359,15 +1366,21 @@ class BackupFilesManager(TaskManager):
for path in self.backup_devices_by_path:
backup_type = self.backup_devices_by_path[path][2]
- if ((backup_type == PHOTO_VIDEO_BACKUP) or
+ do_backup = ((backup_type == PHOTO_VIDEO_BACKUP) or
(rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO and backup_type == PHOTO_BACKUP) or
- (rpd_file.file_type == rpdfile.FILE_TYPE_VIDEO and backup_type == VIDEO_BACKUP)):
+ (rpd_file.file_type == rpdfile.FILE_TYPE_VIDEO and backup_type == VIDEO_BACKUP))
+ if do_backup:
logger.debug("Backing up to %s", path)
- task_results_conn = self.backup_devices_by_path[path][0]
- task_results_conn.send((move_succeeded, rpd_file, path_suffix,
- backup_duplicate_overwrite, download_count))
else:
logger.debug("Not backing up to %s", path)
+ # Even if not going to backup to this device, need to send it anyway so
+ # progress bar can be updated. Not this most efficient but the
+ # code is much more simple
+ task_results_conn = self.backup_devices_by_path[path][0]
+ task_results_conn.send((move_succeeded, do_backup, rpd_file,
+ path_suffix,
+ backup_duplicate_overwrite,
+ verify_file, download_count))
def add_device(self, path, name, backup_type):
"""
@@ -2343,8 +2356,8 @@ class RapidApp(dbus.service.Object):
self.download_active_by_scan_pid = []
def modify_files_during_download(self):
- """ Returns True if there is a need to modify files during download"""
- return self.prefs.auto_rotate_jpeg or (self.focal_length is not None)
+ """ Returns True if there is a need to modify or verify files during download"""
+ return self.prefs.auto_rotate_jpeg or (self.focal_length is not None) or self.prefs.verify_file
def start_download(self, scan_pid=None):
@@ -2355,6 +2368,7 @@ class RapidApp(dbus.service.Object):
"""
files_by_scan_pid = self.thumbnails.get_files_checked_for_download(scan_pid)
+ self.check_file_types_to_be_downloaded(files_by_scan_pid)
folders_valid, invalid_dirs = self.check_download_folder_validity(files_by_scan_pid)
if not folders_valid:
@@ -2366,6 +2380,18 @@ class RapidApp(dbus.service.Object):
self.log_error(config.CRITICAL_ERROR, _("Download cannot proceed"),
msg)
else:
+ missing_destinations = self.backup_destinations_missing()
+ if missing_destinations is not None:
+ # Warn user that they have specified that they want to backup a file type, but no such folder exists on backup devices
+ if not missing_destinations[0]:
+ logger.warning("No backup device contains a valid folder for backing up photos")
+ msg = _("No backup device contains a valid folder for backing up %(filetype)s") % {'filetype': _('photos')}
+ else:
+ logger.warning("No backup device contains a valid folder for backing up videos")
+ msg = _("No backup device contains a valid folder for backing up %(filetype)s") % {'filetype': _('videos')}
+
+ self.log_error(config.WARNING, _("Backup problem"), msg)
+
# set time download is starting if it is not already set
# it is unset when all downloads are completed
if self.download_start_time is None:
@@ -2457,9 +2483,17 @@ class RapidApp(dbus.service.Object):
for rpd_file in files:
rpd_file.generate_thumbnail = True
+ verify_file = self.prefs.verify_file
+ if verify_file:
+ # since a file might be modified in the file modify process,
+ # if it will be backed up, need to refresh the md5 once it has
+ # been modified
+ refresh_md5_on_file_change = self.prefs.backup_images
+ else:
+ refresh_md5_on_file_change = False
modify_files_during_download = self.modify_files_during_download()
if modify_files_during_download:
- self.file_modify_manager.add_task((scan_pid, self.prefs.auto_rotate_jpeg, self.focal_length))
+ self.file_modify_manager.add_task((scan_pid, self.prefs.auto_rotate_jpeg, self.focal_length, verify_file, refresh_md5_on_file_change))
modify_pipe = self.file_modify_manager.get_modify_pipe(scan_pid)
else:
modify_pipe = None
@@ -2468,7 +2502,8 @@ class RapidApp(dbus.service.Object):
# Initiate copy files process
self.copy_files_manager.add_task((photo_download_folder,
video_download_folder, scan_pid,
- files, modify_files_during_download,
+ files, verify_file,
+ modify_files_during_download,
modify_pipe))
def copy_files_results(self, source, condition):
@@ -2549,11 +2584,13 @@ class RapidApp(dbus.service.Object):
rpd_file.synchronize_raw_jpg = self.prefs.must_synchronize_raw_jpg()
rpd_file.job_code = self.job_code
- self.subfolder_file_manager.rename_file_and_move_to_subfolder(
- download_succeeded,
- download_count,
- rpd_file
- )
+ # Call this even if download did not succeed e.g. file verification error
+ self.subfolder_file_manager.rename_file_and_move_to_subfolder(
+ download_succeeded,
+ download_count,
+ rpd_file
+ )
+
def file_modify_results(self, source, condition):
"""
'file modify' is a process that runs immediately after 'copy files',
@@ -2595,22 +2632,39 @@ class RapidApp(dbus.service.Object):
self.log_error(config.WARNING, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
- if self.prefs.backup_images and len(self.backup_devices):
- if self.prefs.backup_device_autodetection:
- if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
- path_suffix = self.prefs.backup_identifier
+ if self.prefs.backup_images:
+ if self.backup_possible(rpd_file.file_type):
+ if self.prefs.backup_device_autodetection:
+ if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
+ path_suffix = self.prefs.backup_identifier
+ else:
+ path_suffix = self.prefs.video_backup_identifier
else:
- path_suffix = self.prefs.video_backup_identifier
+ path_suffix = None
+
+ self.backup_manager.backup_file(move_succeeded, rpd_file,
+ path_suffix,
+ self.prefs.backup_duplicate_overwrite,
+ self.prefs.verify_file,
+ download_count)
else:
- path_suffix = None
+ if rpd_file.status == config.STATUS_DOWNLOAD_FAILED:
+ rpd_file.status = config.STATUS_DOWNLOAD_AND_BACKUP_FAILED
+ else:
+ rpd_file.status = config.STATUS_BACKUP_PROBLEM
- self.backup_manager.backup_file(move_succeeded, rpd_file,
- path_suffix,
- self.prefs.backup_duplicate_overwrite,
- download_count)
+ self.file_download_finished(move_succeeded, rpd_file)
else:
self.file_download_finished(move_succeeded, rpd_file)
+ def backup_possible(self, file_type):
+ if file_type == rpdfile.FILE_TYPE_PHOTO:
+ return self.no_photo_backup_devices > 0
+ elif file_type == rpdfile.FILE_TYPE_VIDEO:
+ return self.no_video_backup_devices > 0
+ else:
+ logger.critical("Unrecognized file type when determining if backup is possible")
+
def multiple_backup_devices(self, file_type):
"""Returns true if more than one backup device is being used for that
@@ -2641,22 +2695,24 @@ class RapidApp(dbus.service.Object):
self.time_remaining.update(scan_pid, bytes_downloaded=chunk_downloaded)
elif msg_type == rpdmp.MSG_FILE:
- backup_succeeded, rpd_file = data
+ backup_succeeded, do_backup, rpd_file = data
# Only show an error message if there is more than one device
# backing up files of this type - if that is the case,
- # do not want to reply on showing an error message in the
+ # do not want to rely on showing an error message in the
# function file_download_finished, as it is only called once,
# when all files have been backed up
- if not backup_succeeded and self.multiple_backup_devices(rpd_file.file_type):
+ if not backup_succeeded and self.multiple_backup_devices(rpd_file.file_type) and do_backup:
self.log_error(config.SERIOUS_ERROR,
rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
- self.download_tracker.file_backed_up(rpd_file.unique_id)
- if self.download_tracker.all_files_backed_up(rpd_file.unique_id,
+ if do_backup:
+ self.download_tracker.file_backed_up(rpd_file.unique_id)
+ if self.download_tracker.all_files_backed_up(rpd_file.unique_id,
rpd_file.file_type):
- self.file_download_finished(backup_succeeded, rpd_file)
+ logger.debug("File %s will not be backed up to any more locations", rpd_file.download_name)
+ self.file_download_finished(backup_succeeded or not do_backup, rpd_file)
return True
else:
return False
@@ -2901,7 +2957,7 @@ class RapidApp(dbus.service.Object):
files_to_download = self.download_tracker.get_no_files_in_download(scan_pid)
file_types = self.download_tracker.get_file_types_present(scan_pid)
completed = files_downloaded == files_to_download
- if completed and (self.prefs.backup_images and len(self.backup_devices)):
+ if completed and (self.prefs.backup_images and self.backup_possible(file_type)):
completed = self.download_tracker.all_files_backed_up(unique_id, file_type)
if completed:
@@ -3753,6 +3809,22 @@ class RapidApp(dbus.service.Object):
return (photo_size, video_size)
+ def check_file_types_to_be_downloaded(self, files_by_scan_pid):
+ """Determines what types of files need to be downloaded, setting
+ self.downloading_photos and self.downloading_videos accordingly"""
+ self.downloading_photos = False
+ self.downloading_videos = False
+ while not self.downloading_photos and not self.downloading_videos:
+ for scan_pid in files_by_scan_pid:
+ files = files_by_scan_pid[scan_pid]
+ if not self.downloading_photos:
+ if self.files_of_type_present(files, rpdfile.FILE_TYPE_PHOTO):
+ self.downloading_photos = True
+ if not self.downloading_videos:
+ if self.files_of_type_present(files, rpdfile.FILE_TYPE_VIDEO):
+ self.downloading_videos = True
+
+
def check_download_folder_validity(self, files_by_scan_pid):
"""
Checks validity of download folders based on the file types the user
@@ -3763,21 +3835,8 @@ class RapidApp(dbus.service.Object):
"""
valid = True
invalid_dirs = []
- # first, check what needs to be downloaded - photos and / or videos
- need_photo_folder = False
- need_video_folder = False
- while not need_photo_folder and not need_video_folder:
- for scan_pid in files_by_scan_pid:
- files = files_by_scan_pid[scan_pid]
- if not need_photo_folder:
- if self.files_of_type_present(files, rpdfile.FILE_TYPE_PHOTO):
- need_photo_folder = True
- if not need_video_folder:
- if self.files_of_type_present(files, rpdfile.FILE_TYPE_VIDEO):
- need_video_folder = True
- # second, check validity
- if need_photo_folder:
+ if self.downloading_photos:
if not self.is_valid_download_dir(self.prefs.download_folder,
is_photo_dir=True):
valid = False
@@ -3786,7 +3845,7 @@ class RapidApp(dbus.service.Object):
logger.debug("Photo download folder is valid: %s",
self.prefs.download_folder)
- if need_video_folder:
+ if self.downloading_videos:
if not self.is_valid_download_dir(self.prefs.video_download_folder,
is_photo_dir=False):
valid = False
@@ -3798,6 +3857,19 @@ class RapidApp(dbus.service.Object):
return (valid, invalid_dirs)
+ def backup_destinations_missing(self):
+ if self.prefs.backup_images and self.prefs.backup_device_autodetection:
+ photo_backup_ok = video_backup_ok = True
+ if self.downloading_photos and not self.backup_possible(rpdfile.FILE_TYPE_PHOTO):
+ photo_backup_ok = False
+ if self.downloading_videos and not self.backup_possible(rpdfile.FILE_TYPE_VIDEO):
+ video_backup_ok = False
+ if photo_backup_ok and video_backup_ok:
+ return None
+ else:
+ return (photo_backup_ok, video_backup_ok)
+ return None
+
def same_file_system(self, file1, file2):
"""Returns True if the files / diretories are on the same file system
"""
diff --git a/rapid/rpdfile.py b/rapid/rpdfile.py
index 3749554..15c1722 100644
--- a/rapid/rpdfile.py
+++ b/rapid/rpdfile.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -48,7 +48,7 @@ RAW_EXTENSIONS = ['arw', 'dcr', 'cr2', 'crw', 'dng', 'mos', 'mef', 'mrw',
JPEG_EXTENSIONS = ['jpg', 'jpe', 'jpeg']
-NON_RAW_IMAGE_EXTENSIONS = JPEG_EXTENSIONS + ['tif', 'tiff']
+NON_RAW_IMAGE_EXTENSIONS = JPEG_EXTENSIONS + ['tif', 'tiff', 'mpo']
PHOTO_EXTENSIONS = RAW_EXTENSIONS + NON_RAW_IMAGE_EXTENSIONS
diff --git a/rapid/scan.py b/rapid/scan.py
index f0e4422..f6b7c5e 100755
--- a/rapid/scan.py
+++ b/rapid/scan.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
diff --git a/rapid/subfolderfile.py b/rapid/subfolderfile.py
index ec99a6a..c81a119 100644
--- a/rapid/subfolderfile.py
+++ b/rapid/subfolderfile.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -26,9 +26,8 @@ Runs as a daemon process.
import os, datetime, collections
-import shutil
+#~ import shutil
import errno
-import gio
import multiprocessing
import logging
logger = multiprocessing.get_logger()
@@ -156,9 +155,6 @@ class SubfolderFile(multiprocessing.Process):
self.uses_stored_sequence_no = sequence_values[5]
self.uses_session_sequece_no = sequence_values[6]
self.uses_sequence_letter = sequence_values[7]
- # As of Ubuntu 12.10 / Fedora 18, the file move/rename command is running agonisingly slowly
- # A hackish workaround is to replace it with the standard python function
- self.use_gnome_file_operations = False
logger.debug("Start of day is set to %s", self.day_start.value)
@@ -277,21 +273,12 @@ class SubfolderFile(multiprocessing.Process):
download_full_name = full_base_name + extension
# move (rename) associate file
- if self.use_gnome_file_operations:
- source = gio.File(path=temp_associate_file)
- dest = gio.File(path=download_full_name)
- try:
- source.move(dest, self.progress_callback_no_update, cancellable=None)
- success = True
- except gio.Error, inst:
- success = False
- else:
- try:
- # don't check to see if it already exists
- shutil.move(temp_associate_file, download_full_name)
- success = True
- except:
- success = False
+ try:
+ # don't check to see if it already exists
+ os.rename(temp_associate_file, download_full_name)
+ success = True
+ except:
+ success = False
return (success, download_full_name)
@@ -328,6 +315,29 @@ class SubfolderFile(multiprocessing.Process):
return rpd_file
+ def check_for_fatal_name_generation_errors(self, rpd_file):
+ """Returns False if either the download subfolder or filename are blank
+
+ Else returns True"""
+
+ if not rpd_file.download_subfolder or not rpd_file.download_name:
+ if not rpd_file.download_subfolder and not rpd_file.download_name:
+ area = _("subfolder and filename")
+ elif not rpd_file.download_name:
+ area = _("filename")
+ else:
+ area = _("subfolder")
+ rpd_file.add_problem(None, pn.ERROR_IN_NAME_GENERATION, {'filetype': rpd_file.title_capitalized, 'area': area})
+ rpd_file.add_extra_detail(pn.NO_DATA_TO_NAME, {'filetype': area})
+ rpd_file.status = config.STATUS_DOWNLOAD_FAILED
+
+ rpd_file.error_title = rpd_file.problem.get_title()
+ rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
+ {'problem': rpd_file.problem.get_problems(),
+ 'file': rpd_file.full_file_name}
+ return False
+ else:
+ return True
def run(self):
"""
@@ -370,8 +380,6 @@ class SubfolderFile(multiprocessing.Process):
if download_succeeded:
- if self.use_gnome_file_operations:
- temp_file = gio.File(rpd_file.temp_full_file_name)
synchronize_raw_jpg_failed = False
if not (rpd_file.synchronize_raw_jpg and
@@ -383,6 +391,8 @@ class SubfolderFile(multiprocessing.Process):
sync_photo_name, sync_photo_ext = os.path.splitext(rpd_file.name)
if not load_metadata(rpd_file):
synchronize_raw_jpg_failed = True
+ rpd_file.status = config.STATUS_DOWNLOAD_FAILED
+ self.check_for_fatal_name_generation_errors(rpd_file)
else:
j, sequence_to_use = self.sync_raw_jpeg.matching_pair(
name=sync_photo_name, extension=sync_photo_ext,
@@ -452,22 +462,9 @@ class SubfolderFile(multiprocessing.Process):
logger.debug("Failed to generate subfolder name for file: %s", rpd_file.name)
# Check for any errors
- if not rpd_file.download_subfolder or not rpd_file.download_name:
- if not rpd_file.download_subfolder and not rpd_file.download_name:
- area = _("subfolder and filename")
- elif not rpd_file.download_name:
- area = _("filename")
- else:
- area = _("subfolder")
- rpd_file.add_problem(None, pn.ERROR_IN_NAME_GENERATION, {'filetype': rpd_file.title_capitalized, 'area': area})
- rpd_file.add_extra_detail(pn.NO_DATA_TO_NAME, {'filetype': area})
- generation_succeeded = False
- rpd_file.status = config.STATUS_DOWNLOAD_FAILED
+ generation_succeeded = self.check_for_fatal_name_generation_errors(rpd_file)
+
- rpd_file.error_title = rpd_file.problem.get_title()
- rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
- {'problem': rpd_file.problem.get_problems(),
- 'file': rpd_file.full_file_name}
if generation_succeeded:
@@ -475,80 +472,41 @@ class SubfolderFile(multiprocessing.Process):
rpd_file.download_full_file_name = os.path.join(rpd_file.download_path, rpd_file.download_name)
rpd_file.download_full_base_name = os.path.splitext(rpd_file.download_full_file_name)[0]
- subfolder = gio.File(path=rpd_file.download_path)
-
- # Create subfolder if it does not exist.
- # It is possible to skip the query step, and just try to create
- # the directories and ignore the error of it already existing -
- # but it takes twice as long to fail with an error than just
- # run the straight query
-
logger.debug("Probing to see if subfolder already exists...")
- if not subfolder.query_exists(cancellable=None):
- if self.use_gnome_file_operations:
- try:
- logger.debug("...subfolder doesn't exist: creating it using gnome...")
- subfolder.make_directory_with_parents(cancellable=gio.Cancellable())
- logger.debug("...subfolder created")
- except gio.Error, inst:
- # The directory may have been created by another process
- # between the time it takes to query and the time it takes
- # to create a new directory. Ignore such errors.
- if inst.code <> gio.ERROR_EXISTS:
- logger.error("Failed to create download subfolder: %s", rpd_file.download_path)
- logger.error(inst)
- rpd_file.error_title = _("Failed to create download subfolder")
- rpd_file.error_msg = _("Path: %s") % rpd_file.download_path
- else:
- try:
- logger.debug("...subfolder doesn't exist: creating it using python...")
- os.makedirs(rpd_file.download_path)
- logger.debug("...subfolder created")
- except IOError as inst:
- if inst.errno <> errno.EEXIST:
- logger.error("Failed to create download subfolder: %s", rpd_file.download_path)
- logger.error(inst)
- rpd_file.error_title = _("Failed to create download subfolder")
- rpd_file.error_msg = _("Path: %s") % rpd_file.download_path
+ if not os.path.isdir(rpd_file.download_path):
+ try:
+ logger.debug("...subfolder doesn't exist: creating it...")
+ os.makedirs(rpd_file.download_path)
+ logger.debug("...subfolder created")
+ except IOError as inst:
+ if inst.errno <> errno.EEXIST:
+ logger.error("Failed to create download subfolder: %s", rpd_file.download_path)
+ logger.error(inst)
+ rpd_file.error_title = _("Failed to create download subfolder")
+ rpd_file.error_msg = _("Path: %s") % rpd_file.download_path
else:
logger.debug("...subfolder already exists")
# Move temp file to subfolder
add_unique_identifier = False
- if self.use_gnome_file_operations:
- download_file = gio.File(rpd_file.download_full_file_name)
- try:
- logger.debug("Attempting to use Gnome to rename file %s to %s .....", rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
- temp_file.move(download_file, self.progress_callback_no_update, cancellable=None)
- logger.debug("....successfully renamed file")
- move_succeeded = True
- if rpd_file.status <> config.STATUS_DOWNLOADED_WITH_WARNING:
- rpd_file.status = config.STATUS_DOWNLOADED
- except gio.Error as inst:
- if inst.code == gio.ERROR_EXISTS:
- rpd_file, add_unique_identifier = download_file_exists(rpd_file)
- else:
- rpd_file = self.download_failure_file_error(rpd_file, inst)
- else:
- # Use python library functions to rename file
- # Sadly this code basically duplicates the logic of the previous block
- try:
- if os.path.exists(rpd_file.download_full_file_name):
- raise IOError(errno.EEXIST, "File exists: %s" % rpd_file.download_full_file_name)
- logger.debug("Attempting to use python to rename file %s to %s .....", rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
- shutil.move(rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
- logger.debug("....successfully renamed file")
- move_succeeded = True
- if rpd_file.status <> config.STATUS_DOWNLOADED_WITH_WARNING:
- rpd_file.status = config.STATUS_DOWNLOADED
- except IOError as inst:
- if inst.errno == errno.EEXIST:
- rpd_file, add_unique_identifier = self.download_file_exists(rpd_file)
- else:
- rpd_file = self.download_failure_file_error(rpd_file, inst.strerror)
- except:
- rpd_file = self.download_failure_file_error(rpd_file, "An unknown error occurred while renaming the file")
+ # Use python library functions to rename file
+ try:
+ if os.path.exists(rpd_file.download_full_file_name):
+ raise IOError(errno.EEXIST, "File exists: %s" % rpd_file.download_full_file_name)
+ logger.debug("Attempting to rename file %s to %s .....", rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
+ os.rename(rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
+ logger.debug("....successfully renamed file")
+ move_succeeded = True
+ if rpd_file.status <> config.STATUS_DOWNLOADED_WITH_WARNING:
+ rpd_file.status = config.STATUS_DOWNLOADED
+ except (IOError, OSError) as inst:
+ if inst.errno == errno.EEXIST:
+ rpd_file, add_unique_identifier = self.download_file_exists(rpd_file)
+ else:
+ rpd_file = self.download_failure_file_error(rpd_file, inst.strerror)
+ except:
+ rpd_file = self.download_failure_file_error(rpd_file, "An unknown error occurred while renaming the file")
if add_unique_identifier:
name = os.path.splitext(rpd_file.download_name)
@@ -563,28 +521,18 @@ class SubfolderFile(multiprocessing.Process):
rpd_file.download_path,
rpd_file.download_name)
- if self.use_gnome_file_operations:
- download_file = gio.File(
- rpd_file.download_full_file_name)
- try:
- temp_file.move(download_file, self.progress_callback_no_update, cancellable=None)
- rpd_file, move_succeeded, suffix_already_used = self.added_unique_identifier(rpd_file)
- except gio.Error, inst:
- if inst.code <> gio.ERROR_EXISTS:
- rpd_file = self.download_failure_file_error(rpd_file, inst)
- else:
- try:
- if os.path.exists(rpd_file.download_full_file_name):
- raise IOError(errno.EEXIST, "File exists: %s" % rpd_file.download_full_file_name)
- shutil.move(rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
- rpd_file, move_succeeded, suffix_already_used = self.added_unique_identifier(rpd_file)
- except IOError as inst:
- if inst.errno <> errno.EEXIST:
- rpd_file = self.download_failure_file_error(rpd_file, inst)
- break
- except:
+ try:
+ if os.path.exists(rpd_file.download_full_file_name):
+ raise IOError(errno.EEXIST, "File exists: %s" % rpd_file.download_full_file_name)
+ os.rename(rpd_file.temp_full_file_name, rpd_file.download_full_file_name)
+ rpd_file, move_succeeded, suffix_already_used = self.added_unique_identifier(rpd_file)
+ except IOError as inst:
+ if inst.errno <> errno.EEXIST:
rpd_file = self.download_failure_file_error(rpd_file, inst)
break
+ except:
+ rpd_file = self.download_failure_file_error(rpd_file, inst)
+ break
@@ -623,37 +571,22 @@ class SubfolderFile(multiprocessing.Process):
# copy and rename XMP sidecar file
# generate_name() has generated xmp extension with correct capitalization
download_xmp_full_name = rpd_file.download_full_base_name + rpd_file.xmp_extension
- if self.use_gnome_file_operations:
- source = gio.File(path=rpd_file.temp_xmp_full_name)
- dest = gio.File(path=download_xmp_full_name)
- try:
- source.move(dest, self.progress_callback_no_update, cancellable=None)
- rpd_file.download_xmp_full_name = download_xmp_full_name
- except gio.Error, inst:
- logger.error("Failed to move XMP sidecar file %s", download_xmp_full_name)
- else:
- try:
- shutil.move(rpd_file.temp_xmp_full_name, download_xmp_full_name)
- rpd_file.download_xmp_full_name = download_xmp_full_name
- except:
- logger.error("Failed to move XMP sidecar file %s", download_xmp_full_name)
+
+ try:
+ os.rename(rpd_file.temp_xmp_full_name, download_xmp_full_name)
+ rpd_file.download_xmp_full_name = download_xmp_full_name
+ except:
+ logger.error("Failed to move XMP sidecar file %s", download_xmp_full_name)
if not move_succeeded:
logger.error("%s: %s - %s", rpd_file.full_file_name,
rpd_file.problem.get_title(),
rpd_file.problem.get_problems())
- if self.use_gnome_file_operations:
- try:
- temp_file.delete(cancellable=None)
- except gio.Error, inst:
- logger.error("Failed to delete temporary file %s", rpd_file.temp_full_file_name)
- logger.error(inst)
- else:
- try:
- os.remove(rpd_file.temp_full_file_name)
- except:
- logger.error("Failed to delete temporary file %s", rpd_file.temp_full_file_name)
+ try:
+ os.remove(rpd_file.temp_full_file_name)
+ except:
+ logger.error("Failed to delete temporary file %s", rpd_file.temp_full_file_name)
diff --git a/rapid/thumbnail.py b/rapid/thumbnail.py
index 69a6cf7..ba72828 100644
--- a/rapid/thumbnail.py
+++ b/rapid/thumbnail.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: latin1 -*-
-### Copyright (C) 2011-2012 Damon Lynch <damonlynch@gmail.com>
+### Copyright (C) 2011-2014 Damon Lynch <damonlynch@gmail.com>
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
@@ -46,26 +46,26 @@ logger = multiprocessing.get_logger()
def get_stock_photo_image():
length = min(gtk.gdk.screen_width(), gtk.gdk.screen_height())
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(paths.share_dir('glade3/photo.svg'), length, length)
- image = pixbuf_to_image(pixbuf)
+ image = pixbuf_to_image(pixbuf)
return image
-
+
def get_stock_photo_image_icon():
image = Image.open(paths.share_dir('glade3/photo66.png'))
image = image.convert("RGBA")
return image
-
+
def get_stock_video_image():
length = min(gtk.gdk.screen_width(), gtk.gdk.screen_height())
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(paths.share_dir('glade3/video.svg'), length, length)
- image = pixbuf_to_image(pixbuf)
+ image = pixbuf_to_image(pixbuf)
return image
-
+
def get_stock_video_image_icon():
image = Image.open(paths.share_dir('glade3/video66.png'))
image = image.convert("RGBA")
return image
-
-
+
+
class PhotoIcons():
stock_thumbnail_image_icon = get_stock_photo_image_icon()
@@ -83,7 +83,7 @@ def upsize_pil(image, size):
else:
width = int((width_orig / height_orig) * height_max)
height=height_max
-
+
return image.resize((width, height), Image.ANTIALIAS)
def downsize_pil(image, box, fit=False):
@@ -91,7 +91,7 @@ def downsize_pil(image, box, fit=False):
image: Image - an Image-object
box: tuple(x, y) - the bounding box of the result image
fix: boolean - crop the image to fill the box
-
+
Code adpated from example by Christian Harms
Source: http://united-coders.com/christian-harms/image-resizing-tips-every-coder-should-know
"""
@@ -105,7 +105,7 @@ def downsize_pil(image, box, fit=False):
logger.debug("quick resize %sx%s", image.size[0]/factor, image.size[1]/factor)
image.thumbnail((image.size[0]/factor, image.size[1]/factor), Image.NEAREST)
logger.debug("did first thumbnail")
-
+
#calculate the cropping box and get the cropped part
if fit:
x1 = y1 = 0
@@ -119,33 +119,33 @@ def downsize_pil(image, box, fit=False):
x1 = x2/2-box[0]*hRatio/2
x2 = x2/2+box[0]*hRatio/2
image = image.crop((x1,y1,x2,y2))
-
+
#Resize the image with best quality algorithm ANTI-ALIAS
logger.debug("about to actually downsize using image.thumbnail")
image.thumbnail(box, Image.ANTIALIAS)
logger.debug("it downsized")
-
+
class PicklablePIL:
def __init__(self, image):
self.size = image.size
self.mode = image.mode
self.image_data = image.tostring()
-
+
def get_image(self):
return Image.fromstring(self.mode, self.size, self.image_data)
-
+
def get_pixbuf(self):
return image_to_pixbuf(self.get_image())
class Thumbnail:
-
+
# file types from which to remove letterboxing (black bands in the thumbnail
# previews)
crop_thumbnails = ('CR2', 'DNG', 'RAF', 'ORF', 'PEF', 'ARW')
-
+
def _ignore_embedded_160x120_thumbnail(self, max_size_needed, metadata):
return max_size_needed is None or max_size_needed[0] > 160 or max_size_needed[1] > 120 or not metadata.exif_thumbnail.data
-
+
def _get_thumbnail_data(self, metadata, max_size_needed):
logger.debug("Getting thumbnail data %s", max_size_needed)
if self._ignore_embedded_160x120_thumbnail(max_size_needed, metadata):
@@ -165,7 +165,7 @@ class Thumbnail:
thumbnail = metadata.exif_thumbnail
lowrez = True
return (thumbnail.data, lowrez)
-
+
def _process_thumbnail(self, image, size_reduced):
image_ok = True
if image.mode <> "RGBA":
@@ -179,14 +179,14 @@ class Thumbnail:
thumbnail = PicklablePIL(image)
if size_reduced is not None:
thumbnail_icon = image.copy()
- downsize_pil(thumbnail_icon, size_reduced, fit=False)
+ downsize_pil(thumbnail_icon, size_reduced, fit=False)
thumbnail_icon = PicklablePIL(thumbnail_icon)
else:
thumbnail_icon = None
else:
thumbnail = thumbnail_icon = None
return (thumbnail, thumbnail_icon)
-
+
def _get_photo_thumbnail(self, full_file_name, size_max, size_reduced):
thumbnail = None
thumbnail_icon = None
@@ -224,14 +224,14 @@ class Thumbnail:
try:
orientation = metadata['Exif.Image.Orientation'].value
except:
- orientation = None
+ orientation = None
if lowrez:
# need to remove letterboxing / pillarboxing from some
# RAW thumbnails
if os.path.splitext(full_file_name)[1][1:].upper() in Thumbnail.crop_thumbnails:
image2 = image.crop((0, 8, 160, 112))
image2.load()
- image = image2
+ image = image2
if size_max is not None and (image.size[0] > size_max[0] or image.size[1] > size_max[1]):
logger.debug("downsizing")
downsize_pil(image, size_max, fit=False)
@@ -249,7 +249,7 @@ class Thumbnail:
logger.debug("...got thumbnail for %s", full_file_name)
return (thumbnail, thumbnail_icon)
-
+
def _get_video_thumbnail(self, full_file_name, thm_full_name, size_max, size_reduced):
thumbnail = None
thumbnail_icon = None
@@ -269,7 +269,7 @@ class Thumbnail:
else:
thumbnail = add_filmstrip(thumbnail)
image = pixbuf_to_image(thumbnail)
-
+
if image is None:
try:
tmp_dir = tempfile.mkdtemp(prefix="rpd-tmp")
@@ -284,10 +284,10 @@ class Thumbnail:
logger.error("Error generating thumbnail for %s", full_file_name)
if image:
thumbnail, thumbnail_icon = self._process_thumbnail(image, size_reduced)
-
- logger.debug("...got thumbnail for %s", full_file_name)
+
+ logger.debug("...got thumbnail for %s", full_file_name)
return (thumbnail, thumbnail_icon)
-
+
def get_thumbnail(self, full_file_name, thm_full_name, file_type, size_max=None, size_reduced=None):
logger.debug("Getting thumbnail for %s...", full_file_name)
if file_type == rpdfile.FILE_TYPE_PHOTO:
@@ -295,8 +295,8 @@ class Thumbnail:
return self._get_photo_thumbnail(full_file_name, size_max, size_reduced)
else:
return self._get_video_thumbnail(full_file_name, thm_full_name, size_max, size_reduced)
-
-
+
+
class GetPreviewImage(multiprocessing.Process):
def __init__(self, results_pipe):
multiprocessing.Process.__init__(self)
@@ -304,8 +304,8 @@ class GetPreviewImage(multiprocessing.Process):
self.results_pipe = results_pipe
self.thumbnail_maker = Thumbnail()
self.stock_photo_thumbnail_image = None
- self.stock_video_thumbnail_image = None
-
+ self.stock_video_thumbnail_image = None
+
def get_stock_image(self, file_type):
"""
Get stock image for file type scaled to the current size of the screen
@@ -317,8 +317,8 @@ class GetPreviewImage(multiprocessing.Process):
else:
if self.stock_video_thumbnail_image is None:
self.stock_video_thumbnail_image = PicklablePIL(get_stock_video_image())
- return self.stock_video_thumbnail_image
-
+ return self.stock_video_thumbnail_image
+
def run(self):
while True:
unique_id, full_file_name, thm_full_name, file_type, size_max = self.results_pipe.recv()
@@ -329,7 +329,7 @@ class GetPreviewImage(multiprocessing.Process):
class GenerateThumbnails(multiprocessing.Process):
- def __init__(self, scan_pid, files, batch_size, results_pipe, terminate_queue,
+ def __init__(self, scan_pid, files, batch_size, results_pipe, terminate_queue,
run_event):
multiprocessing.Process.__init__(self)
self.results_pipe = results_pipe
@@ -338,32 +338,33 @@ class GenerateThumbnails(multiprocessing.Process):
self.files = files
self.run_event = run_event
self.results = []
-
+
self.thumbnail_maker = Thumbnail()
-
+
self.scan_pid = scan_pid
-
-
+
+
def run(self):
counter = 0
i = 0
for f in self.files:
-
+
# pause if instructed by the caller
self.run_event.wait()
-
+
if not self.terminate_queue.empty():
x = self.terminate_queue.get()
# terminate immediately
logger.info("Terminating thumbnailing")
return None
-
+
thumbnail, thumbnail_icon = self.thumbnail_maker.get_thumbnail(
f.full_file_name,
f.thm_full_name,
f.file_type,
+ #~ f.extension,
(160, 120), (100,100))
-
+
self.results.append((f.unique_id, thumbnail_icon, thumbnail))
counter += 1
if counter == self.batch_size:
@@ -371,10 +372,10 @@ class GenerateThumbnails(multiprocessing.Process):
self.results = []
counter = 0
i += 1
-
+
if counter > 0:
# send any remaining results
self.results_pipe.send((rpdmp.CONN_PARTIAL, self.results))
self.results_pipe.send((rpdmp.CONN_COMPLETE, self.scan_pid))
self.results_pipe.close()
-
+