diff options
Diffstat (limited to 'rapid')
-rw-r--r-- | rapid/ChangeLog | 28 | ||||
-rw-r--r-- | rapid/config.py | 2 | ||||
-rw-r--r-- | rapid/glade3/rapid.glade | 4 | ||||
-rwxr-xr-x | rapid/rapid.py | 208 |
4 files changed, 201 insertions, 41 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog index f700e80..0c2d27a 100644 --- a/rapid/ChangeLog +++ b/rapid/ChangeLog @@ -1,3 +1,31 @@ +Version 0.1.3 +------------- + +2010-01-22 + +Fixed bug #509348: When both the backup and "Delete images from image device +upon download completion" options are selected, the program should only delete +an image from the image device if it was both downloaded to the download folder +and backed up. Previously it did not check to ensure it was backed up +correctly too. + +Fixed bug #505492: Program failed to start in environments where the +notification system has problems. + +Fixed bug #508304: User is now prompted to confirm if they really want to +remove all of their Job Codes after clicking on "Remove All" in the preferences +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 +to see if the image location path exists. + +Updated Czech, Dutch, Finnish, French, German, Hungarian, Italian, Polish, +Russian, Serbian, Spanish and Swedish translations + + Version 0.1.2 ------------- diff --git a/rapid/config.py b/rapid/config.py index 615fda3..d9f4b1f 100644 --- a/rapid/config.py +++ b/rapid/config.py @@ -15,7 +15,7 @@ ### along with this program; if not, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -version = '0.1.2' +version = '0.1.3' GCONF_KEY="/apps/rapid-photo-downloader" GLADE_FILE = "glade3/rapid.glade" diff --git a/rapid/glade3/rapid.glade b/rapid/glade3/rapid.glade index 41387c7..f379024 100644 --- a/rapid/glade3/rapid.glade +++ b/rapid/glade3/rapid.glade @@ -1056,13 +1056,13 @@ </packing> </child> <child> - <widget class="GtkButton" id="clear_job_code_button"> + <widget 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="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_clear_job_code_button_clicked"/> + <signal name="clicked" handler="on_remove_all_job_code_button_clicked"/> </widget> <packing> <property name="expand">False</property> diff --git a/rapid/rapid.py b/rapid/rapid.py index 4a137e2..1c04763 100755 --- a/rapid/rapid.py +++ b/rapid/rapid.py @@ -844,7 +844,7 @@ class PreferencesDialog(gnomeglade.Component): # set multiple selections self.job_code_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.clear_job_code_button.set_image(gtk.image_new_from_stock( + self.remove_all_job_code_button.set_image(gtk.image_new_from_stock( gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)) def _setupDeviceTab(self): @@ -1113,11 +1113,16 @@ class PreferencesDialog(gnomeglade.Component): self.updateImageRenameExample() self.updateDownloadFolderExample() - def on_clear_job_code_button_clicked(self, button): - self.job_code_liststore.clear() - self.update_job_codes() - self.updateImageRenameExample() - self.updateDownloadFolderExample() + def on_remove_all_job_code_button_clicked(self, button): + j = RemoveAllJobCodeDialog(self.widget, self.remove_all_job_code) + + def remove_all_job_code(self, dialog, userSelected): + dialog.destroy() + if userSelected: + self.job_code_liststore.clear() + self.update_job_codes() + self.updateImageRenameExample() + self.updateDownloadFolderExample() def on_job_code_edited(self, widget, path, new_text): iter = self.job_code_liststore.get_iter(path) @@ -1415,7 +1420,9 @@ class CopyPhotos(Thread): msg += _("\nPlease check preferences, restart the program, and try again.") logError(config.CRITICAL_ERROR, _("Download cannot proceed"), msg) cmd_line(_("Download cannot proceed")) - cmd_line(msg) + cmd_line(msg) + display_queue.put((self.parentApp.downloadFailed, (self.thread_id, ))) + display_queue.close("rw") return False def scanMedia(): @@ -1495,7 +1502,7 @@ class CopyPhotos(Thread): self.noErrors += 1 - def checkProblemWithImageNameGeneration(newName, image, problem): + def checkProblemWithImageNameGeneration(newName, destination, image, problem): if not newName: # a serious problem - a filename should never be blank! logError(config.SERIOUS_ERROR, @@ -1506,8 +1513,8 @@ class CopyPhotos(Thread): elif problem: logError(config.WARNING, _("Image filename could not be properly generated. Check to ensure there is sufficient image metadata."), - _("Source: %(source)s\nDestination: %(destination)s\nProblem: %(problem)s") % - {'source': image, 'destination': newName, 'problem': problem}) + _("Source: %(source)s\nPartially generated filename: %(newname)s\nDestination: %(destination)s\nProblem: %(problem)s") % + {'source': image, 'destination': destination, 'newname': newName, 'problem': problem}) def imageAlreadyExists(source, destination=None, identifier=None): @@ -1615,7 +1622,7 @@ class CopyPhotos(Thread): if not newName: skipImage = True if not alreadyDownloaded: - checkProblemWithImageNameGeneration(newName, image, problem) + checkProblemWithImageNameGeneration(newName, path, image, problem) else: imageAlreadyExists(image, newFile) newName = newFile = path = subfolder = None @@ -1679,7 +1686,7 @@ class CopyPhotos(Thread): imageMetadata, originalName, self.stripCharacters, subfolder, sequencesPreliminary = False, sequence_to_use = sequence_to_use) - checkProblemWithImageNameGeneration(newName, image, problem) + checkProblemWithImageNameGeneration(newName, path, image, problem) if not newName: # there was a serious error generating the filename doRename = False @@ -1761,13 +1768,14 @@ class CopyPhotos(Thread): (3) image has been backed up already (or at least, a file with the same name already exists) """ + backed_up = False try: for backupDir in self.parentApp.backupVolumes: backupPath = os.path.join(backupDir, subfolder) newBackupFile = os.path.join(backupPath, newName) copyBackup = True if os.path.exists(newBackupFile): - # again, not thread safe + # not thread safe -- it doesn't need to be, because the file names are at this stage going to be unique copyBackup = self.prefs.backup_duplicate_overwrite if self.prefs.indicate_download_error: severity = config.SERIOUS_ERROR @@ -1785,9 +1793,11 @@ class CopyPhotos(Thread): fileToCopy = newFile else: fileToCopy = image - if not os.path.isdir(backupPath): + if os.path.isdir(backupPath): + pathExists = True + else: # recreate folder structure in backup location - # cannot do os.makedirs(backupPath) - it gives bad results when using external drives + # cannot do os.makedirs(backupPath) - it can give bad results when using external drives # we know backupDir exists # all the components of subfolder may not folders = subfolder.split(os.path.sep) @@ -1798,25 +1808,29 @@ class CopyPhotos(Thread): if not os.path.isdir(folderToMake): try: os.mkdir(folderToMake) - except (errno, strerror): + pathExists = True + except (IOError, OSError), (errno, strerror): logError(config.SERIOUS_ERROR, _('Backing up error'), - _("Destination directory could not be created\n%(directory)s\nError: %(errno)s %(strerror)s") - % {'directory': folderToMake, 'errno': errno, 'strerror': strerror}, + _("Destination directory could not be created: %(directory)s\n") % + {'directory': folderToMake, } + + _("Source: %(source)s\nDestination: %(destination)s\n") % + {'source': image, 'destination': newBackupFile} + + _("Error: %(errno)s %(strerror)s") % {'errno': errno, 'strerror': strerror}, + _('The image was not copied.') ) + pathExists = False - shutil.copy2(fileToCopy, newBackupFile) + if pathExists: + shutil.copy2(fileToCopy, newBackupFile) + backed_up = True - except IOError, (errno, strerror): + except (IOError, OSError), (errno, strerror): logError(config.SERIOUS_ERROR, _('Backing up error'), _("Source: %(source)s\nDestination: %(destination)s\nError: %(errno)s %(strerror)s") % {'source': image, 'destination': newBackupFile, 'errno': errno, 'strerror': strerror}, _('The image was not copied.')) - except OSError, (errno, strerror): - logError(config.CRITICAL_ERROR, _('Backing up error'), - _("Source: %(source)s\nDestination: %(destination)s\nError: %(errno)s %(strerror)s") - % {'source': image, 'destination': newBackupFile, 'errno': errno, 'strerror': strerror} - ) + return backed_up def notifyAndUnmount(): if not self.cardMedia.volume: @@ -1875,9 +1889,9 @@ class CopyPhotos(Thread): if not scanMedia(): cmd_line(_("This device has no images to download from.")) + display_queue.put((self.parentApp.downloadFailed, (self.thread_id, ))) display_queue.close("rw") self.running = False - self.lock.release() return elif self.autoStart and need_job_code: if job_code == None: @@ -1952,9 +1966,29 @@ class CopyPhotos(Thread): #don't want to put it in system temp folder, as that is likely #to be on another partition and hence copying files from it #to the download folder will be slow! - tempWorkingDir = tempfile.mkdtemp(prefix='rapid-tmp-', + try: + tempWorkingDir = tempfile.mkdtemp(prefix='rapid-tmp-', dir=baseDownloadDir) - + except OSError, (errno, strerror): + if not self.cardMedia.volume: + image_device = _("Source: %s\n") % self.cardMedia.getPath() + else: + _("Image device: %s\n") % self.cardMedia.volume.get_name() + destination = _("Destination: %s") % baseDownloadDir + logError(config.CRITICAL_ERROR, _('Could not create temporary download directory'), + image_device + destination, + _("Download cannot proceed")) + cmd_line(_("Error:") + " " + _('Could not create temporary download directory')) + cmd_line(image_device + destination) + cmd_line(_("Download cannot proceed")) + display_queue.put((media_collection_treeview.removeCard, (self.thread_id, ))) + display_queue.put((self.parentApp.downloadFailed, (self.thread_id, ))) + display_queue.close("rw") + self.running = False + self.lock.release() + return + + IMAGE_SKIPPED = _("Image skipped") IMAGE_OVERWRITTEN = _("Image overwritten") IMAGE_ALREADY_EXISTS = _("Image already exists") @@ -1993,11 +2027,14 @@ class CopyPhotos(Thread): imageMetadata, subfolder, sequence_to_use) if self.prefs.backup_images: - backupImage(subfolder, newName, imageDownloaded, newFile, image) + backed_up = backupImage(subfolder, newName, imageDownloaded, newFile, image) if imageDownloaded: noImagesDownloaded += 1 - imagesDownloadedSuccessfully.append(image) + if self.prefs.backup_images and backed_up: + imagesDownloadedSuccessfully.append(image) + elif not self.prefs.backup_images: + imagesDownloadedSuccessfully.append(image) else: noImagesSkipped += 1 try: @@ -2355,8 +2392,50 @@ class UseDeviceDialog(gtk.Dialog): cmd_line(_("This device or partition will never be used to download from")) self.postChoiceCB(self, userSelected, permanent_choice, self.path, - self.volume, self.autostart) + self.volume, self.autostart) + +class RemoveAllJobCodeDialog(gtk.Dialog): + def __init__(self, parent_window, postChoiceCB): + 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_YES, gtk.RESPONSE_OK)) + + self.postChoiceCB = postChoiceCB + self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader-about.png')) + + prompt_hbox = gtk.HBox() + + icontheme = gtk.icon_theme_get_default() + icon = icontheme.load_icon('gtk-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_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): + userSelected = response == gtk.RESPONSE_OK + self.postChoiceCB(self, userSelected) + + class JobCodeDialog(gtk.Dialog): """ Dialog prompting for a job code""" @@ -2631,6 +2710,14 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object): self.last_chosen_job_code = None self.prompting_for_job_code = False + #check to see if the download folder exists and is writable + displayPreferences_2 = not self.checkDownloadPathOnStartup() + displayPreferences = displayPreferences or displayPreferences_2 + + if self.prefs.device_autodetection == False: + displayPreferences_2 = not self.checkImageDevicePathOnStartup() + displayPreferences = displayPreferences or displayPreferences_2 + #setup download and backup mediums, initiating scans self.setupAvailableImageAndBackupMedia(onStartup=True, onPreferenceChange=False, doNotAllowAutoStart = displayPreferences) @@ -2669,16 +2756,51 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object): r = ['Date time', 'Image date', 'YYYYMMDD', 'Text', '-', '', 'Date time', 'Image date', 'HHMM', 'Text', '-', '', 'Session number', '1', 'Three digits', 'Text', '-iso', '', 'Metadata', 'ISO', '', 'Text', '-f', '', 'Metadata', 'Aperture', '', 'Text', '-', '', 'Metadata', 'Focal length', '', 'Text', 'mm-', '', 'Metadata', 'Exposure time', '', 'Filename', 'Extension', 'lowercase'] self.prefs.image_rename = r - + + def checkImageDevicePathOnStartup(self): + msg = None + if not os.path.isdir(self.prefs.device_location): + msg = _("Sorry, this image location does not exist:\n%(path)s\n\nPlease resolve the problem, or modify your preferences." % {"path": self.prefs.device_location}) + + if msg: + sys.stderr.write(msg +'\n') + misc.run_dialog(_("Problem with Image Location Folder"), msg, + self, + gtk.MESSAGE_ERROR) + return False + else: + return True + + def checkDownloadPathOnStartup(self): + msg = None + if not os.path.isdir(self.prefs.download_folder): + msg = _("Sorry, the Download Folder does not exist. Please create the folder, or modify your preferences") + else: + #unfortunately 'os.access(self.prefs.download_folder, os.W_OK)' is not reliable + try: + tempWorkingDir = tempfile.mkdtemp(prefix='rapid-tmp-', + dir=self.prefs.download_folder) + except: + msg = _("Sorry, the Download Folder exists but cannot be written to. Please check the folder's permissions, or modify your preferences") + else: + os.rmdir(tempWorkingDir) + + if msg: + sys.stderr.write(msg +'\n') + misc.run_dialog(_("Problem with Download Folder"), msg, + self, + gtk.MESSAGE_ERROR) + return False + else: + return True def checkPreferencesOnStartup(self): prefsOk = rn.checkPreferencesForValidity(self.prefs.image_rename, self.prefs.subfolder) if not prefsOk: - title = PROGRAM_NAME msg = _("There is an error in the program preferences.") msg += " " + _("Some preferences will be reset.") + # do not use cmd_line here, as this is a genuine error sys.stderr.write(msg +'\n') -# misc.run_dialog(title, msg) return prefsOk def needJobCode(self): @@ -2846,12 +2968,16 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object): for cap in caps: capabilities[cap] = True - info = pynotify.get_server_info() - - if info['name'] == 'Notification Daemon': + try: + info = pynotify.get_server_info() + except: + cmd_line(_("Warning: desktop environment notification server is incorrectly configured.")) self.notification_icon_size = 48 else: - self.notification_icon_size = 128 + if info['name'] == 'Notification Daemon': + self.notification_icon_size = 48 + else: + self.notification_icon_size = 128 self.application_icon = gtk.gdk.pixbuf_new_from_file_at_size( paths.share_dir('glade3/rapid-photo-downloader-about.png'), @@ -3300,6 +3426,12 @@ class RapidApp(gnomeglade.GnomeApp, dbus.service.Object): if not (self.downloadStats.noErrors or self.downloadStats.noWarnings): self.quit() + def downloadFailed(self, thread_id): + if workers.noDownloadingWorkers() == 0: + self.download_button_is_download = True + self._set_download_button() + self.setDownloadButtonSensitivity() + def downloadComplete(self): return self.totalDownloadedSoFar == self.totalDownloadSize |