summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog28
-rw-r--r--rapid/config.py2
-rw-r--r--rapid/glade3/rapid.glade4
-rwxr-xr-xrapid/rapid.py208
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