summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog76
-rw-r--r--rapid/backupfile.py5
-rw-r--r--rapid/device.py18
-rw-r--r--rapid/downloadtracker.py50
-rw-r--r--rapid/glade3/about.ui9
-rw-r--r--rapid/glade3/about.ui.h1
-rw-r--r--rapid/glade3/prefs.ui6
-rw-r--r--rapid/glade3/prefs.ui.h99
-rw-r--r--rapid/glade3/rapid.ui.h32
-rw-r--r--rapid/preferencesdialog.py36
-rw-r--r--rapid/prefsrapid.py1
-rwxr-xr-xrapid/problemnotification.py2
-rwxr-xr-xrapid/rapid.py322
-rw-r--r--rapid/subfolderfile.py4
-rw-r--r--rapid/thumbnail.py3
15 files changed, 393 insertions, 271 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog
index 06cdcf8..35cd727 100644
--- a/rapid/ChangeLog
+++ b/rapid/ChangeLog
@@ -1,27 +1,53 @@
Version 0.4.2
-------------
-2011-08-xx
+2011-10-01
-Added feature in Prefences window to remove any paths that have previously
+Added feature in Preferences window to remove any paths that have previously
been marked to always be scanned or ignored. These paths can be specified when
automatic detection of Portable Storage Devices is enabled.
-Fixed bug #768026: added option to ignore paths from which to download.
+Fixed bug #768026: added option to ignore paths from which to download -
-You can now specify specific paths never to scan for photos or videos. By
-default, any path ending in .Trash or .thumbnails is ignored. Advanced users
-can specify python-style regular expressions.
+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 backup 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.
+
+Fixed bug #838722: wrong file types may be backed up to external devices -
+
+Fixed a bug when auto detection of backup devices is enabled, files of the wrong
+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
+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
+used during a download.
+
+Fixed bug #859242: Crash when displaying a preview of file without an extracted
+thumbnail.
+
+Fixed bug #810559: Crash when generating thumbnail images
Fixed bug #789995: crash when --reset-settings option is given on the command
line.
-Fixed bug #795446: small error in translation template.
+Fixed bugs #795446 and #844714: small errors in translation template.
+
+Fixed a bug in the Swedish translation.
-Updated Brazilian, Dutch, Français, German, Japanese, Polish, Russian, Spanish
-and Turkish translations.
+Added Danish translation, by Torben Gundtofte-Bruun. Updated Brazilian, Czech,
+Dutch, French, German, Hungarian, Italian, Japanese, Norwegian, Polish, Russian,
+Serbian, Slovak, Spanish, Swedish and Turkish translations.
Version 0.4.1
@@ -32,7 +58,7 @@ Version 0.4.1
Added exif Artist and Copyright metadata options to file and subfolder name
generation.
-Fixed bug #774476: thumbnails occassionally not sorted by file modification
+Fixed bug #774476: thumbnails occasionally not sorted by file modification
time.
Fixed bug #784399: job code not prompted for after preference change.
@@ -52,7 +78,7 @@ Version 0.4.0
2011-04-28
-Features added since Release Candiate 1:
+Features added since Release Candidate 1:
* Allow multiple selection of files to check or uncheck for downloading.
* Automation feature to delete downloaded files from a device.
@@ -200,7 +226,7 @@ download. It also performs the actual downloads quicker. It will use
multiple CPU cores if they are available.
Rapid Photo Downloader now requires version 0.3.0 or newer of pyexiv2. It also
-requries Python Imaging (PIL) to run. It will only run on recent Linux
+requires Python Imaging (PIL) to run. It will only run on recent Linux
distributions such as Ubuntu 10.04 or newer. It has been tested on Ubuntu 10.04,
10.10 and 11.04, as well as Fedora 14. (There is currently an unusual bug
adjusting some preferences when running Ubuntu 11.04. See bug #739021).
@@ -236,7 +262,7 @@ Version 0.3.5
2011-03-23
-The primrary purpose of this release is update translations and fix bug #714039,
+The primary purpose of this release is update translations and fix bug #714039,
where under certain circumstances the program could crash while downloading
files.
@@ -291,7 +317,7 @@ is that the video creation date is now correctly read (the creation time read by
kaa metadata was sometimes wrong by a few hours). Kaa-metadata is still used to
extract some the codec, fourcc and frames per second (FPS) metadata.
-Fixed bug #640722: Added preliminary support for Samusung SRW files. Current
+Fixed bug #640722: Added preliminary support for Samsung SRW files. Current
versions of Exiv2 and pyexiv2 can read some but not all metadata from this new
RAW format. If you try to use metadata that cannot be extracted, Rapid Photo
Downloader will issue a warning.
@@ -317,7 +343,7 @@ Version 0.3.2
Added Norwegian Nynorsk translation. Updated Chinese, Finnish, Hungarian, Dutch,
Occitan (post 1500), Polish, Brazilian Portuguese, and Russian translations.
-Fixed crash on startup when checking for freespace, and the download folder does
+Fixed crash on startup when checking for free space, and the download folder does
not exist.
@@ -330,7 +356,7 @@ The main window now works more effectively on tiny screens, such as those found
on netbooks. If the screen height is less than or equal to 650 pixels, elements
in the preview pane are removed, and the spacing is tightened.
-The amount of free space available on the filesystem where photos are to be
+The amount of free space available on the file-system where photos are to be
downloaded is now displayed in the status bar. (Note this is only the case on
moderately up-to-date Linux distributions that use GVFS, such as Ubuntu 8.10 or
higher).
@@ -431,7 +457,7 @@ Don't stop a file being downloaded if a valid subfolder or filename can be
generated using a Job Code.
Bug fix: don't automatically exit if there were errors or warnings and a
-download was occuring from more than one device.
+download was occurring from more than one device.
Auto start now works correctly again.
@@ -560,7 +586,7 @@ system has, it must either be running pyexiv2 >= 0.2.0, or have exiv2 installed.
Fixed bug #483222: sometimes images could not be downloaded to NTFS partitions.
This fix was a welcome side effect of using GIO to copy images, instead of
-relying on the python standard libary.
+relying on the python standard library.
Error message headings in the Error Log are now displayed in a red font.
@@ -594,7 +620,7 @@ dialog window.
Fixed bug #510484: Crashes when fails to create temporary download directory.
Fixed bug #510516: Program now checks to see if the download folder exists and
-is writeable. If automatic detection of image devices is not enabled, it checks
+is writable. If automatic detection of image devices is not enabled, it checks
to see if the image location path exists.
Updated Czech, Dutch, Finnish, French, German, Hungarian, Italian, Polish,
@@ -617,7 +643,7 @@ example, if 200 RAW images and 200 matching JPEG images are downloaded, the
value of Downloads today will be incremented by 200, and not 400. The same goes
for the rest of the sequence values, including the Stored number sequence
number. Images are detected by comparing filename, as well as the exif value for
-the date and time the image was created (including subseconds when the camera
+the date and time the image was created (including sub seconds when the camera
records this value). This option will take effect regardless of whether the RAW
and JPEG images are stored on different memory cards or the same memory card.
Furthermore, if they are stored on separate memory cards, you can download from
@@ -773,7 +799,7 @@ to the documentation found online at the program's website. This documentation
is now complete.
The Preferences Dialog Window is now navigated using a list control, as it was
-in early versions of the program. This change was necesseary because with some
+in early versions of the program. This change was necessary because with some
translations, the dialog window was becoming too wide with the normal tab
layout. Usability of the preferences dialog is improved: it will now resize
itself based on its content.
@@ -928,7 +954,7 @@ more helpful information is printed to the console output.
Bug fix: better handle automated shortening Canon names like 'Canon 5D Mark II'.
It is now shortened to '5DMkII' instead of merely '5D'.
-Bug fix: reenable example of image renaming and subfolder name generation by
+Bug fix: re-enable example of image renaming and subfolder name generation by
using first image from the first available download device. This was
inadvertently disabled in an earlier beta.
@@ -1001,7 +1027,7 @@ Version 0.0.8 beta 2
2009-03-25
-First Ububtu package.
+First Ubuntu package.
Rename tarball package to suit package name.
@@ -1046,7 +1072,7 @@ robust.
Remove list control from preferences, reverting to normal tabbed preferences,
as the window was becoming too wide.
-Show notifcations via libnotify.
+Show notifications via libnotify.
Error and warning icons can now be clicked on to open log window.
diff --git a/rapid/backupfile.py b/rapid/backupfile.py
index 6b6d11d..40039bc 100644
--- a/rapid/backupfile.py
+++ b/rapid/backupfile.py
@@ -31,6 +31,9 @@ import rpdfile
import problemnotification as pn
import config
+PHOTO_BACKUP = 1
+VIDEO_BACKUP = 2
+PHOTO_VIDEO_BACKUP = 3
from gettext import gettext as _
@@ -149,7 +152,7 @@ class BackupFiles(multiprocessing.Process):
if backup_duplicate_overwrite:
flags = gio.FILE_COPY_OVERWRITE
else:
- flags = gio.FILE_COPY_NONE
+ flags = gio.FILE_COPY_NONE
try:
source.copy(dest, self.progress_callback, flags,
diff --git a/rapid/device.py b/rapid/device.py
index dcfdf94..423acdd 100644
--- a/rapid/device.py
+++ b/rapid/device.py
@@ -156,21 +156,5 @@ def is_DCIM_device(path):
test_path = os.path.join(path, "DCIM")
return utilities.is_directory(test_path)
-def is_backup_media(path, identifiers, writeable=True):
- """ Test to see if path is used as a backup medium for storing photos or videos
-
- Identifiers is expected to be a list of folder names to check to see
- if the path is a backup path. Only one of them needs to be present
- for the path to be considered a backup medium.
-
- If writeable is True, the directory must be writeable by the user """
- suitable = False
-
- for identifier in identifiers:
- if os.path.isdir(os.path.join(path, identifier)):
- if writeable:
- suitable = os.access(os.path.join(path, identifier), os.W_OK)
- else:
- suitable = True
- return suitable
+
diff --git a/rapid/downloadtracker.py b/rapid/downloadtracker.py
index 824a812..df48754 100644
--- a/rapid/downloadtracker.py
+++ b/rapid/downloadtracker.py
@@ -40,10 +40,16 @@ class DownloadTracker:
def _refresh_values(self):
""" these values are reset when a download is completed"""
self.size_of_download_in_bytes_by_scan_pid = dict()
+ self.total_bytes_backed_up_by_scan_pid = dict()
+ self.size_of_photo_backup_in_bytes_by_scan_pid = dict()
+ self.size_of_video_backup_in_bytes_by_scan_pid = dict()
self.raw_size_of_download_in_bytes_by_scan_pid = dict()
self.total_bytes_copied_by_scan_pid = dict()
- self.total_bytes_backed_up_by_scan_pid = dict()
+ self.total_bytes_video_backed_up_by_scan_pid = dict()
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
@@ -66,11 +72,30 @@ class DownloadTracker:
self.backups_performed_by_unique_id = dict()
self.auto_delete = dict()
- def set_no_backup_devices(self, no_backup_devices):
- self.no_backup_devices = no_backup_devices
+ 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)
- def init_stats(self, scan_pid, bytes, no_files):
+ 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
+ self.no_photos_in_download_by_scan_pid[scan_pid] = no_photos_to_download
+ self.no_videos_in_download_by_scan_pid[scan_pid] = no_videos_to_download
+ self.size_of_photo_backup_in_bytes_by_scan_pid[scan_pid] = photo_size_in_bytes * self.no_photo_backup_devices
+ self.size_of_video_backup_in_bytes_by_scan_pid[scan_pid] = video_size_in_bytes * self.no_video_backup_devices
+ 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
+ # 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
self.raw_size_of_download_in_bytes_by_scan_pid[scan_pid] = bytes
@@ -86,7 +111,6 @@ class DownloadTracker:
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)
@@ -119,9 +143,12 @@ class DownloadTracker:
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):
+ def all_files_backed_up(self, unique_id, file_type):
if unique_id in self.backups_performed_by_unique_id:
- return self.backups_performed_by_unique_id[unique_id] == self.no_backup_devices
+ if file_type == FILE_TYPE_PHOTO:
+ return self.backups_performed_by_unique_id[unique_id] == self.no_photo_backup_devices
+ else:
+ return self.backups_performed_by_unique_id[unique_id] == self.no_video_backup_devices
else:
logger.critical("Unexpected unique_id in self.backups_performed_by_unique_id")
return True
@@ -155,14 +182,17 @@ class DownloadTracker:
has been completed
"""
- # three components: copy (download), rename, and backup
+ # 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.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.raw_size_of_download_in_bytes_by_scan_pid[scan_pid] *
- self.no_backup_devices)) * 100
+ 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):
diff --git a/rapid/glade3/about.ui b/rapid/glade3/about.ui
index ef8a65b..3e59698 100644
--- a/rapid/glade3/about.ui
+++ b/rapid/glade3/about.ui
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.20"/>
- <!-- interface-naming-policy project-wide -->
<object class="GtkAboutDialog" id="about">
<property name="can_focus">False</property>
<property name="border_width">5</property>
@@ -22,9 +21,13 @@ You should have received a copy of the GNU General Public License along with Rap
Lőrincz András &lt;level.andrasnak@gmail.com&gt;
Michel Ange &lt;michelange@wanadoo.fr&gt;
Alain J. Baudrez &lt;a.baudrez@gmail.com&gt;
+Kevin Brubeck Unhammer &lt;unhammer@fsfe.org&gt;
Bert &lt;crinbert@yahoo.com&gt;
Martin Dahl Moe
+Marco de Freitas &lt;marcodefreitas@gmail.com&gt;
Martin Egger &lt;martin.egger@gmx.net&gt;
+Emanuele Grande &lt;caccolangrifata@gmail.com&gt;
+Torben Gundtofte-Bruun &lt;torben@g-b.dk&gt;
Miroslav Matejaš &lt;silverspace@ubuntu-hr.org&gt;
Nicolás M. Zahlut &lt;nzahlut@live.com&gt;
Erik M
@@ -49,12 +52,12 @@ Aron Xu &lt;happyaron.xu@gmail.com&gt;
<property name="logo">rapid-photo-downloader.svg</property>
<property name="wrap_license">True</property>
<child internal-child="vbox">
- <object class="GtkVBox" id="dialog-vbox1">
+ <object class="GtkBox" id="dialog-vbox1">
<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_area1">
+ <object class="GtkButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
diff --git a/rapid/glade3/about.ui.h b/rapid/glade3/about.ui.h
deleted file mode 100644
index 7c721a5..0000000
--- a/rapid/glade3/about.ui.h
+++ /dev/null
@@ -1 +0,0 @@
-char *s = N_("Import your photos and videos efficiently and reliably");
diff --git a/rapid/glade3/prefs.ui b/rapid/glade3/prefs.ui
index d5ff1e0..0a13978 100644
--- a/rapid/glade3/prefs.ui
+++ b/rapid/glade3/prefs.ui
@@ -2309,7 +2309,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<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 that you have chosen to always scan or ignore when automatic detection of Portable Storage Devices is enabled.</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>
<property name="wrap">True</property>
</object>
<packing>
@@ -2454,7 +2454,7 @@ When this option is enabled, and a potential device is detected, you will be pro
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
- <property name="label" translatable="yes">Specify any paths you want ignored when scanning devices for photos or videos. Any path ending with the values below will not be scanned.</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>
<property name="wrap">True</property>
</object>
<packing>
@@ -2957,7 +2957,7 @@ When this option is enabled, and a potential device is detected, you will be pro
</packing>
</child>
<child>
- <object class="GtkLabel" id="video_backup_location_label">
+ <object class="GtkLabel" id="backup_video_location_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
diff --git a/rapid/glade3/prefs.ui.h b/rapid/glade3/prefs.ui.h
deleted file mode 100644
index ef85be3..0000000
--- a/rapid/glade3/prefs.ui.h
+++ /dev/null
@@ -1,99 +0,0 @@
-char *s = N_(" ");
-char *s = N_(" hh:mm");
-char *s = N_(":");
-char *s = N_("<b>Backup</b>");
-char *s = N_("<b>Compatibility with Other Operating Systems</b>");
-char *s = N_("<b>Download Folder</b>");
-char *s = N_("<b>Download Subfolders</b>");
-char *s = N_("<b>Example</b>");
-char *s = N_("<b>Job Codes</b>");
-char *s = N_("<b>Photo Rename</b>");
-char *s = N_("<b>Photo and Video Name Conflicts</b>");
-char *s = N_("<b>Program Automation</b>");
-char *s = N_("<b>Sequence Numbers</b>");
-char *s = N_("<i>/media/externaldrive/Photos</i>");
-char *s = N_("<i>Example: /home/user/Pictures</i>");
-char *s = N_("<i>Example:</i>");
-char *s = N_("<i>New:</i>");
-char *s = N_("<i>Original:</i>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Backup</span> ");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Device Options</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Devices</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Error Handling</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Job Codes</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Miscellaneous</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Photo Download Folders</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Photo Rename</span> ");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Rename Options</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Video Download Folders</span>");
-char *s = N_("<span weight=\"bold\" size=\"x-large\">Video Rename</span> ");
-char *s = N_("Add unique identifier");
-char *s = N_("Automatically detect Portable Storage Devices");
-char *s = N_("Automatically detect backup devices");
-char *s = N_("Automatically detect devices");
-char *s = N_("Backup");
-char *s = N_("Backup photos and videos when downloading");
-char *s = N_("Choose the download folder. Subfolders for the downloaded photos will be automatically created in this folder using the structure specified below.");
-char *s = N_("Choose the download folder. Subfolders for the downloaded videos will be automatically created in this folder using the structure specified below.");
-char *s = N_("Day start:");
-char *s = N_("Delete photos and videos from device upon download completion");
-char *s = N_("Device Options");
-char *s = N_("Devices");
-char *s = N_("Devices are from where to download photos and videos, such as cameras, memory cards or Portable Storage Devices.\n"
- "\n"
- "You can download from multiple devices simultaneously, or you can specify a location on your hard drive.\n"
- "\n"
- "<i>Downloading directly from cameras is currently an experimental feature. If downloading directly from your camera works poorly or not at all, try setting it to PTP mode. If that is not possible, a card reader must be used.</i>");
-char *s = N_("Download folder:");
-char *s = N_("Downloads today:");
-char *s = N_("Error Handling");
-char *s = N_("Exit program even if download had warnings or errors");
-char *s = N_("Exit program when download completes");
-char *s = N_("Generate thumbnails (slower)");
-char *s = N_("If you disable automatic detection, choose the exact backup locations.");
-char *s = N_("If you disable automatic detection, choose the exact location of the photos and videos.");
-char *s = N_("If you enable automatic detection of Portable Storage Devices, the entire device will be scanned. On large devices, this could take some time.\n"
- "\n"
- "When this option is enabled, and a potential device is detected, you will be prompted to determine if it should be scanned or not.");
-char *s = N_("Ignored Paths");
-char *s = N_("Job Codes");
-char *s = N_("Location:");
-char *s = N_("Miscellaneous");
-char *s = N_("Overwrite");
-char *s = N_("Performance");
-char *s = N_("Photo Folders");
-char *s = N_("Photo Rename");
-char *s = N_("Photo backup folder name:");
-char *s = N_("Photo backup location:");
-char *s = N_("Preferences: Rapid Photo Downloader");
-/* 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' */
-char *s = N_("R_emove All");
-/* 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' */
-char *s = N_("Re_move All");
-char *s = N_("Remembered Paths");
-char *s = N_("Remembered paths are those that you have chosen to always scan or ignore when automatic detection of Portable Storage Devices is enabled.");
-char *s = N_("Rename Options");
-char *s = N_("Skip");
-char *s = N_("Skip download");
-char *s = N_("Sorry, video downloading functionality disabled. To download videos, please install the <i>hachoir metadata</i> and <i>kaa metadata</i> packages for python.");
-char *s = N_("Specify any paths you want ignored when scanning devices for photos or videos. Any path ending with the values below will not be scanned.");
-char *s = N_("Specify the folder in which backups are stored on the device. \n"
- "\n"
- "<i>Note: this will also be used to determine whether or not the device is used for backups. For each device you wish to use for backing up to, create a folder in it with one of these names.</i>");
-char *s = N_("Specify the time in 24 hour format at which the <i>Downloads today</i> sequence number should be reset.");
-char *s = N_("Specify whether photo, video and folder names should have any characters removed that are not allowed by other operating systems.");
-char *s = N_("Start downloading at program startup");
-char *s = N_("Start downloading upon device insertion");
-char *s = N_("Stored number:");
-char *s = N_("Strip incompatible characters");
-char *s = N_("Synchronize RAW + JPEG sequence numbers");
-char *s = N_("Unmount (\"eject\") device upon download completion");
-char *s = N_("Use _python-style regular expressions");
-char *s = N_("Video Folders");
-char *s = N_("Video Rename");
-char *s = N_("Video backup folder name:");
-char *s = N_("Video backup location:");
-char *s = N_("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.");
-char *s = N_("When backing up, choose whether to overwrite a file on the backup device that has the same name, or skip backing it up.");
-char *s = N_("You can have your photos and videos backed up to multiple locations as they are downloaded, e.g. external hard drives.");
-char *s = N_("_Add...");
diff --git a/rapid/glade3/rapid.ui.h b/rapid/glade3/rapid.ui.h
deleted file mode 100644
index 7d5efa3..0000000
--- a/rapid/glade3/rapid.ui.h
+++ /dev/null
@@ -1,32 +0,0 @@
-char *s = N_("About...");
-char *s = N_("Check All");
-char *s = N_("Check All Photos");
-char *s = N_("Check All Videos");
-char *s = N_("Download");
-char *s = N_("Download / Pause");
-char *s = N_("Get Help Online...");
-char *s = N_("Help");
-char *s = N_("Make a Donation...");
-char *s = N_("Next File");
-char *s = N_("Preferences");
-char *s = N_("Previous File");
-char *s = N_("Quit");
-char *s = N_("Rapid Photo Downloader");
-char *s = N_("Refresh");
-char *s = N_("Report a Problem...");
-char *s = N_("Select All Wit_h Job Code");
-char *s = N_("Select All Without _Job Code");
-char *s = N_("Translate this Application...");
-char *s = N_("Uncheck All");
-char *s = N_("_Check All");
-char *s = N_("_Clear Completed Downloads");
-char *s = N_("_Download");
-char *s = N_("_Error Log");
-char *s = N_("_File");
-char *s = N_("_Help");
-char *s = N_("_Include in download");
-char *s = N_("_Make a Donation...");
-char *s = N_("_Select");
-char *s = N_("_Translate this Application...");
-char *s = N_("_Uncheck All");
-char *s = N_("_View");
diff --git a/rapid/preferencesdialog.py b/rapid/preferencesdialog.py
index ea66c8d..1509630 100644
--- a/rapid/preferencesdialog.py
+++ b/rapid/preferencesdialog.py
@@ -435,8 +435,8 @@ class RemoveAllJobCodeDialog(QuestionDialog):
class RemoveAllRemeberedDevicesDialog(QuestionDialog):
def __init__(self, parent_window, post_choice_callback):
QuestionDialog.__init__(self, parent_window,
- _('Remove all Remembered Devices?'),
- _('Should all remembered devices be removed?'),
+ _('Remove all Remembered Paths?'),
+ _('Should all remembered paths be removed?'),
post_choice_callback)
class RemoveAllIgnoredPathsDialog(QuestionDialog):
@@ -972,6 +972,10 @@ class PreferencesDialog():
self.prefs.backup_location = widget.get_current_folder()
self.update_backup_example()
+ def on_backup_video_folder_filechooser_button_selection_changed(self, widget):
+ self.prefs.backup_video_location = widget.get_current_folder()
+ self.update_backup_example()
+
def on_device_location_filechooser_button_selection_changed(self, widget):
self.prefs.device_location = widget.get_current_folder()
@@ -1337,8 +1341,12 @@ class PreferencesDialog():
def _setup_backup_tab(self):
+ """
+ Setup and configure backup tab
+ """
+ #Manual backup location for photos file chooser
self.backup_folder_filechooser_button = gtk.FileChooserButton(
- _("Select a folder in which to backup %(file_types)s") % {'file_types':self.file_types})
+ _("Select a folder in which to backup photos"))
self.backup_folder_filechooser_button.set_current_folder(
self.prefs.backup_location)
self.backup_folder_filechooser_button.set_action(
@@ -1348,6 +1356,20 @@ class PreferencesDialog():
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"))
+ self.backup_video_folder_filechooser_button.set_current_folder(
+ 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.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)
+ self.backup_video_folder_filechooser_button.show()
+
self.backup_identifier_entry.set_text(self.prefs.backup_identifier)
self.video_backup_identifier_entry.set_text(self.prefs.video_backup_identifier)
@@ -1361,6 +1383,14 @@ 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
diff --git a/rapid/prefsrapid.py b/rapid/prefsrapid.py
index 6fa5f74..080e6f9 100644
--- a/rapid/prefsrapid.py
+++ b/rapid/prefsrapid.py
@@ -101,6 +101,7 @@ class RapidPreferences(prefs.Preferences):
"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('~')),
"strip_characters": prefs.Value(prefs.BOOL, True),
"auto_download_at_startup": prefs.Value(prefs.BOOL, False),
"auto_download_upon_device_insertion": prefs.Value(prefs.BOOL, False),
diff --git a/rapid/problemnotification.py b/rapid/problemnotification.py
index 6238847..95e31a4 100755
--- a/rapid/problemnotification.py
+++ b/rapid/problemnotification.py
@@ -99,7 +99,7 @@ problem_definitions = {
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 at on %(image1_date)s at %(image1_time)s, and %(image2)s on %(image2_date)s at %(image2_time)s."), False),
+ 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),
}
diff --git a/rapid/rapid.py b/rapid/rapid.py
index 237c843..db5bdc6 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -68,6 +68,7 @@ import scan as scan_process
import copyfiles
import subfolderfile
import backupfile
+from backupfile import PHOTO_BACKUP, VIDEO_BACKUP, PHOTO_VIDEO_BACKUP
import errorlog
@@ -890,9 +891,10 @@ class ThumbnailDisplay(gtk.IconView):
def generate_thumbnails(self, scan_pid):
"""Initiate thumbnail generation for files scanned in one process
"""
- rpd_files = [self.rpd_files[unique_id] for unique_id in self.process_index[scan_pid]]
- thumbnail_pid = self.thumbnail_manager.add_task((scan_pid, rpd_files))
- self.generating_thumbnails[scan_pid] = thumbnail_pid
+ if scan_pid in self.process_index:
+ rpd_files = [self.rpd_files[unique_id] for unique_id in self.process_index[scan_pid]]
+ thumbnail_pid = self.thumbnail_manager.add_task((scan_pid, rpd_files))
+ self.generating_thumbnails[scan_pid] = thumbnail_pid
def _set_thumbnail(self, unique_id, icon):
treerowref = self.treerow_index[unique_id]
@@ -991,7 +993,7 @@ class ThumbnailDisplay(gtk.IconView):
self.rapid_app.update_preview_image(unique_id, preview_image)
# user can turn off option for thumbnail generation after a scan
- if unique_id not in self.thumbnails:
+ if unique_id not in self.thumbnails and preview_small is not None:
self._set_thumbnail(unique_id, preview_small.get_image())
@@ -1229,6 +1231,7 @@ class BackupFilesManager(TaskManager):
terminate_queue, run_event):
path = task[0]
name = task[1]
+ backup_type = task[2]
backup_files = backupfile.BackupFiles(path, name, self.batch_size,
task_process_conn, terminate_queue,
run_event)
@@ -1236,22 +1239,36 @@ class BackupFilesManager(TaskManager):
self._processes.append((backup_files, terminate_queue, run_event,
task_results_conn))
- self.backup_devices_by_path[path] = (task_results_conn, backup_files.pid)
+ self.backup_devices_by_path[path] = (task_results_conn, backup_files.pid,
+ backup_type)
return backup_files.pid
def backup_file(self, move_succeeded, rpd_file, path_suffix,
backup_duplicate_overwrite):
+
+ if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
+ logger.debug("Backing up photo %s", rpd_file.download_name)
+ else:
+ logger.debug("Backing up video %s", rpd_file.download_name)
+
for path in self.backup_devices_by_path:
- task_results_conn = self.backup_devices_by_path[path][0]
- task_results_conn.send((move_succeeded, rpd_file, path_suffix,
+ backup_type = self.backup_devices_by_path[path][2]
+ if ((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)):
+ 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))
+ else:
+ logger.debug("Not backing up to %s", path)
- def add_device(self, path, name):
+ def add_device(self, path, name, backup_type):
"""
Convenience function to setup adding a backup device
"""
- return self.add_task((path, name))
+ return self.add_task((path, name, backup_type))
def remove_device(self, path):
pid = self.backup_devices_by_path[path][1]
@@ -1643,10 +1660,10 @@ class RapidApp(dbus.service.Object):
def _backup_device_name(self, path):
- if self.backup_devices[path] is None:
+ if self.backup_devices[path][0] is None:
name = path
else:
- name = self.backup_devices[path].get_name()
+ name = self.backup_devices[path][0].get_name()
return name
def start_device_scan(self, device):
@@ -1699,9 +1716,9 @@ class RapidApp(dbus.service.Object):
logger.info("%s ignored", mount.get_name())
else:
logger.info("Detected %s", mount.get_name())
- is_backup_mount = self.check_if_backup_mount(path)
+ is_backup_mount, backup_file_type = self.check_if_backup_mount(path)
if is_backup_mount:
- self.backup_devices[path] = mount
+ self.backup_devices[path] = (mount, backup_file_type)
elif (self.prefs.device_autodetection and
(dv.is_DCIM_device(path) or
self.search_for_PSD())):
@@ -1723,14 +1740,8 @@ class RapidApp(dbus.service.Object):
if self.prefs.backup_images:
if not self.prefs.backup_device_autodetection:
- # user manually specified backup location
- # will backup to this path, but don't need any volume info
- # associated with it
- self.backup_devices[self.prefs.backup_location] = None
-
- for path in self.backup_devices:
- name = self._backup_device_name(path)
- self.backup_manager.add_device(path, name)
+ self._setup_manual_backup()
+ self._add_backup_devices()
self.update_no_backup_devices()
@@ -1760,6 +1771,38 @@ class RapidApp(dbus.service.Object):
if not mounts:
self.set_download_action_sensitivity()
+ def _setup_manual_backup(self):
+ """
+ Setup backup devices that the user has manually specified.
+ Depending on the folder the user has chosen, the paths for photo and
+ video backup will either be the same or they will differ.
+ """
+ # user manually specified backup locations
+ # will backup to these paths, but don't need any volume info
+ # associated with them
+ self.backup_devices[self.prefs.backup_location] = (None, PHOTO_BACKUP)
+ if DOWNLOAD_VIDEO:
+ if self.prefs.backup_location <> self.prefs.backup_video_location:
+ self.backup_devices[self.prefs.backup_video_location] = (None, VIDEO_BACKUP)
+ logger.info("Backing up photos to %s", self.prefs.backup_location)
+ logger.info("Backing up videos to %s", self.prefs.backup_video_location)
+ else:
+ # videos and photos are being backed up to the same location
+ self.backup_devices[self.prefs.backup_location] = (None, PHOTO_VIDEO_BACKUP)
+ logger.info("Backing up photos and videos to %s", self.prefs.backup_location)
+ else:
+ logger.info("Backing up photos to %s", self.prefs.backup_location)
+
+ def _add_backup_devices(self):
+ """
+ Add each backup devices / path to backup manager
+ """
+ for path in self.backup_devices:
+ name = self._backup_device_name(path)
+ backup_type = self.backup_devices[path][1]
+ self.backup_manager.add_device(path, name, backup_type)
+
+
def get_use_device(self, device):
""" Prompt user whether or not to download from this device """
@@ -1797,26 +1840,67 @@ class RapidApp(dbus.service.Object):
"""
return self.prefs.device_autodetection_psd and self.prefs.device_autodetection
- def check_if_backup_mount(self, path):
+ def check_if_backup_mount(self, path):
"""
- Checks to see if backups are enabled and path represents a valid backup location
+ Checks to see if backups are enabled and path represents a valid backup
+ location. It must be writeable.
Checks against user preferences.
+
+ Returns a tuple:
+ (True, <backup-type> (one of PHOTO_VIDEO_BACKUP, PHOTO_BACKUP, or VIDEO_BACKUP)) or
+ (False, None)
"""
- identifiers = [self.prefs.backup_identifier]
- if DOWNLOAD_VIDEO:
- identifiers.append(self.prefs.video_backup_identifier)
if self.prefs.backup_images:
if self.prefs.backup_device_autodetection:
- if dv.is_backup_media(path, identifiers):
- return True
+ # Determine if the auto-detected backup device is
+ # to be used to backup only photos, or videos, or both.
+ # Use the presence of a corresponding directory to
+ # determine this.
+ # The directory must be writable.
+ photo_path = os.path.join(path, self.prefs.backup_identifier)
+ p_backup = os.path.isdir(photo_path) and os.access(photo_path, os.W_OK)
+ if DOWNLOAD_VIDEO:
+ video_path = os.path.join(path, self.prefs.video_backup_identifier)
+ v_backup = os.path.isdir(video_path) and os.access(video_path, os.W_OK)
+ else:
+ v_backup = False
+ if p_backup and v_backup:
+ logger.info("Photos and videos will be backed up to %s", path)
+ return (True, PHOTO_VIDEO_BACKUP)
+ elif p_backup:
+ logger.info("Photos will be backed up to %s", path)
+ return (True, PHOTO_BACKUP)
+ elif v_backup:
+ logger.info("Videos will be backed up to %s", path)
+ return (True, VIDEO_BACKUP)
elif path == self.prefs.backup_location:
+ # user manually specified the path
+ if os.access(self.prefs.backup_location, os.W_OK):
+ return (True, PHOTO_BACKUP)
+ elif path == self.prefs.backup_video_location:
# user manually specified the path
- return True
- return False
+ if os.access(self.prefs.backup_video_location, os.W_OK):
+ return (True, VIDEO_BACKUP)
+ return (False, None)
def update_no_backup_devices(self):
- self.download_tracker.set_no_backup_devices(len(self.backup_devices))
+ self.no_photo_backup_devices = 0
+ self.no_video_backup_devices = 0
+ for path, value in self.backup_devices.iteritems():
+ backup_type = value[1]
+ if backup_type == PHOTO_BACKUP:
+ self.no_photo_backup_devices += 1
+ elif backup_type == VIDEO_BACKUP:
+ self.no_video_backup_devices += 1
+ else:
+ #both videos and photos are backed up to this device / path
+ self.no_photo_backup_devices += 1
+ self.no_video_backup_devices += 1
+ logger.info("# photo backup devices: %s; # video backup devices: %s",
+ self.no_photo_backup_devices, self.no_video_backup_devices)
+ self.download_tracker.set_no_backup_devices(self.no_photo_backup_devices,
+ self.no_video_backup_devices)
def refresh_backup_media(self):
"""
@@ -1832,22 +1916,19 @@ class RapidApp(dbus.service.Object):
self.backup_devices = {}
if self.prefs.backup_images:
if not self.prefs.backup_device_autodetection:
- # user manually specified backup location
- # will backup to this path, but don't need any volume info associated with it
- self.backup_devices[self.prefs.backup_location] = None
+ self._setup_manual_backup()
else:
for mount in self.vmonitor.get_mounts():
if not mount.is_shadowed():
path = mount.get_root().get_path()
if path:
- if self.check_if_backup_mount(path):
+ is_backup_mount, backup_file_type = self.check_if_backup_mount(path)
+ if is_backup_mount:
# is a backup volume
if path not in self.backup_devices:
- self.backup_devices[path] = mount
+ self.backup_devices[path] = (mount, backup_file_type)
- for path in self.backup_devices:
- name = self._backup_device_name(path)
- self.backup_manager.add_device(path, name)
+ self._add_backup_devices()
self.update_no_backup_devices()
self.display_free_space()
@@ -1880,13 +1961,13 @@ class RapidApp(dbus.service.Object):
logger.info("Device %(device)s (%(path)s) ignored" % {
'device': mount.get_name(), 'path': path})
else:
- is_backup_mount = self.check_if_backup_mount(path)
+ is_backup_mount, backup_file_type = self.check_if_backup_mount(path)
if is_backup_mount:
if path not in self.backup_devices:
self.backup_devices[path] = mount
name = self._backup_device_name(path)
- self.backup_manager.add_device(path, name)
+ self.backup_manager.add_device(path, name, backup_file_type)
self.update_no_backup_devices()
self.display_free_space()
@@ -2158,25 +2239,40 @@ class RapidApp(dbus.service.Object):
"""
Initiate downloading and renaming of files
"""
-
# Check which file types will be downloaded for this particular process
- if self.files_of_type_present(files, rpdfile.FILE_TYPE_PHOTO):
+ no_photos_to_download = self.files_of_type_present(files,
+ rpdfile.FILE_TYPE_PHOTO,
+ return_file_count=True)
+ if no_photos_to_download:
photo_download_folder = self.prefs.download_folder
else:
photo_download_folder = None
- if self.files_of_type_present(files, rpdfile.FILE_TYPE_VIDEO):
- video_download_folder = self.prefs.video_download_folder
+ if DOWNLOAD_VIDEO:
+ no_videos_to_download = self.files_of_type_present(files,
+ rpdfile.FILE_TYPE_VIDEO,
+ return_file_count=True)
+ if no_videos_to_download:
+ video_download_folder = self.prefs.video_download_folder
+ else:
+ video_download_folder = None
else:
video_download_folder = None
+ no_videos_to_download = 0
- download_size = self.size_files_to_be_downloaded(files)
+ photo_download_size, video_download_size = self.size_files_to_be_downloaded(files)
self.download_tracker.init_stats(scan_pid=scan_pid,
- bytes=download_size,
- no_files=len(files))
+ photo_size_in_bytes=photo_download_size,
+ video_size_in_bytes=video_download_size,
+ no_photos_to_download=no_photos_to_download,
+ no_videos_to_download=no_videos_to_download)
+
+
+ download_size = photo_download_size + video_download_size
if self.prefs.backup_images:
- download_size = download_size * (len(self.backup_devices) + 1)
+ download_size = download_size + ((self.no_photo_backup_devices * photo_download_size) +
+ (self.no_video_backup_devices * video_download_size))
self.time_remaining.set(scan_pid, download_size)
self.time_check.set_download_mark()
@@ -2276,10 +2372,6 @@ class RapidApp(dbus.service.Object):
if rpd_file.status == config.STATUS_DOWNLOADED_WITH_WARNING:
self.log_error(config.WARNING, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
- self.error_title = ''
- self.error_msg = ''
- self.error_extra_detail = ''
-
if self.prefs.backup_images and len(self.backup_devices):
if self.prefs.backup_device_autodetection:
@@ -2296,8 +2388,20 @@ class RapidApp(dbus.service.Object):
else:
self.file_download_finished(move_succeeded, rpd_file)
-
+
+ def multiple_backup_devices(self, file_type):
+ """Returns true if more than one backup device is being used for that
+ file type
+ """
+ return ((file_type == rpdfile.FILE_TYPE_PHOTO and
+ self.no_photo_backup_devices > 1) or
+ (file_type == rpdfile.FILE_TYPE_VIDEO and
+ self.no_video_backup_devices > 1))
+
def backup_results(self, source, condition):
+ """
+ Handle results sent from backup processes
+ """
connection = self.backup_manager.get_pipe(source)
conn_type, msg_data = connection.recv()
if conn_type == rpdmp.CONN_PARTIAL:
@@ -2315,8 +2419,20 @@ class RapidApp(dbus.service.Object):
elif msg_type == rpdmp.MSG_FILE:
backup_succeeded, 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
+ # 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):
+ 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 self.download_tracker.all_files_backed_up(rpd_file.unique_id,
+ rpd_file.file_type):
self.file_download_finished(backup_succeeded, rpd_file)
return True
else:
@@ -2324,10 +2440,13 @@ class RapidApp(dbus.service.Object):
def file_download_finished(self, succeeded, rpd_file):
+ """
+ Called when a file has been downloaded i.e. copied, renamed, and backed up
+ """
scan_pid = rpd_file.scan_pid
unique_id = rpd_file.unique_id
# Update error log window if neccessary
- if not succeeded:
+ if not succeeded and not self.multiple_backup_devices(rpd_file.file_type):
self.log_error(config.SERIOUS_ERROR, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
elif self.prefs.auto_delete:
@@ -2340,7 +2459,7 @@ class RapidApp(dbus.service.Object):
rpd_file.file_type,
rpd_file.status)
- completed, files_remaining = self._update_file_download_device_progress(scan_pid, unique_id)
+ completed, files_remaining = self._update_file_download_device_progress(scan_pid, unique_id, rpd_file.file_type)
if self.download_is_occurring():
self.update_time_remaining()
@@ -2538,7 +2657,7 @@ class RapidApp(dbus.service.Object):
self.display_summary_notification = False # don't show it again unless needed
- def _update_file_download_device_progress(self, scan_pid, unique_id):
+ def _update_file_download_device_progress(self, scan_pid, unique_id, file_type):
"""
Increments the progress bar for an individual device
@@ -2552,7 +2671,7 @@ class RapidApp(dbus.service.Object):
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)):
- completed = self.download_tracker.all_files_backed_up(unique_id)
+ completed = self.download_tracker.all_files_backed_up(unique_id, file_type)
if completed:
files_remaining = self.thumbnails.get_no_files_remaining(scan_pid)
@@ -2649,6 +2768,11 @@ class RapidApp(dbus.service.Object):
# related values in the preferences dialog window
self.refresh_downloads_today = False
+ # these values are used to track the number of backup devices /
+ # locations for each file type
+ self.no_photo_backup_devices = 0
+ self.no_video_backup_devices = 0
+
self.downloads_today_tracker = self.prefs.get_downloads_today_tracker()
downloads_today = self.downloads_today_tracker.get_and_maybe_reset_downloads_today()
@@ -2669,6 +2793,7 @@ class RapidApp(dbus.service.Object):
self.uses_session_sequece_no_value = Value(c_bool, self.prefs.any_pref_uses_session_sequece_no())
self.uses_sequence_letter_value = Value(c_bool, self.prefs.any_pref_uses_sequence_letter_value())
+ self.check_prefs_upgrade(__version__)
self.prefs.program_version = __version__
def _check_for_sequence_value_use(self):
@@ -2676,6 +2801,33 @@ class RapidApp(dbus.service.Object):
self.uses_session_sequece_no_value.value = self.prefs.any_pref_uses_session_sequece_no()
self.uses_sequence_letter_value.value = self.prefs.any_pref_uses_sequence_letter_value()
+ def check_prefs_upgrade(self, running_version):
+ """
+ Checks if the running version of the program is different from the
+ version recorded in the preferences.
+
+ If the version is different, the preferences are checked to see
+ whether they should be upgraded or not.
+ """
+ previous_version = self.prefs.program_version
+ if len(previous_version) > 0:
+ # the program has been run previously for this user
+
+ pv = utilities.pythonify_version(previous_version)
+ rv = utilities.pythonify_version(running_version)
+
+ if pv <> rv:
+ # 0.4.1 and below had only one manual backup location
+ # 0.4.2 introduced a distinct video back up location that can be manually set
+ # Therefore must duplicate the previous photo & video manual backup location into the
+ # new video field, unless it has already been changed already.
+
+ if pv < utilities.pythonify_version('0.4.2'):
+ if self.prefs.backup_video_location == os.path.expanduser('~'):
+ self.prefs.backup_video_location = self.prefs.backup_location
+ logger.info("Migrated manual backup location preference to videos: %s",
+ self.prefs.backup_video_location)
+
def on_preference_changed(self, key, value):
"""
Called when user changes the program's preferences
@@ -2691,7 +2843,9 @@ class RapidApp(dbus.service.Object):
if not self.preferences_dialog_displayed:
self.post_preference_change()
- elif key in ['backup_images', 'backup_device_autodetection', 'backup_location', 'backup_identifier', 'video_backup_identifier']:
+ elif key in ['backup_images', 'backup_device_autodetection',
+ 'backup_location', 'backup_video_location',
+ 'backup_identifier', 'video_backup_identifier']:
self.rerun_setup_available_backup_media = True
if not self.preferences_dialog_displayed:
self.post_preference_change()
@@ -2954,7 +3108,7 @@ class RapidApp(dbus.service.Object):
elif i == (v - 1) :
prefix = " " + _("and") + " "
i += 1
- message = "%s%s'%s'" % (message, prefix, self.backup_devices[b].get_name())
+ message = "%s%s'%s'" % (message, prefix, self.backup_devices[b][0].get_name())
if v > 1:
message = _("Using backup devices") + " %s" % message
@@ -3025,8 +3179,18 @@ class RapidApp(dbus.service.Object):
if self.prefs.backup_images:
if not self.prefs.backup_device_autodetection:
- # user manually specified backup location
- msg2 = _('Backing up to %(path)s') % {'path':self.prefs.backup_location}
+ if self.prefs.backup_location == self.prefs.backup_video_location:
+ if DOWNLOAD_VIDEO:
+ # user manually specified the same location for photos and video backups
+ msg2 = _('Backing up photos and videos to %(path)s') % {'path':self.prefs.backup_location}
+ else:
+ # user manually specified backup location
+ msg2 = _('Backing up to %(path)s') % {'path':self.prefs.backup_location}
+ else:
+ # user manually specified different locations for photo and video backups
+ msg2 = _('Backing up photos to %(path)s and videos to %(path2)s') % {
+ 'path':self.prefs.backup_location,
+ 'path2': self.prefs.backup_video_location}
else:
msg2 = self.display_backup_mounts()
@@ -3070,25 +3234,39 @@ class RapidApp(dbus.service.Object):
# Utility functions
# # #
- def files_of_type_present(self, files, file_type):
+ def files_of_type_present(self, files, file_type, return_file_count=False):
"""
Returns true if there is at least one instance of the file_type
in the list of files to be copied
+
+ If return_file_count is True, then the number of files of that type
+ will be counted and returned instead of True or False
"""
+ i = 0
for rpd_file in files:
if rpd_file.file_type == file_type:
- return True
- return False
-
+ if return_file_count:
+ i += 1
+ else:
+ return True
+ if not return_file_count:
+ return False
+ else:
+ return i
+
def size_files_to_be_downloaded(self, files):
"""
- Returns the total size of the files to be downloaded in bytes
+ Returns the total sizes of the photos and videos to be downloaded in bytes
"""
- size = 0
- for i in range(len(files)):
- size += files[i].size
+ photo_size = 0
+ video_size = 0
+ for rpd_file in files:
+ if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
+ photo_size += rpd_file.size
+ else:
+ video_size += rpd_file.size
- return size
+ return (photo_size, video_size)
def check_download_folder_validity(self, files_by_scan_pid):
"""
diff --git a/rapid/subfolderfile.py b/rapid/subfolderfile.py
index 4b6cca1..a80bc84 100644
--- a/rapid/subfolderfile.py
+++ b/rapid/subfolderfile.py
@@ -317,9 +317,9 @@ class SubfolderFile(multiprocessing.Process):
a = rpd_file.metadata.aperture()
if a == '0.0':
fl = rpd_file.metadata["Exif.Photo.FocalLength"].value
- logger.info("Samyang lens - adjusting focal length and aperture... ")
+ logger.info("Adjusting focal length and aperture for %s", rpd_file.full_file_name)
#~ try:
- rpd_file.metadata["Exif.Photo.FocalLength"] = fractions.Fraction(14,1)
+ rpd_file.metadata["Exif.Photo.FocalLength"] = fractions.Fraction(self.focal_length,1)
rpd_file.metadata["Exif.Photo.FNumber"] = fractions.Fraction(8,1)
#~ rpd_file.metadata.write(preserve_timestamps=True)
#~ logger.info("...wrote new value")
diff --git a/rapid/thumbnail.py b/rapid/thumbnail.py
index 7a41e54..c987018 100644
--- a/rapid/thumbnail.py
+++ b/rapid/thumbnail.py
@@ -319,7 +319,7 @@ class GetPreviewImage(multiprocessing.Process):
def get_stock_image(self, file_type):
"""
- Get stock image for file type scaled to the current size of the
+ Get stock image for file type scaled to the current size of the screen
"""
if file_type == rpdfile.FILE_TYPE_PHOTO:
if self.stock_photo_thumbnail_image is None:
@@ -337,7 +337,6 @@ class GetPreviewImage(multiprocessing.Process):
if full_size_preview is None:
full_size_preview = self.get_stock_image(file_type)
self.results_pipe.send((unique_id, full_size_preview, reduced_size_preview))
-
class GenerateThumbnails(multiprocessing.Process):