summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog37
-rw-r--r--rapid/config.py2
-rw-r--r--rapid/filemodify.py6
-rwxr-xr-xrapid/rapid.py157
-rw-r--r--rapid/rpdfile.py25
-rwxr-xr-xrapid/scan.py22
6 files changed, 204 insertions, 45 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog
index e2bd42f..6a28146 100644
--- a/rapid/ChangeLog
+++ b/rapid/ChangeLog
@@ -1,3 +1,40 @@
+Version 0.4.5
+-------------
+
+2012-06-24
+
+Updated Dutch, Estonian, German, Italian, Norwegian and Polish translations.
+
+Updated man page.
+
+
+Version 0.4.5 Beta 1
+--------------------
+
+2012-06-17
+
+To increase performance, thumbnails are now no longer displayed until all
+devices have finished being scanned. To indicate the scan is occurring, the
+progress bar now pulses and it displays a running total of the number of photos
+and videos found. If scanning a very large number of files from a fast device,
+the progress bar may pause. If this happens, just wait for the scan to complete.
+
+Fixed bug #1014203: Very poor program performance after download device changed.
+The program now displays the results of scanning files much quicker if the
+program's download device preferences are changed and a scan begins of a new
+device.
+
+You can now specify via the command line whether you would like to automatically
+detect devices from which to download, or manually specify the path of the
+device. If specified, the option will overwrite the existing program
+preferences.
+
+Added extra information to debugging output.
+
+Fixed bug #1014219: File Modify process crashes if program exits during
+download.
+
+
Version 0.4.4
-------------
diff --git a/rapid/config.py b/rapid/config.py
index 64228e8..d44c9bb 100644
--- a/rapid/config.py
+++ b/rapid/config.py
@@ -16,7 +16,7 @@
### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
### USA
-version = '0.4.4'
+version = '0.4.5'
GCONF_KEY="/apps/rapid-photo-downloader"
diff --git a/rapid/filemodify.py b/rapid/filemodify.py
index 8e5d8a3..98921ae 100644
--- a/rapid/filemodify.py
+++ b/rapid/filemodify.py
@@ -72,7 +72,11 @@ class FileModify(multiprocessing.Process):
while not copy_finished:
logger.debug("Finished %s. Getting next task.", download_count)
- rpd_file, download_count, temp_full_file_name, thumbnail_icon, thumbnail, copy_finished = self.results_pipe.recv()
+ data = self.results_pipe.recv()
+ if len(data) > 2:
+ rpd_file, download_count, temp_full_file_name, thumbnail_icon, thumbnail, copy_finished = data
+ else:
+ rpd_file, copy_finished = data
if rpd_file is None:
# this is a termination signal
logger.info("Terminating file modify via pipe")
diff --git a/rapid/rapid.py b/rapid/rapid.py
index a060db3..fab5796 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -32,6 +32,8 @@ from optparse import OptionParser
import gtk
import gtk.gdk as gdk
+from gobject.constants import G_MAXINT
+
import webbrowser
import sys, time, types, os, datetime
@@ -135,9 +137,9 @@ class DeviceCollection(gtk.TreeView):
self.parent_app = parent_app
# device icon & name, size of images on the device (human readable),
# copy progress (%), copy text, eject button (None if irrelevant),
- # process id
+ # process id, pulse
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, float, str,
- gtk.gdk.Pixbuf, int)
+ gtk.gdk.Pixbuf, int, int)
self.map_process_to_row = {}
self.devices_by_scan_pid = {}
@@ -176,7 +178,8 @@ class DeviceCollection(gtk.TreeView):
column2 = gtk.TreeViewColumn(_("Download Progress"),
gtk.CellRendererProgress(),
value=3,
- text=4)
+ text=4,
+ pulse=7)
self.append_column(column2)
self.show_all()
@@ -211,7 +214,8 @@ class DeviceCollection(gtk.TreeView):
progress,
progress_bar_text,
eject,
- process_id))
+ process_id,
+ -1))
self._set_process_map(process_id, iter)
@@ -275,7 +279,7 @@ class DeviceCollection(gtk.TreeView):
else:
return None
- def update_progress(self, scan_pid, percent_complete, progress_bar_text, bytes_downloaded):
+ def update_progress(self, scan_pid, percent_complete, progress_bar_text, bytes_downloaded, pulse=None):
iter = self._get_process_map(scan_pid)
if iter:
@@ -283,9 +287,19 @@ class DeviceCollection(gtk.TreeView):
self.liststore.set_value(iter, 3, percent_complete)
if progress_bar_text:
self.liststore.set_value(iter, 4, progress_bar_text)
- if percent_complete or bytes_downloaded:
- pass
- #~ logger.info("Implement update overall progress")
+
+ if pulse is not None:
+ if pulse:
+ # Make the bar pulse
+ self.liststore.set_value(iter, 7, self.liststore.get_value(iter, 7) + 1)
+ else:
+ # Set to finished state
+ self.liststore.set_value(iter, 7, G_MAXINT)
+ else:
+ # Reset to allow fraction to be set
+ self.liststore.set_value(iter, 7, -1)
+
+
def button_clicked(self, widget, event):
"""
@@ -502,21 +516,9 @@ class ThumbnailDisplay(gtk.IconView):
self.DOWNLOAD_STATUS_COL = 7
self.STATUS_ICON_COL = 8
- self.liststore = gtk.ListStore(
- gobject.TYPE_PYOBJECT, # 0 PIL thumbnail
- gobject.TYPE_BOOLEAN, # 1 selected or not
- str, # 2 unique id
- str, # 3 file name
- int, # 4 timestamp for sorting, converted float
- int, # 5 file type i.e. photo or video
- gobject.TYPE_BOOLEAN, # 6 visibility of checkbutton
- int, # 7 status of download
- gtk.gdk.Pixbuf, # 8 status icon
- )
+ self._create_liststore()
self.clear()
- self.set_model(self.liststore)
-
checkbutton = gtk.CellRendererToggle()
checkbutton.set_radio(False)
@@ -546,6 +548,22 @@ class ThumbnailDisplay(gtk.IconView):
self.connect('item-activated', self.on_item_activated)
+ def _create_liststore(self):
+ """
+ Creates the default list store to hold the icons
+ """
+ self.liststore = gtk.ListStore(
+ gobject.TYPE_PYOBJECT, # 0 PIL thumbnail
+ gobject.TYPE_BOOLEAN, # 1 selected or not
+ str, # 2 unique id
+ str, # 3 file name
+ int, # 4 timestamp for sorting, converted float
+ int, # 5 file type i.e. photo or video
+ gobject.TYPE_BOOLEAN, # 6 visibility of checkbutton
+ int, # 7 status of download
+ gtk.gdk.Pixbuf, # 8 status icon
+ )
+
def _setup_icons(self):
# icons to be displayed in status column
@@ -1018,7 +1036,13 @@ class ThumbnailDisplay(gtk.IconView):
have been downloaded.
"""
if scan_pid is None and not keep_downloaded_files:
- self.liststore.clear()
+
+ # Here it is critically important to create a brand new liststore,
+ # because the old one is set to be sorted, which is extremely slow.
+ logger.debug("Creating new thumbnails model")
+ self.set_model(None)
+ self._create_liststore()
+
self.treerow_index = {}
self.process_index = {}
@@ -1036,6 +1060,9 @@ class ThumbnailDisplay(gtk.IconView):
del self.rpd_files[rpd_file.unique_id]
if not keep_downloaded_files or not len(self.process_index[scan_pid]):
del self.process_index[scan_pid]
+
+ def display_thumbnails(self):
+ self.set_model(self.liststore)
class TaskManager:
def __init__(self, results_callback, batch_size):
@@ -1503,7 +1530,8 @@ class RapidApp(dbus.service.Object):
processes.
"""
- def __init__(self, bus, path, name, taskserver=None, focal_length=None):
+ def __init__(self, bus, path, name, taskserver=None, focal_length=None,
+ auto_detect=None, device_location=None):
dbus.service.Object.__init__ (self, bus, path, name)
self.running = False
@@ -1513,7 +1541,7 @@ class RapidApp(dbus.service.Object):
self.focal_length = focal_length
# Setup program preferences, and set callback for when they change
- self._init_prefs()
+ self._init_prefs(auto_detect, device_location)
# Initialize widgets in the main window, and variables that point to them
self._init_widgets()
@@ -1696,12 +1724,14 @@ class RapidApp(dbus.service.Object):
if self.preview_image.unique_id is not None:
self.thumbnails.show_prev_image(self.preview_image.unique_id)
- def set_thumbnail_sort(self):
+ def display_scan_thumbnails(self):
"""
- If all the scans are complete, sets the sort order
+ If all the scans are complete, sets the sort order and displays
+ thumbnails in the icon view
"""
if self.scan_manager.no_tasks == 0:
self.thumbnails.sort_by_timestamp()
+ self.thumbnails.display_thumbnails()
# # #
@@ -1740,7 +1770,7 @@ class RapidApp(dbus.service.Object):
user said no.
"""
l = self.prefs.device_location
- if l in ['/media', os.path.expanduser('~'), '/']:
+ if l in ['/media', '/run', os.path.expanduser('~'), '/']:
logger.info("Prompting whether to download from %s", l)
if l == '/':
#this location is a human readable explanation for /, and is inserted into Downloading from %(location)s
@@ -2403,7 +2433,19 @@ class RapidApp(dbus.service.Object):
if msg_type == rpdmp.MSG_TEMP_DIRS:
scan_pid, photo_temp_dir, video_temp_dir = data
- self.temp_dirs_by_scan_pid[scan_pid] = (photo_temp_dir, video_temp_dir)
+ self.temp_dirs_by_scan_pid[scan_pid] = (photo_temp_dir, video_temp_dir)
+
+ # Report which temporary directories are being used for this
+ # download
+ if photo_temp_dir and video_temp_dir:
+ logger.debug("Using temp dirs %s (photos) & %s (videos)",
+ photo_temp_dir, video_temp_dir)
+ elif photo_temp_dir:
+ logger.debug("Using temp dir %s (photos)",
+ photo_temp_dir)
+ else:
+ logger.debug("Using temp dir %s (videos)",
+ video_temp_dir)
elif msg_type == rpdmp.MSG_BYTES:
scan_pid, total_downloaded, chunk_downloaded = data
self.download_tracker.set_total_bytes_copied(scan_pid,
@@ -2881,8 +2923,17 @@ class RapidApp(dbus.service.Object):
# # #
- def _init_prefs(self):
+ def _init_prefs(self, auto_detect, device_location):
self.prefs = prefsrapid.RapidPreferences()
+
+ # handle device preferences set from the command line
+ # do this before preference changes are handled with notify_add
+ if auto_detect:
+ self.prefs.device_autodetection = True
+ elif device_location:
+ self.prefs.device_location = device_location
+ self.prefs.device_autodetection = False
+
self.prefs.notify_add(self.on_preference_changed)
# flag to indicate whether the user changed some preferences that
@@ -3006,7 +3057,7 @@ class RapidApp(dbus.service.Object):
def post_preference_change(self):
if self.rerun_setup_available_image_and_video_media:
- logger.info("Download device settings preferences were changed.")
+ logger.info("Download device settings preferences were changed")
self.thumbnails.clear_all()
self.setup_devices(on_startup = False, on_preference_change = True, block_auto_start = True)
@@ -3658,12 +3709,19 @@ class RapidApp(dbus.service.Object):
is_photo_dir=True):
valid = False
invalid_dirs.append(self.prefs.download_folder)
+ else:
+ logger.debug("Photo download folder is valid: %s",
+ self.prefs.download_folder)
if need_video_folder:
if not self.is_valid_download_dir(self.prefs.video_download_folder,
is_photo_dir=False):
valid = False
invalid_dirs.append(self.prefs.video_download_folder)
+ else:
+ logger.debug("Video download folder is valid: %s",
+ self.prefs.video_download_folder)
+
return (valid, invalid_dirs)
@@ -3823,7 +3881,7 @@ class RapidApp(dbus.service.Object):
logger.info('Found %s' % results_summary)
logger.info('Files total %s' % size)
self.device_collection.update_device(scan_pid, size)
- self.device_collection.update_progress(scan_pid, 0.0, results_summary, 0)
+ self.device_collection.update_progress(scan_pid, 0.0, results_summary, 0, pulse=False)
self.set_download_action_sensitivity()
if (not self.auto_start_is_on and
@@ -3836,16 +3894,24 @@ class RapidApp(dbus.service.Object):
else:
self.start_download(scan_pid=scan_pid)
- self.set_thumbnail_sort()
+ logger.debug("Turning on display of thumbnails")
+ self.display_scan_thumbnails()
self.download_button.grab_focus()
# signal that no more data is coming, finishing io watch for this pipe
return False
else:
+ # partial results
if len(data) > self.batch_size:
logger.critical("incoming pipe length is unexpectedly long: %s" % len(data))
else:
- for rpd_file in data:
+ size, file_type_counter, scan_pid, rpd_files = data
+ size = format_size_for_user(bytes=size)
+ scanning_progress = file_type_counter.running_file_count()
+ self.device_collection.update_device(scan_pid, size)
+ self.device_collection.update_progress(scan_pid, 0.0, scanning_progress, 0, pulse=True)
+
+ for rpd_file in rpd_files:
self.thumbnails.add_file(rpd_file=rpd_file,
generate_thumbnail = not self.auto_start_is_on)
@@ -3880,7 +3946,9 @@ def start():
parser.add_option("-q", "--quiet", action="store_false", dest="verbose", help=_("only output errors to the command line"))
# image file extensions are recognized RAW files plus TIFF and JPG
parser.add_option("-e", "--extensions", action="store_true", dest="extensions", help=_("list photo and video file extensions the program recognizes and exit"))
- parser.add_option("--focal-length", type=int, dest="focal_length", help="If an aperture value of 0.0 is encountered, the focal length metadata will be set to the number passed, and its aperture metadata to f8")
+ parser.add_option("--focal-length", type=int, dest="focal_length", help="If an aperture value of 0.0 is encountered, the focal length metadata will be set to the number passed, and its aperture metadata to f/8")
+ parser.add_option("-a", "--auto-detect", action="store_true", dest="auto_detect", help=_("automatically detect devices from which to download, overwriting existing program preferences"))
+ parser.add_option("-l", "--device-location", type="string", metavar="PATH", dest="device_location", help=_("manually specify the PATH of the device from which to download, overwriting existing program preferences"))
parser.add_option("--reset-settings", action="store_true", dest="reset", help=_("reset all program settings and preferences and exit"))
(options, args) = parser.parse_args()
@@ -3892,6 +3960,24 @@ def start():
logging_level = logging.ERROR
logger.setLevel(logging_level)
+
+ if options.auto_detect and options.device_location:
+ logger.info(_("Error: specify device auto-detection or manually specify a device's path from which to download, but do not do both."))
+ sys.exit(1)
+
+ if options.auto_detect:
+ auto_detect=True
+ logger.info("Device auto detection set from command line")
+ else:
+ auto_detect=None
+
+ if options.device_location:
+ device_location=options.device_location
+ if device_location[-1]=='/':
+ device_location = device_location[:-1]
+ logger.info("Device location set from command line: %s", device_location)
+ else:
+ device_location=None
if options.extensions:
extensions = ((rpdfile.PHOTO_EXTENSIONS, _("Photos:")), (rpdfile.VIDEO_EXTENSIONS, _("Videos:")))
@@ -3932,7 +4018,8 @@ def start():
bus = dbus.SessionBus ()
request = bus.request_name (config.DBUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS:
- app = RapidApp(bus, '/', config.DBUS_NAME, focal_length=focal_length)
+ app = RapidApp(bus, '/', config.DBUS_NAME, focal_length=focal_length,
+ auto_detect=auto_detect, device_location=device_location)
else:
# this application is already running
print "Rapid Photo Downloader is already running"
diff --git a/rapid/rpdfile.py b/rapid/rpdfile.py
index 99f6aa5..cf38ebc 100644
--- a/rapid/rpdfile.py
+++ b/rapid/rpdfile.py
@@ -67,7 +67,6 @@ else:
FILE_TYPE_PHOTO = 0
FILE_TYPE_VIDEO = 1
-
def file_type(file_extension):
"""
Uses file extentsion to determine the type of file - photo or video.
@@ -102,6 +101,14 @@ class FileTypeCounter:
def add(self, file_type):
self._counter[file_type] = self._counter.setdefault(file_type, 0) + 1
+ def no_videos(self):
+ """Returns the number of videos"""
+ return self._counter.setdefault(FILE_TYPE_VIDEO, 0)
+
+ def no_photos(self):
+ """Returns the number of photos"""
+ return self._counter.setdefault(FILE_TYPE_PHOTO, 0)
+
def file_types_present(self):
"""
returns a string to be displayed to the user that can be used
@@ -109,8 +116,8 @@ class FileTypeCounter:
of each
"""
- no_videos = self._counter.setdefault(FILE_TYPE_VIDEO, 0)
- no_images = self._counter.setdefault(FILE_TYPE_PHOTO, 0)
+ no_videos = self.no_videos()
+ no_images = self.no_photos()
if (no_videos > 0) and (no_images > 0):
v = _('photos and videos')
@@ -135,6 +142,10 @@ class FileTypeCounter:
return i
def summarize_file_count(self):
+ """
+ Summarizes the total number of photos and/or videos that can be
+ downloaded. Displayed after a scan is finished.
+ """
#Number of files, e.g. "433 photos and videos" or "23 videos".
#Displayed in the progress bar at the top of the main application
#window.
@@ -144,6 +155,14 @@ class FileTypeCounter:
'filetypes': file_types_present}
return (file_count_summary, file_types_present)
+ def running_file_count(self):
+ """
+ Displays raw numbers of photos and videos. Displayed as a scan is
+ occurring.
+ """
+ return _("scanning (found %(photos)s photos and %(videos)s videos)...") % ({'photos': self.no_photos(),
+ 'videos': self.no_videos()})
+
class RPDFile:
"""
Base class for photo or video file, with metadata
diff --git a/rapid/scan.py b/rapid/scan.py
index c6ccaf8..637031a 100755
--- a/rapid/scan.py
+++ b/rapid/scan.py
@@ -104,6 +104,7 @@ class Scan(multiprocessing.Process):
self.run_event = run_event
self.batch_size = batch_size
self.counter = 0
+ self.files_scanned = 0
self.files = []
self.file_type_counter = rpdfile.FileTypeCounter()
@@ -139,6 +140,11 @@ class Scan(multiprocessing.Process):
return None
elif file_type == gio.FILE_TYPE_REGULAR:
+
+ self.files_scanned += 1
+ if self.files_scanned % 100 == 0:
+ logger.debug("Scanned %s files", self.files_scanned)
+
base_name, ext = os.path.splitext(name)
ext = ext.lower()[1:]
@@ -174,15 +180,17 @@ class Scan(multiprocessing.Process):
file_type)
self.files.append(scanned_file)
-
+ file_size_sum += size
+
if self.counter == self.batch_size:
# send batch of results
self.results_pipe.send((rpdmp.CONN_PARTIAL,
- self.files))
+ (file_size_sum,
+ self.file_type_counter,
+ self.pid,
+ self.files)))
self.files = []
self.counter = 0
-
- file_size_sum += size
return file_size_sum
@@ -206,7 +214,11 @@ class Scan(multiprocessing.Process):
if size is not None:
if self.counter > 0:
# send any remaining results
- self.results_pipe.send((rpdmp.CONN_PARTIAL, self.files))
+ self.results_pipe.send((rpdmp.CONN_PARTIAL, (size,
+ self.file_type_counter,
+ self.pid,
+ self.files)))
+
self.results_pipe.send((rpdmp.CONN_COMPLETE, (size,
self.file_type_counter, self.pid)))
self.results_pipe.close()