diff options
Diffstat (limited to 'rapid/rapid.py')
-rwxr-xr-x | rapid/rapid.py | 322 |
1 files changed, 250 insertions, 72 deletions
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): """ |