summaryrefslogtreecommitdiff
path: root/rapid/rapid.py
diff options
context:
space:
mode:
Diffstat (limited to 'rapid/rapid.py')
-rwxr-xr-xrapid/rapid.py1771
1 files changed, 902 insertions, 869 deletions
diff --git a/rapid/rapid.py b/rapid/rapid.py
index fab5796..7de18c2 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -18,6 +18,7 @@
### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
### USA
+use_pynotify = True
import tempfile
@@ -39,7 +40,9 @@ import webbrowser
import sys, time, types, os, datetime
import gobject, pango, cairo, array, pangocairo, gio
-import pynotify
+
+if use_pynotify:
+ import pynotify
from multiprocessing import Process, Pipe, Queue, Event, Value, Array, current_process, log_to_stderr
from ctypes import c_int, c_bool, c_char
@@ -50,7 +53,7 @@ logger = log_to_stderr()
# Rapid Photo Downloader modules
import rpdfile
-
+
import problemnotification as pn
import thumbnail as tn
import rpdmultiprocessing as rpdmp
@@ -105,10 +108,10 @@ from config import STATUS_CANNOT_DOWNLOAD, STATUS_DOWNLOADED, \
STATUS_NOT_DOWNLOADED, \
STATUS_DOWNLOAD_AND_BACKUP_FAILED, \
STATUS_WARNING
-
+
DOWNLOADED = [STATUS_DOWNLOADED, STATUS_DOWNLOADED_WITH_WARNING, STATUS_BACKUP_PROBLEM]
-#Translators: if neccessary, for guidance in how to translate this program, you may see http://damonlynch.net/translate.html
+#Translators: if neccessary, for guidance in how to translate this program, you may see http://damonlynch.net/translate.html
PROGRAM_NAME = _('Rapid Photo Downloader')
__version__ = config.version
@@ -117,12 +120,12 @@ def date_time_human_readable(date, with_line_break=True):
return _("%(date)s\n%(time)s") % {'date':date.strftime("%x"), 'time':date.strftime("%X")}
else:
return _("%(date)s %(time)s") % {'date':date.strftime("%x"), 'time':date.strftime("%X")}
-
+
def date_time_subseconds_human_readable(date, subseconds):
return _("%(date)s %(hour)s:%(minute)s:%(second)s:%(subsecond)s") % \
- {'date':date.strftime("%x"),
+ {'date':date.strftime("%x"),
'hour':date.strftime("%H"),
- 'minute':date.strftime("%M"),
+ 'minute':date.strftime("%M"),
'second':date.strftime("%S"),
'subsecond': subseconds}
@@ -135,7 +138,7 @@ class DeviceCollection(gtk.TreeView):
def __init__(self, parent_app):
self.parent_app = parent_app
- # device icon & name, size of images on the device (human readable),
+ # device icon & name, size of images on the device (human readable),
# copy progress (%), copy text, eject button (None if irrelevant),
# process id, pulse
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, float, str,
@@ -144,15 +147,15 @@ class DeviceCollection(gtk.TreeView):
self.devices_by_scan_pid = {}
gtk.TreeView.__init__(self, self.liststore)
-
+
self.props.enable_search = False
# make it impossible to select a row
selection = self.get_selection()
selection.set_mode(gtk.SELECTION_NONE)
self.set_headers_visible(False)
-
-
- # Device refers to a thing like a camera, memory card in its reader,
+
+
+ # Device refers to a thing like a camera, memory card in its reader,
# external hard drive, Portable Storage Device, etc.
column0 = gtk.TreeViewColumn(_("Device"))
pixbuf_renderer = gtk.CellRendererPixbuf()
@@ -168,46 +171,46 @@ class DeviceCollection(gtk.TreeView):
column0.add_attribute(text_renderer, 'text', 1)
column0.add_attribute(eject_renderer, 'pixbuf', 5)
self.append_column(column0)
-
-
+
+
# Size refers to the total size of images on the device, typically in
# MB or GB
column1 = gtk.TreeViewColumn(_("Size"), gtk.CellRendererText(), text=2)
self.append_column(column1)
-
- column2 = gtk.TreeViewColumn(_("Download Progress"),
+
+ column2 = gtk.TreeViewColumn(_("Download Progress"),
gtk.CellRendererProgress(),
value=3,
text=4,
pulse=7)
self.append_column(column2)
self.show_all()
-
+
icontheme = gtk.icon_theme_get_default()
try:
- self.eject_pixbuf = icontheme.load_icon('media-eject', 16,
+ self.eject_pixbuf = icontheme.load_icon('media-eject', 16,
gtk.ICON_LOOKUP_USE_BUILTIN)
except:
self.eject_pixbuf = gtk.gdk.pixbuf_new_from_file(
paths.share_dir('glade3/media-eject.png'))
-
+
self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.connect('button-press-event', self.button_clicked)
-
+
def add_device(self, process_id, device, progress_bar_text = ''):
-
+
# add the row, and get a temporary pointer to the row
size_files = ''
progress = 0.0
-
+
if device.mount is None:
eject = None
else:
eject = self.eject_pixbuf
-
+
self.devices_by_scan_pid[process_id] = device
-
+
iter = self.liststore.append((device.get_icon(),
device.get_name(),
size_files,
@@ -216,9 +219,9 @@ class DeviceCollection(gtk.TreeView):
eject,
process_id,
-1))
-
+
self._set_process_map(process_id, iter)
-
+
# adjust scrolled window height, based on row height and number of ready to start downloads
# please note, at program startup, self.row_height() will be less than it will be when already running
@@ -228,7 +231,8 @@ class DeviceCollection(gtk.TreeView):
row_height = self.get_background_area(0, self.get_column(0))[3] + 1
height = max(((len(self.map_process_to_row) + 1) * row_height), 24)
self.parent_app.device_collection_scrolledwindow.set_size_request(-1, height)
-
+
+
def update_device(self, process_id, total_size_files):
"""
Updates the size of the photos and videos on the device, displayed to the user
@@ -238,39 +242,39 @@ class DeviceCollection(gtk.TreeView):
self.liststore.set_value(iter, 2, total_size_files)
else:
logger.critical("This device is unknown")
-
+
def get_device(self, process_id):
return self.devices_by_scan_pid.get(process_id)
-
+
def remove_device(self, process_id):
if process_id in self.map_process_to_row:
iter = self._get_process_map(process_id)
self.liststore.remove(iter)
del self.map_process_to_row[process_id]
del self.devices_by_scan_pid[process_id]
-
+
def get_all_displayed_processes(self):
"""
- returns a list of the processes currently being displayed to the user
+ returns a list of the processes currently being displayed to the user
"""
return self.map_process_to_row.keys()
def _set_process_map(self, process_id, iter):
"""
- convert the temporary iter into a tree reference, which is
+ convert the temporary iter into a tree reference, which is
permanent
"""
path = self.liststore.get_path(iter)
treerowref = gtk.TreeRowReference(self.liststore, path)
self.map_process_to_row[process_id] = treerowref
-
+
def _get_process_map(self, process_id):
"""
return the tree iter for this process
"""
-
+
if process_id in self.map_process_to_row:
treerowref = self.map_process_to_row[process_id]
path = treerowref.get_path()
@@ -278,16 +282,16 @@ class DeviceCollection(gtk.TreeView):
return iter
else:
return None
-
+
def update_progress(self, scan_pid, percent_complete, progress_bar_text, bytes_downloaded, pulse=None):
-
+
iter = self._get_process_map(scan_pid)
if iter:
if percent_complete:
self.liststore.set_value(iter, 3, percent_complete)
if progress_bar_text:
self.liststore.set_value(iter, 4, progress_bar_text)
-
+
if pulse is not None:
if pulse:
# Make the bar pulse
@@ -316,14 +320,14 @@ class DeviceCollection(gtk.TreeView):
iter = self.liststore.get_iter(path)
if self.liststore.get_value(iter, 5) is not None:
self.unmount(process_id = self.liststore.get_value(iter, 6))
-
+
def unmount(self, process_id):
device = self.devices_by_scan_pid[process_id]
if device.mount is not None:
logger.debug("Unmounting device with scan pid %s", process_id)
device.mount.unmount(self.unmount_callback)
-
-
+
+
def unmount_callback(self, mount, result):
name = mount.get_name()
@@ -332,13 +336,14 @@ class DeviceCollection(gtk.TreeView):
logger.debug("%s successfully unmounted" % name)
except gio.Error, inst:
logger.error("%s did not unmount: %s", name, inst)
-
- title = _("%(device)s did not unmount") % {'device': name}
- message = '%s' % inst
-
- n = pynotify.Notification(title, message)
- n.set_icon_from_pixbuf(self.parent_app.application_icon)
- n.show()
+
+ if use_pynotify:
+ title = _("%(device)s did not unmount") % {'device': name}
+ message = '%s' % inst
+
+ n = pynotify.Notification(title, message)
+ n.set_icon_from_pixbuf(self.parent_app.application_icon)
+ n.show()
def create_cairo_image_surface(pil_image, image_width, image_height):
@@ -353,55 +358,55 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
__gproperties__ = {
"image": (gobject.TYPE_PYOBJECT, "Image",
"Image", gobject.PARAM_READWRITE),
-
- "filename": (gobject.TYPE_STRING, "Filename",
+
+ "filename": (gobject.TYPE_STRING, "Filename",
"Filename", '', gobject.PARAM_READWRITE),
-
+
"status": (gtk.gdk.Pixbuf, "Status",
"Status", gobject.PARAM_READWRITE),
}
-
+
def __init__(self, checkbutton_height):
gtk.CellRenderer.__init__(self)
self.image = None
-
+
self.image_area_size = 100
self.text_area_size = 30
self.padding = 6
self.checkbutton_height = checkbutton_height
self.icon_width = 20
-
+
def do_set_property(self, pspec, value):
setattr(self, pspec.name, value)
def do_get_property(self, pspec):
return getattr(self, pspec.name)
-
+
def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
-
+
cairo_context = window.cairo_create()
-
+
x = cell_area.x
y = cell_area.y + self.checkbutton_height - 8
w = cell_area.width
h = cell_area.height
-
- #constrain operations to cell area, allowing for a 1 pixel border
+
+ #constrain operations to cell area, allowing for a 1 pixel border
#either side
#~ cairo_context.rectangle(x-1, y-1, w+2, h+2)
#~ cairo_context.clip()
-
+
#fill in the background with dark grey
#this ensures that a selected cell's fill does not make
#the text impossible to read
#~ cairo_context.rectangle(x, y, w, h)
#~ cairo_context.set_source_rgb(0.267, 0.267, 0.267)
#~ cairo_context.fill()
-
+
#image width and height
image_w = self.image.size[0]
image_h = self.image.size[1]
-
+
#center the image horizontally
#bottom align vertically
#top left and right corners for the image:
@@ -416,33 +421,33 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
cairo_context.set_line_width(1)
cairo_context.rectangle(image_x-.5, image_y-.5, image_w+1, image_h+1)
cairo_context.stroke()
-
+
# draw a thin border around each cell
#~ cairo_context.set_source_rgb(0.33,0.33,0.33)
#~ cairo_context.rectangle(x, y, w, h)
#~ cairo_context.stroke()
-
+
#place the image
cairo_context.set_source_surface(image, image_x, image_y)
cairo_context.paint()
-
+
#text
context = pangocairo.CairoContext(cairo_context)
-
+
text_y = y + self.image_area_size + 10
text_w = w - self.icon_width
text_x = x + self.icon_width
#~ context.rectangle(text_x, text_y, text_w, 15)
- #~ context.clip()
-
+ #~ context.clip()
+
layout = context.create_layout()
width = text_w * pango.SCALE
layout.set_width(width)
-
+
layout.set_alignment(pango.ALIGN_CENTER)
layout.set_ellipsize(pango.ELLIPSIZE_END)
-
+
#font color and size
fg_color = pango.AttrForeground(65535, 65535, 65535, 0, -1)
font_size = pango.AttrSize(8192, 0, -1) # 8 * 1024 = 8192
@@ -453,7 +458,7 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
attr.insert(font_family)
layout.set_attributes(attr)
- layout.set_text(self.filename)
+ layout.set_text(self.filename)
context.move_to(text_x, text_y)
context.show_layout(layout)
@@ -461,13 +466,13 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
#status
cairo_context.set_source_pixbuf(self.status, x, y + self.image_area_size + 10)
cairo_context.paint()
-
+
def do_get_size(self, widget, cell_area):
return (0, 0, self.image_area_size, self.image_area_size + self.text_area_size - self.checkbutton_height + 4)
-
+
gobject.type_register(ThumbnailCellRenderer)
-
+
class ThumbnailDisplay(gtk.IconView):
def __init__(self, parent_app):
@@ -475,39 +480,39 @@ class ThumbnailDisplay(gtk.IconView):
self.set_spacing(0)
self.set_row_spacing(5)
self.set_margin(25)
-
+
self.set_selection_mode(gtk.SELECTION_MULTIPLE)
self.connect('selection-changed', self.on_selection_changed)
self._selected_items = []
-
+
self.rapid_app = parent_app
-
+
self.batch_size = 10
-
+
self.thumbnail_manager = ThumbnailManager(self.thumbnail_results, self.batch_size)
self.preview_manager = PreviewManager(self.preview_results)
-
- self.treerow_index = {}
- self.process_index = {}
-
+
+ self.treerow_index = {}
+ self.process_index = {}
+
self.rpd_files = {}
-
+
self.total_thumbs_to_generate = 0
self.thumbnails_generated = 0
-
+
# dict of scan_pids that are having thumbnails generated
# value is the thumbnail process id
# this is needed when terminating thumbnailing early such as when
# user clicks download before the thumbnailing is finished
self.generating_thumbnails = {}
-
+
self.thumbnails = {}
self.previews = {}
self.previews_being_fetched = set()
-
+
self.stock_photo_thumbnails = tn.PhotoIcons()
self.stock_video_thumbnails = tn.VideoIcons()
-
+
self.SELECTED_COL = 1
self.UNIQUE_ID_COL = 2
self.TIMESTAMP_COL = 4
@@ -515,11 +520,11 @@ class ThumbnailDisplay(gtk.IconView):
self.CHECKBUTTON_VISIBLE_COL = 6
self.DOWNLOAD_STATUS_COL = 7
self.STATUS_ICON_COL = 8
-
+
self._create_liststore()
self.clear()
-
+
checkbutton = gtk.CellRendererToggle()
checkbutton.set_radio(False)
checkbutton.props.activatable = True
@@ -529,11 +534,11 @@ class ThumbnailDisplay(gtk.IconView):
self.add_attribute(checkbutton, "active", 1)
self.add_attribute(checkbutton, "visible", 6)
-
+
checkbutton_size = checkbutton.get_size(self, None)
checkbutton_height = checkbutton_size[3]
checkbutton_width = checkbutton_size[2]
-
+
image = ThumbnailCellRenderer(checkbutton_height)
self.pack_start(image, expand=True)
self.add_attribute(image, "image", 0)
@@ -542,12 +547,12 @@ class ThumbnailDisplay(gtk.IconView):
#set the background color to a darkish grey
self.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color('#444444'))
-
+
self.show_all()
self._setup_icons()
-
+
self.connect('item-activated', self.on_item_activated)
-
+
def _create_liststore(self):
"""
Creates the default list store to hold the icons
@@ -562,8 +567,8 @@ class ThumbnailDisplay(gtk.IconView):
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
@@ -582,19 +587,19 @@ class ThumbnailDisplay(gtk.IconView):
size, size)
self.download_pending_icon = gtk.gdk.pixbuf_new_from_file_at_size(
paths.share_dir('glade3/rapid-photo-downloader-download-pending.png'),
- size, size)
+ size, size)
self.downloaded_with_warning_icon = gtk.gdk.pixbuf_new_from_file_at_size(
paths.share_dir('glade3/rapid-photo-downloader-downloaded-with-warning.svg'),
size, size)
self.downloaded_with_error_icon = gtk.gdk.pixbuf_new_from_file_at_size(
paths.share_dir('glade3/rapid-photo-downloader-downloaded-with-error.svg'),
size, size)
-
+
# make the not yet downloaded icon a transparent square
self.not_downloaded_icon = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, 16, 16)
self.not_downloaded_icon.fill(0xffffffff)
self.not_downloaded_icon = self.not_downloaded_icon.add_alpha(True, chr(255), chr(255), chr(255))
-
+
def get_status_icon(self, status):
"""
Returns the correct icon, based on the status
@@ -616,33 +621,33 @@ class ThumbnailDisplay(gtk.IconView):
else:
logger.critical("FIXME: unknown status: %s", status)
status_icon = self.not_downloaded_icon
- return status_icon
-
+ return status_icon
+
def sort_by_timestamp(self):
self.liststore.set_sort_column_id(self.TIMESTAMP_COL, gtk.SORT_ASCENDING)
-
+
def on_selection_changed(self, iconview):
self._selected_items = self.get_selected_items()
-
+
def on_checkbutton_toggled(self, cellrenderertoggle, path):
paths = [p[0] for p in self._selected_items]
if int(path) not in paths:
self._selected_items = [path,]
-
+
for path in self._selected_items:
iter = self.liststore.get_iter(path)
status = self.liststore.get_value(iter, self.DOWNLOAD_STATUS_COL)
if status == STATUS_NOT_DOWNLOADED:
self.liststore.set_value(iter, self.SELECTED_COL, not cellrenderertoggle.get_active())
self.select_path(path)
-
+
self.rapid_app.set_download_action_sensitivity()
-
-
+
+
def set_selected(self, unique_id, value):
iter = self.get_iter_from_unique_id(unique_id)
self.liststore.set_value(iter, self.SELECTED_COL, value)
-
+
def add_file(self, rpd_file, generate_thumbnail):
thumbnail_icon = self.get_stock_icon(rpd_file.file_type)
@@ -650,7 +655,7 @@ class ThumbnailDisplay(gtk.IconView):
scan_pid = rpd_file.scan_pid
timestamp = int(rpd_file.modification_time)
-
+
iter = self.liststore.append((thumbnail_icon,
True,
unique_id,
@@ -661,47 +666,47 @@ class ThumbnailDisplay(gtk.IconView):
STATUS_NOT_DOWNLOADED,
self.not_downloaded_icon
))
-
+
path = self.liststore.get_path(iter)
treerowref = gtk.TreeRowReference(self.liststore, path)
-
+
if scan_pid in self.process_index:
self.process_index[scan_pid].append(unique_id)
else:
self.process_index[scan_pid] = [unique_id,]
-
+
self.treerow_index[unique_id] = treerowref
self.rpd_files[unique_id] = rpd_file
-
+
if generate_thumbnail:
self.total_thumbs_to_generate += 1
def get_sample_file(self, file_type):
- """Returns an rpd_file for of a given file type, or None if it does
+ """Returns an rpd_file for of a given file type, or None if it does
not exist"""
for unique_id, rpd_file in self.rpd_files.iteritems():
if rpd_file.file_type == file_type:
if rpd_file.status <> STATUS_CANNOT_DOWNLOAD:
return rpd_file
-
+
return None
-
+
def get_unique_id_from_iter(self, iter):
return self.liststore.get_value(iter, 2)
-
+
def get_iter_from_unique_id(self, unique_id):
treerowref = self.treerow_index[unique_id]
path = treerowref.get_path()
return self.liststore.get_iter(path)
-
- def on_item_activated(self, iconview, path):
+
+ def on_item_activated(self, iconview, path):
"""
"""
iter = self.liststore.get_iter(path)
self.show_preview(iter=iter)
self.advance_get_preview_image(iter)
-
+
def _get_preview(self, unique_id, rpd_file):
if unique_id not in self.previews_being_fetched:
#check if preview should be from a downloaded file, or the source
@@ -711,13 +716,13 @@ class ThumbnailDisplay(gtk.IconView):
else:
file_location = rpd_file.full_file_name
thm_file_name = rpd_file.thm_full_name
-
+
self.preview_manager.get_preview(unique_id, file_location,
thm_file_name,
rpd_file.file_type, size_max=None,)
-
+
self.previews_being_fetched.add(unique_id)
-
+
def show_preview(self, unique_id=None, iter=None):
if unique_id is not None:
iter = self.get_iter_from_unique_id(unique_id)
@@ -734,31 +739,31 @@ class ThumbnailDisplay(gtk.IconView):
path = 0
iter = self.liststore.get_iter(path)
unique_id = self.get_unique_id_from_iter(iter)
-
-
- rpd_file = self.rpd_files[unique_id]
-
+
+
+ rpd_file = self.rpd_files[unique_id]
+
if unique_id in self.previews:
preview_image = self.previews[unique_id]
else:
# request daemon process to get a full size thumbnail
self._get_preview(unique_id, rpd_file)
- if unique_id in self.thumbnails:
+ if unique_id in self.thumbnails:
preview_image = self.thumbnails[unique_id]
else:
preview_image = self.get_stock_icon(rpd_file.file_type)
-
+
checked = self.liststore.get_value(iter, self.SELECTED_COL)
include_checkbutton_visible = rpd_file.status == STATUS_NOT_DOWNLOADED
- self.rapid_app.show_preview_image(unique_id, preview_image,
+ self.rapid_app.show_preview_image(unique_id, preview_image,
include_checkbutton_visible, checked)
-
+
def _get_next_iter(self, iter):
iter = self.liststore.iter_next(iter)
if iter is None:
iter = self.liststore.get_iter_first()
return iter
-
+
def _get_prev_iter(self, iter):
row = self.liststore.get_path(iter)[0]
if row == 0:
@@ -766,44 +771,44 @@ class ThumbnailDisplay(gtk.IconView):
else:
row -= 1
iter = self.liststore.get_iter(row)
- return iter
-
+ return iter
+
def show_next_image(self, unique_id):
iter = self.get_iter_from_unique_id(unique_id)
iter = self._get_next_iter(iter)
if iter is not None:
self.show_preview(iter=iter)
-
+
# cache next image
self.advance_get_preview_image(iter, prev=False, next=True)
-
+
def show_prev_image(self, unique_id):
iter = self.get_iter_from_unique_id(unique_id)
iter = self._get_prev_iter(iter)
if iter is not None:
self.show_preview(iter=iter)
-
+
# cache next image
self.advance_get_preview_image(iter, prev=True, next=False)
-
+
def advance_get_preview_image(self, iter, prev=True, next=True):
unique_ids = []
if next:
next_iter = self._get_next_iter(iter)
unique_ids.append(self.get_unique_id_from_iter(next_iter))
-
+
if prev:
prev_iter = self._get_prev_iter(iter)
unique_ids.append(self.get_unique_id_from_iter(prev_iter))
-
+
for unique_id in unique_ids:
if not unique_id in self.previews:
rpd_file = self.rpd_files[unique_id]
self._get_preview(unique_id, rpd_file)
-
+
def check_all(self, check_all, file_type=None):
for row in self.liststore:
if row[self.CHECKBUTTON_VISIBLE_COL]:
@@ -813,7 +818,7 @@ class ThumbnailDisplay(gtk.IconView):
else:
row[self.SELECTED_COL] = check_all
self.rapid_app.set_download_action_sensitivity()
-
+
def files_are_checked_to_download(self):
"""
Returns True if there is any file that the user has indicated they
@@ -825,12 +830,12 @@ class ThumbnailDisplay(gtk.IconView):
if rpd_file.status not in DOWNLOADED:
return True
return False
-
+
def get_files_checked_for_download(self, scan_pid):
"""
Returns a dict of scan ids and associated files the user has indicated
they want to download
-
+
If scan_pid is not None, then returns only those files from that scan_pid
"""
files = dict()
@@ -853,7 +858,7 @@ class ThumbnailDisplay(gtk.IconView):
if self.liststore.get_value(iter, self.SELECTED_COL):
files[scan_pid].append(rpd_file)
return files
-
+
def get_no_files_remaining(self, scan_pid):
"""
Returns the number of files that have not yet been downloaded for the
@@ -865,17 +870,17 @@ class ThumbnailDisplay(gtk.IconView):
if rpd_file.status == STATUS_NOT_DOWNLOADED:
i += 1
return i
-
+
def files_remain_to_download(self):
"""
- Returns True if any files remain that are not downloaded, else returns
+ Returns True if any files remain that are not downloaded, else returns
False
"""
for row in self.liststore:
if row[self.DOWNLOAD_STATUS_COL] == STATUS_NOT_DOWNLOADED:
return True
return False
-
+
def mark_download_pending(self, files_by_scan_pid):
"""
@@ -895,19 +900,19 @@ class ThumbnailDisplay(gtk.IconView):
self.liststore.set_value(iter, self.DOWNLOAD_STATUS_COL, STATUS_DOWNLOAD_PENDING)
icon = self.get_status_icon(STATUS_DOWNLOAD_PENDING)
self.liststore.set_value(iter, self.STATUS_ICON_COL, icon)
-
+
def select_image(self, unique_id):
iter = self.get_iter_from_unique_id(unique_id)
path = self.liststore.get_path(iter)
self.select_path(path)
self.scroll_to_path(path, use_align=False, row_align=0.5, col_align=0.5)
-
+
def get_stock_icon(self, file_type):
if file_type == rpdfile.FILE_TYPE_PHOTO:
return self.stock_photo_thumbnails.stock_thumbnail_image_icon
else:
return self.stock_video_thumbnails.stock_thumbnail_image_icon
-
+
def update_status_post_download(self, rpd_file):
iter = self.get_iter_from_unique_id(rpd_file.unique_id)
self.liststore.set_value(iter, self.DOWNLOAD_STATUS_COL, rpd_file.status)
@@ -915,7 +920,7 @@ class ThumbnailDisplay(gtk.IconView):
self.liststore.set_value(iter, self.STATUS_ICON_COL, icon)
self.liststore.set_value(iter, self.CHECKBUTTON_VISIBLE_COL, False)
self.rpd_files[rpd_file.unique_id] = rpd_file
-
+
def generate_thumbnails(self, scan_pid):
"""Initiate thumbnail generation for files scanned in one process
"""
@@ -923,81 +928,81 @@ class ThumbnailDisplay(gtk.IconView):
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]
path = treerowref.get_path()
iter = self.liststore.get_iter(path)
- self.liststore.set(iter, 0, icon)
-
+ self.liststore.set(iter, 0, icon)
+
def update_thumbnail(self, thumbnail_data):
"""
Takes the generated thumbnail and updates the display
-
+
If the thumbnail_data includes a second image, that is used to
update the thumbnail list using the unique_id
"""
unique_id = thumbnail_data[0]
thumbnail_icon = thumbnail_data[1]
-
+
if thumbnail_icon is not None:
# get the thumbnail icon in PIL format
thumbnail_icon = thumbnail_icon.get_image()
-
+
if thumbnail_icon:
self._set_thumbnail(unique_id, thumbnail_icon)
-
+
if len(thumbnail_data) > 2:
# get the 2nd image in PIL format
self.thumbnails[unique_id] = thumbnail_data[2].get_image()
def terminate_thumbnail_generation(self, scan_pid):
"""
- Terminates thumbnail generation if thumbnails are currently
+ Terminates thumbnail generation if thumbnails are currently
being generated for this scan_pid
"""
-
+
if scan_pid in self.generating_thumbnails:
terminated = True
self.thumbnail_manager.terminate_process(
self.generating_thumbnails[scan_pid])
del self.generating_thumbnails[scan_pid]
-
+
if len(self.generating_thumbnails) == 0:
self._reset_thumbnail_tracking_and_display()
else:
terminated = False
-
+
return terminated
-
+
def mark_thumbnails_needed(self, rpd_files):
for rpd_file in rpd_files:
if rpd_file.unique_id not in self.thumbnails:
rpd_file.generate_thumbnail = True
-
+
def _reset_thumbnail_tracking_and_display(self):
self.rapid_app.download_progressbar.set_fraction(0.0)
self.rapid_app.download_progressbar.set_text('')
self.thumbnails_generated = 0
self.total_thumbs_to_generate = 0
-
+
def thumbnail_results(self, source, condition):
connection = self.thumbnail_manager.get_pipe(source)
-
+
conn_type, data = connection.recv()
-
+
if conn_type == rpdmp.CONN_COMPLETE:
scan_pid = data
del self.generating_thumbnails[scan_pid]
connection.close()
return False
else:
-
+
for thumbnail_data in data:
self.update_thumbnail(thumbnail_data)
-
+
self.thumbnails_generated += len(data)
-
+
# clear progress bar information if all thumbnails have been
# extracted
if self.thumbnails_generated == self.total_thumbs_to_generate:
@@ -1006,10 +1011,10 @@ class ThumbnailDisplay(gtk.IconView):
if self.total_thumbs_to_generate:
self.rapid_app.download_progressbar.set_fraction(
float(self.thumbnails_generated) / self.total_thumbs_to_generate)
-
-
+
+
return True
-
+
def preview_results(self, unique_id, preview_full_size, preview_small):
"""
Receive a full size preview image and update
@@ -1019,24 +1024,24 @@ class ThumbnailDisplay(gtk.IconView):
preview_image = preview_full_size.get_image()
self.previews[unique_id] = preview_image
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 and preview_small is not None:
self._set_thumbnail(unique_id, preview_small.get_image())
-
-
+
+
def clear_all(self, scan_pid=None, keep_downloaded_files=False):
"""
Removes files from display and internal tracking.
-
+
If scan_pid is not None, then only files matching that scan_pid will
be removed. Otherwise, everything will be removed.
-
+
If keep_downloaded_files is True, files will not be removed if they
have been downloaded.
"""
if scan_pid is None and not keep_downloaded_files:
-
+
# 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")
@@ -1045,7 +1050,7 @@ class ThumbnailDisplay(gtk.IconView):
self.treerow_index = {}
self.process_index = {}
-
+
self.rpd_files = {}
else:
if scan_pid in self.process_index:
@@ -1060,80 +1065,80 @@ 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):
self.results_callback = results_callback
-
+
# List of actual process, it's terminate_queue, and it's run_event
self._processes = []
-
+
self._pipes = {}
self.batch_size = batch_size
-
+
self.paused = False
self.no_tasks = 0
-
-
+
+
def add_task(self, task):
pid = self._setup_task(task)
logger.debug("TaskManager PID: %s", pid)
self.no_tasks += 1
return pid
-
+
def _setup_task(self, task):
task_results_conn, task_process_conn = self._setup_pipe()
-
+
source = task_results_conn.fileno()
self._pipes[source] = task_results_conn
gobject.io_add_watch(source, gobject.IO_IN, self.results_callback)
-
+
terminate_queue = Queue()
run_event = Event()
run_event.set()
-
- return self._initiate_task(task, task_results_conn, task_process_conn,
+
+ return self._initiate_task(task, task_results_conn, task_process_conn,
terminate_queue, run_event)
-
+
def _setup_pipe(self):
return Pipe(duplex=False)
-
+
def _initiate_task(self, task, task_process_conn, terminate_queue, run_event):
logger.error("Implement child class method!")
-
-
+
+
def processes(self):
for i in range(len(self._processes)):
yield self._processes[i]
-
+
def start(self):
self.paused = False
for scan in self.processes():
run_event = scan[2]
if not run_event.is_set():
run_event.set()
-
+
def pause(self):
self.paused = True
for scan in self.processes():
run_event = scan[2]
if run_event.is_set():
run_event.clear()
-
+
def _terminate_process(self, p):
self._send_termination_msg(p)
# The process might be paused: let it run
run_event = p[2]
if not run_event.is_set():
run_event.set()
-
+
def _send_termination_msg(self, p):
p[1].put(None)
-
+
def terminate_process(self, process_id):
"""
Send a signal to process with matching process_id that it should
@@ -1143,7 +1148,7 @@ class TaskManager:
if p[0].pid == process_id:
if p[0].is_alive():
self._terminate_process(p)
-
+
def request_termination(self):
"""
Send a signal to processes that they should immediately terminate
@@ -1153,29 +1158,29 @@ class TaskManager:
if p[0].is_alive():
requested = True
self._terminate_process(p)
-
+
return requested
-
+
def terminate_forcefully(self):
"""
Forcefully terminates any running processes. Use with great caution.
- No cleanup action is performed.
-
+ No cleanup action is performed.
+
As python essential reference (4th edition) says, if the process
'holds a lock or is involved with interprocess communication,
terminating it might cause a deadlock or corrupted I/O.'
"""
-
+
for p in self.processes():
if p[0].is_alive():
- logger.info("Forcefully terminating %s in %s" , p[0].name,
+ logger.info("Forcefully terminating %s in %s" , p[0].name,
self.__class__.__name__)
p[0].terminate()
-
+
def get_pipe(self, source):
return self._pipes[source]
-
+
def get_no_active_processes(self):
"""
Returns how many processes are currently active, i.e. running
@@ -1188,38 +1193,38 @@ class TaskManager:
class ScanManager(TaskManager):
-
- def __init__(self, results_callback, batch_size,
+
+ def __init__(self, results_callback, batch_size,
add_device_function):
TaskManager.__init__(self, results_callback, batch_size)
self.add_device_function = add_device_function
-
- def _initiate_task(self, task, task_results_conn, task_process_conn,
+
+ def _initiate_task(self, task, task_results_conn, task_process_conn,
terminate_queue, run_event):
-
+
device = task[0]
ignored_paths = task[1]
use_re_ignored_paths = task[2]
-
+
scan = scan_process.Scan(device.get_path(),
ignored_paths,
use_re_ignored_paths,
- self.batch_size,
+ self.batch_size,
task_process_conn, terminate_queue, run_event)
scan.start()
self._processes.append((scan, terminate_queue, run_event))
- self.add_device_function(scan.pid, device,
+ self.add_device_function(scan.pid, device,
# This refers to when a device like a hard drive is having its contents scanned,
- # looking for photos or videos. It is visible initially in the progress bar for each device
+ # looking for photos or videos. It is visible initially in the progress bar for each device
# (which normally holds "x photos and videos").
# It maybe displayed only briefly if the contents of the device being scanned is small.
progress_bar_text=_('scanning...'))
-
+
return scan.pid
-
+
class CopyFilesManager(TaskManager):
-
- def _initiate_task(self, task, task_results_conn,
+
+ def _initiate_task(self, task, task_results_conn,
task_process_conn, terminate_queue, run_event):
photo_download_folder = task[0]
video_download_folder = task[1]
@@ -1227,25 +1232,25 @@ class CopyFilesManager(TaskManager):
files = task[3]
modify_files_during_download = task[4]
modify_pipe = task[5]
-
+
copy_files = copyfiles.CopyFiles(photo_download_folder,
video_download_folder,
- files,
+ files,
modify_files_during_download,
modify_pipe,
- scan_pid, self.batch_size,
+ scan_pid, self.batch_size,
task_process_conn, terminate_queue, run_event)
copy_files.start()
self._processes.append((copy_files, terminate_queue, run_event))
return copy_files.pid
-
+
class ThumbnailManager(TaskManager):
def _initiate_task(self, task, task_results_conn,
task_process_conn, terminate_queue, run_event):
scan_pid = task[0]
files = task[1]
- generator = tn.GenerateThumbnails(scan_pid, files, self.batch_size,
- task_process_conn, terminate_queue,
+ generator = tn.GenerateThumbnails(scan_pid, files, self.batch_size,
+ task_process_conn, terminate_queue,
run_event)
generator.start()
self._processes.append((generator, terminate_queue, run_event))
@@ -1256,44 +1261,44 @@ class FileModifyManager(TaskManager):
Duplex, multiprocess, similar to BackupFilesManager
"""
def __init__(self, results_callback):
- TaskManager.__init__(self, results_callback=results_callback,
+ TaskManager.__init__(self, results_callback=results_callback,
batch_size=0)
self.file_modify_by_scan_pid = {}
-
- def _initiate_task(self, task, task_results_conn, task_process_conn,
+
+ def _initiate_task(self, task, task_results_conn, task_process_conn,
terminate_queue, run_event):
scan_pid = task[0]
auto_rotate_jpeg = task[1]
focal_length = task[2]
-
+
file_modify = filemodify.FileModify(auto_rotate_jpeg, focal_length,
- task_process_conn, terminate_queue,
+ task_process_conn, terminate_queue,
run_event)
file_modify.start()
- self._processes.append((file_modify, terminate_queue, run_event,
+ self._processes.append((file_modify, terminate_queue, run_event,
task_results_conn))
-
+
self.file_modify_by_scan_pid[scan_pid] = (task_results_conn, file_modify.pid)
-
+
return file_modify.pid
def _setup_pipe(self):
return Pipe(duplex=True)
-
+
def _send_termination_msg(self, p):
p[1].put(None)
p[3].send((None, None))
-
+
def get_modify_pipe(self, scan_pid):
return self.file_modify_by_scan_pid[scan_pid][0]
-
-
+
+
class BackupFilesManager(TaskManager):
"""
Handles backup processes. This is a little different from some other Task
Manager classes in that its pipe is Duplex, and the work done by it
is not pre-assigned when the process is started.
-
+
Duplex, multiprocess.
"""
def __init__(self, results_callback, batch_size):
@@ -1302,30 +1307,31 @@ class BackupFilesManager(TaskManager):
def _setup_pipe(self):
return Pipe(duplex=True)
-
+
def _send_termination_msg(self, p):
p[1].put(None)
- p[3].send((None, None, None, None))
-
- def _initiate_task(self, task, task_results_conn, task_process_conn,
+ p[3].send((None, None, None, None, None))
+
+ def _initiate_task(self, task, task_results_conn, task_process_conn,
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,
+ backup_files = backupfile.BackupFiles(path, name, self.batch_size,
+ task_process_conn, terminate_queue,
run_event)
backup_files.start()
- self._processes.append((backup_files, terminate_queue, run_event,
+ self._processes.append((backup_files, terminate_queue, run_event,
task_results_conn))
-
+
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):
+
+ def backup_file(self, move_succeeded, rpd_file, path_suffix,
+ backup_duplicate_overwrite,
+ download_count):
if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
logger.debug("Backing up photo %s", rpd_file.download_name)
@@ -1334,124 +1340,124 @@ class BackupFilesManager(TaskManager):
for path in self.backup_devices_by_path:
backup_type = self.backup_devices_by_path[path][2]
- if ((backup_type == PHOTO_VIDEO_BACKUP) or
+ 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))
+ task_results_conn.send((move_succeeded, rpd_file, path_suffix,
+ backup_duplicate_overwrite, download_count))
else:
logger.debug("Not backing up to %s", path)
-
+
def add_device(self, path, name, backup_type):
"""
Convenience function to setup adding a backup device
"""
return self.add_task((path, name, backup_type))
-
+
def remove_device(self, path):
pid = self.backup_devices_by_path[path][1]
self.terminate_process(pid)
del self.backup_devices_by_path[path]
-
-
+
+
class SingleInstanceTaskManager:
"""
Base class to manage single instance processes. Examples are daemon
processes, but also a non-daemon process that has one simple task.
-
+
Core (infrastructure) functionality is implemented in this class.
Derived classes should implemented functionality to actually implement
specific tasks.
"""
- def __init__(self, results_callback):
+ def __init__(self, results_callback):
self.results_callback = results_callback
-
+
self.task_results_conn, self.task_process_conn = Pipe(duplex=True)
-
+
source = self.task_results_conn.fileno()
gobject.io_add_watch(source, gobject.IO_IN, self.task_results)
-
+
class PreviewManager(SingleInstanceTaskManager):
def __init__(self, results_callback):
SingleInstanceTaskManager.__init__(self, results_callback)
self._get_preview = tn.GetPreviewImage(self.task_process_conn)
self._get_preview.start()
-
+
def get_preview(self, unique_id, full_file_name, thm_file_name, file_type, size_max):
self.task_results_conn.send((unique_id, full_file_name, thm_file_name, file_type, size_max))
-
+
def task_results(self, source, condition):
unique_id, preview_full_size, preview_small = self.task_results_conn.recv()
self.results_callback(unique_id, preview_full_size, preview_small)
- return True
-
+ return True
+
class SubfolderFileManager(SingleInstanceTaskManager):
"""
Manages the daemon process that renames files and creates subfolders
"""
def __init__(self, results_callback, sequence_values):
SingleInstanceTaskManager.__init__(self, results_callback)
- self._subfolder_file = subfolderfile.SubfolderFile(self.task_process_conn,
+ self._subfolder_file = subfolderfile.SubfolderFile(self.task_process_conn,
sequence_values)
self._subfolder_file.start()
logger.debug("SubfolderFile PID: %s", self._subfolder_file.pid)
-
- def rename_file_and_move_to_subfolder(self, download_succeeded,
+
+ def rename_file_and_move_to_subfolder(self, download_succeeded,
download_count, rpd_file):
-
- self.task_results_conn.send((download_succeeded, download_count,
+
+ logger.debug("Sending file for rename: %s.", download_count)
+ self.task_results_conn.send((download_succeeded, download_count,
rpd_file))
- logger.debug("Download count: %s.", download_count)
-
+
def task_results(self, source, condition):
- move_succeeded, rpd_file = self.task_results_conn.recv()
- self.results_callback(move_succeeded, rpd_file)
+ move_succeeded, rpd_file, download_count = self.task_results_conn.recv()
+ self.results_callback(move_succeeded, rpd_file, download_count)
return True
-
+
class ResizblePilImage(gtk.DrawingArea):
def __init__(self, bg_color=None):
gtk.DrawingArea.__init__(self)
self.base_image = None
self.bg_color = bg_color
self.connect('expose_event', self.expose)
-
+
def set_image(self, image):
self.base_image = image
-
+
#set up sizes and ratio used for drawing the derived image
self.base_image_w = self.base_image.size[0]
self.base_image_h = self.base_image.size[1]
self.base_image_aspect = float(self.base_image_w) / self.base_image_h
-
+
self.queue_draw()
-
+
def expose(self, widget, event):
cairo_context = self.window.cairo_create()
-
- x = event.area.x
- y = event.area.y
+
+ x = event.area.x
+ y = event.area.y
w = event.area.width
h = event.area.height
-
- #constrain operations to event area
+
+ #constrain operations to event area
cairo_context.rectangle(x, y, w, h)
cairo_context.clip_preserve()
-
+
#set background color, if needed
if self.bg_color:
cairo_context.set_source_rgb(*self.bg_color)
- cairo_context.fill_preserve()
+ cairo_context.fill_preserve()
if not self.base_image:
return False
-
+
frame_aspect = float(w) / h
-
+
if frame_aspect > self.base_image_aspect:
# Frame is wider than image
height = h
@@ -1460,7 +1466,7 @@ class ResizblePilImage(gtk.DrawingArea):
# Frame is taller than image
width = w
height = int(width / self.base_image_aspect)
-
+
#resize image
pil_image = self.base_image.copy()
if self.base_image_w < width or self.base_image_h < height:
@@ -1473,38 +1479,38 @@ class ResizblePilImage(gtk.DrawingArea):
#image width and height
image_w = pil_image.size[0]
image_h = pil_image.size[1]
-
+
#center the image horizontally and vertically
#top left and right corners for the image:
image_x = x + ((w - image_w) / 2)
image_y = y + ((h - image_h) / 2)
-
+
image = create_cairo_image_surface(pil_image, image_w, image_h)
cairo_context.set_source_surface(image, image_x, image_y)
- cairo_context.paint()
+ cairo_context.paint()
+
+ return False
+
- return False
-
-
class PreviewImage:
-
+
def __init__(self, parent_app, builder):
#set background color to equivalent of '#444444
- self.preview_image = ResizblePilImage(bg_color=(0.267, 0.267, 0.267))
+ self.preview_image = ResizblePilImage(bg_color=(0.267, 0.267, 0.267))
self.preview_image_eventbox = builder.get_object("preview_eventbox")
self.preview_image_eventbox.add(self.preview_image)
self.preview_image.show()
self.download_this_checkbutton = builder.get_object("download_this_checkbutton")
self.rapid_app = parent_app
-
+
self.base_preview_image = None # large size image used to scale down from
self.current_preview_size = (0,0)
self.preview_image_size_limit = (0,0)
-
+
self.unique_id = None
-
- def set_preview_image(self, unique_id, pil_image, include_checkbutton_visible=None,
+
+ def set_preview_image(self, unique_id, pil_image, include_checkbutton_visible=None,
checked=None):
"""
"""
@@ -1516,72 +1522,74 @@ class PreviewImage:
if include_checkbutton_visible is not None:
self.download_this_checkbutton.props.visible = include_checkbutton_visible
-
+
def update_preview_image(self, unique_id, pil_image):
if unique_id == self.unique_id:
self.set_preview_image(unique_id, pil_image)
-
+
class RapidApp(dbus.service.Object):
"""
The main Rapid Photo Downloader application class.
-
+
Contains functionality for main program window, and directs all other
processes.
"""
-
+
def __init__(self, bus, path, name, taskserver=None, focal_length=None,
- auto_detect=None, device_location=None):
-
+ auto_detect=None, device_location=None):
+
dbus.service.Object.__init__ (self, bus, path, name)
self.running = False
-
+
self.taskserver = taskserver
-
+
self.focal_length = focal_length
-
+
# Setup program preferences, and set callback for when they change
self._init_prefs(auto_detect, device_location)
-
+
# Initialize widgets in the main window, and variables that point to them
self._init_widgets()
- self._init_pynotify()
-
+
+ if use_pynotify:
+ self._init_pynotify()
+
# Initialize job code handling
self._init_job_code()
-
+
# Remember the window size from the last time the program was run, or
# set a default size
self._set_window_size()
-
+
# Setup various widgets
self._setup_buttons()
self._setup_error_icons()
self._setup_icons()
-
+
# Show the main window
self.rapidapp.show()
-
+
# Check program preferences - don't allow auto start if there is a problem
prefs_valid, msg = prefsrapid.check_prefs_for_validity(self.prefs)
if not prefs_valid:
self.notify_prefs_are_invalid(details=msg)
-
+
# Initialize variables with which to track important downloads results
self._init_download_tracking()
-
+
# Set up process managers.
# A task such as scanning a device or copying files is handled in its
# own process.
self._start_process_managers()
-
+
# Setup devices from which to download from and backup to
- self.setup_devices(on_startup=True, on_preference_change=False,
+ self.setup_devices(on_startup=True, on_preference_change=False,
block_auto_start=not prefs_valid)
-
+
# Ensure the device collection scrolled window is not too small
self._set_device_collection_size()
-
+
def on_rapidapp_destroy(self, widget, data=None):
self._terminate_processes(terminate_file_copies = True)
@@ -1592,30 +1600,30 @@ class RapidApp(dbus.service.Object):
x, y, width, height = self.rapidapp.get_allocation()
self.prefs.main_window_size_x = width
self.prefs.main_window_size_y = height
-
+
self.prefs.set_downloads_today_from_tracker(self.downloads_today_tracker)
-
+
gtk.main_quit()
-
+
def _terminate_processes(self, terminate_file_copies=False):
-
+
if terminate_file_copies:
logger.info("Terminating all processes...")
- scan_termination_requested = self.scan_manager.request_termination()
+ scan_termination_requested = self.scan_manager.request_termination()
thumbnails_termination_requested = self.thumbnails.thumbnail_manager.request_termination()
backup_termination_requested = self.backup_manager.request_termination()
file_modify_termination_requested = self.file_modify_manager.request_termination()
-
+
if terminate_file_copies:
copy_files_termination_requested = self.copy_files_manager.request_termination()
else:
copy_files_termination_requested = False
-
+
if (scan_termination_requested or thumbnails_termination_requested or
backup_termination_requested or file_modify_termination_requested):
time.sleep(1)
- if (self.scan_manager.get_no_active_processes() > 0 or
+ if (self.scan_manager.get_no_active_processes() > 0 or
self.thumbnails.thumbnail_manager.get_no_active_processes() > 0 or
self.backup_manager.get_no_active_processes() > 0 or
self.file_modify_manager.get_no_active_processes() > 0):
@@ -1626,14 +1634,14 @@ class RapidApp(dbus.service.Object):
self.scan_manager.terminate_forcefully()
self.backup_manager.terminate_forcefully()
self.file_modify_manager.terminate_forcefully()
-
+
if terminate_file_copies and copy_files_termination_requested:
time.sleep(1)
self.copy_files_manager.terminate_forcefully()
-
+
if terminate_file_copies:
self._clean_all_temp_dirs()
-
+
# # #
# Events and tasks related to displaying preview images and thumbnails
# # #
@@ -1642,61 +1650,61 @@ class RapidApp(dbus.service.Object):
value = checkbutton.get_active()
self.thumbnails.set_selected(self.preview_image.unique_id, value)
self.set_download_action_sensitivity()
-
+
def on_preview_eventbox_button_press_event(self, widget, event):
-
+
if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
- self.show_thumbnails()
-
+ self.show_thumbnails()
+
def on_show_thumbnails_action_activate(self, action):
logger.debug("on_show_thumbnails_action_activate")
self.show_thumbnails()
-
+
def on_show_image_action_activate(self, action):
logger.debug("on_show_image_action_activate")
self.thumbnails.show_preview()
-
+
def on_check_all_action_activate(self, action):
self.thumbnails.check_all(check_all=True)
-
+
def on_uncheck_all_action_activate(self, action):
self.thumbnails.check_all(check_all=False)
def on_check_all_photos_action_activate(self, action):
- self.thumbnails.check_all(check_all=True,
+ self.thumbnails.check_all(check_all=True,
file_type=rpdfile.FILE_TYPE_PHOTO)
-
+
def on_check_all_videos_action_activate(self, action):
- self.thumbnails.check_all(check_all=True,
+ self.thumbnails.check_all(check_all=True,
file_type=rpdfile.FILE_TYPE_VIDEO)
-
+
def on_quit_action_activate(self, action):
self.on_rapidapp_destroy(widget=self.rapidapp, data=None)
-
+
def on_refresh_action_activate(self, action):
self.thumbnails.clear_all()
self.setup_devices(on_startup=False, on_preference_change=False,
block_auto_start=True)
-
+
def on_get_help_action_activate(self, action):
webbrowser.open("http://www.damonlynch.net/rapid/help.html")
-
+
def on_about_action_activate(self, action):
self.about.set_property("name", PROGRAM_NAME)
self.about.set_property("version", utilities.human_readable_version(
__version__))
self.about.run()
self.about.hide()
-
+
def on_report_problem_action_activate(self, action):
webbrowser.open("https://bugs.launchpad.net/rapid")
-
+
def on_translate_action_activate(self, action):
webbrowser.open("http://www.damonlynch.net/rapid/translate.html")
-
+
def on_donate_action_activate(self, action):
webbrowser.open("http://www.damonlynch.net/rapid/donate.html")
-
+
def show_preview_image(self, unique_id, image, include_checkbutton_visible, checked):
if self.main_notebook.get_current_page() == 0: # thumbnails
logger.debug("Switching to preview image display")
@@ -1704,26 +1712,26 @@ class RapidApp(dbus.service.Object):
self.preview_image.set_preview_image(unique_id, image, include_checkbutton_visible, checked)
self.next_image_action.set_sensitive(True)
self.prev_image_action.set_sensitive(True)
-
+
def update_preview_image(self, unique_id, image):
self.preview_image.update_preview_image(unique_id, image)
-
+
def show_thumbnails(self):
logger.debug("Switching to thumbnails display")
self.main_notebook.set_current_page(0)
self.thumbnails.select_image(self.preview_image.unique_id)
self.next_image_action.set_sensitive(False)
self.prev_image_action.set_sensitive(False)
-
-
+
+
def on_next_image_action_activate(self, action):
if self.preview_image.unique_id is not None:
self.thumbnails.show_next_image(self.preview_image.unique_id)
-
+
def on_prev_image_action_activate(self, action):
- if self.preview_image.unique_id is not None:
+ if self.preview_image.unique_id is not None:
self.thumbnails.show_prev_image(self.preview_image.unique_id)
-
+
def display_scan_thumbnails(self):
"""
If all the scans are complete, sets the sort order and displays
@@ -1737,35 +1745,36 @@ class RapidApp(dbus.service.Object):
# # #
# Volume management
# # #
-
+
def start_volume_monitor(self):
if not self.vmonitor:
self.vmonitor = gio.volume_monitor_get()
self.vmonitor.connect("mount-added", self.on_mount_added)
- self.vmonitor.connect("mount-removed", self.on_mount_removed)
-
-
+ self.vmonitor.connect("mount-removed", self.on_mount_removed)
+
+
def _backup_device_name(self, path):
if self.backup_devices[path][0] is None:
name = path
else:
name = self.backup_devices[path][0].get_name()
return name
-
+
def start_device_scan(self, device):
"""
- Commences the scanning of a device using the preference values for
+ Commences the scanning of a device using the preference values for
any paths to ignore while scanning
"""
- return self.scan_manager.add_task([device,
+ logger.debug("Starting a device scan for device %s", device.get_name())
+ return self.scan_manager.add_task([device,
self.prefs.ignored_paths,
self.prefs.use_re_ignored_paths])
-
+
def confirm_manual_location(self):
"""
- Queries the user to ask if they really want to download from locations
+ Queries the user to ask if they really want to download from locations
that could take a very long time to scan. They can choose yes or no.
-
+
Returns True if yes or there was no need to ask the user, False if the
user said no.
"""
@@ -1781,34 +1790,34 @@ class RapidApp(dbus.service.Object):
question="<b>" + _("Downloading from %(location)s.") % {'location': l} + "</b>\n\n" +
_("Do you really want to download from here? On some systems, scanning this location can take a very long time."),
default_to_yes=False,
- use_markup=True)
+ use_markup=True)
response = c.run()
user_confirmed = response == gtk.RESPONSE_OK
c.destroy()
if not user_confirmed:
return False
return True
-
+
def setup_devices(self, on_startup, on_preference_change, block_auto_start):
"""
-
+
Setup devices from which to download from and backup to
-
+
Sets up volumes for downloading from and backing up to
-
- on_startup should be True if the program is still starting,
+
+ on_startup should be True if the program is still starting,
i.e. this is being called from the program's initialization.
-
+
on_preference_change should be True if this is being called as the
result of a preference being changed
-
+
block_auto_start should be True if automation options to automatically
start a download should be ignored
-
- Removes any image media that are currently not downloaded,
- or finished downloading
+
+ Removes any image media that are currently not downloaded,
+ or finished downloading
"""
-
+
if self.using_volume_monitor():
self.start_volume_monitor()
@@ -1816,10 +1825,10 @@ class RapidApp(dbus.service.Object):
if not self.prefs.device_autodetection:
if not self.confirm_manual_location():
return
-
+
mounts = []
self.backup_devices = {}
-
+
if self.using_volume_monitor():
# either using automatically detected backup devices
# or download devices
@@ -1827,7 +1836,7 @@ class RapidApp(dbus.service.Object):
if not mount.is_shadowed():
path = mount.get_root().get_path()
if path:
- if (path in self.prefs.device_blacklist and
+ if (path in self.prefs.device_blacklist and
self.search_for_PSD()):
logger.info("%s ignored", mount.get_name())
else:
@@ -1835,17 +1844,17 @@ class RapidApp(dbus.service.Object):
is_backup_mount, backup_file_type = self.check_if_backup_mount(path)
if is_backup_mount:
self.backup_devices[path] = (mount, backup_file_type)
- elif (self.prefs.device_autodetection and
- (dv.is_DCIM_device(path) or
+ elif (self.prefs.device_autodetection and
+ (dv.is_DCIM_device(path) or
self.search_for_PSD())):
logger.debug("Appending %s", mount.get_name())
mounts.append((path, mount))
else:
logger.debug("Ignoring %s", mount.get_name())
-
-
+
+
if not self.prefs.device_autodetection:
- # user manually specified the path from which to download
+ # user manually specified the path from which to download
path = self.prefs.device_location
if path:
logger.info("Using manually specified path %s", path)
@@ -1858,35 +1867,47 @@ class RapidApp(dbus.service.Object):
if not self.prefs.backup_device_autodetection:
self._setup_manual_backup()
self._add_backup_devices()
-
+
self.update_no_backup_devices()
-
+
# Display amount of free space in a status bar message
self.display_free_space()
-
+
if block_auto_start:
self.auto_start_is_on = False
else:
self.auto_start_is_on = ((not on_preference_change) and
- ((self.prefs.auto_download_at_startup and
- on_startup) or
+ ((self.prefs.auto_download_at_startup and
+ on_startup) or
(self.prefs.auto_download_upon_device_insertion and
not on_startup)))
-
+
+ logger.debug("Working with %s devices", len(mounts))
for m in mounts:
path, mount = m
device = dv.Device(path=path, mount=mount)
- if (self.search_for_PSD() and
- path not in self.prefs.device_whitelist):
- # prompt user to see if device should be used or not
- self.get_use_device(device)
- else:
- scan_pid = self.start_device_scan(device)
- if mount is not None:
- self.mounts_by_path[path] = scan_pid
+
+
+ if not self._device_already_detected(device):
+ if (self.search_for_PSD() and
+ path not in self.prefs.device_whitelist):
+ # prompt user to see if device should be used or not
+ self.get_use_device(device)
+ else:
+ scan_pid = self.start_device_scan(device)
+ if mount is not None:
+ self.mounts_by_path[path] = scan_pid
if not mounts:
self.set_download_action_sensitivity()
-
+
+ def _device_already_detected(self, device):
+ path = device.get_path()
+ if path in self.mounts_by_path:
+ logger.debug("Ignoring device %s as already have path %s", device.get_name(), path)
+ return True
+ else:
+ return False
+
def _setup_manual_backup(self):
"""
Setup backup devices that the user has manually specified.
@@ -1894,8 +1915,8 @@ class RapidApp(dbus.service.Object):
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
+ # 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:
@@ -1908,7 +1929,7 @@ class RapidApp(dbus.service.Object):
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
@@ -1917,20 +1938,28 @@ class RapidApp(dbus.service.Object):
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):
+
+
+ def get_use_device(self, device):
""" Prompt user whether or not to download from this device """
-
+
logger.info("Prompting whether to use %s", device.get_name())
+
+ # On some systems, e.g. Ubuntu 12.10, the GTK/Gnome environment
+ # unexpectedly results in a device being added twice and not once.
+ # The hack on the next line ensures the user is not prompted twice
+ # for the same device.
+
+ self.mounts_by_path[device.get_path()] = "PROMPTING"
+
d = dv.UseDeviceDialog(self.rapidapp, device, self.got_use_device)
-
+
def got_use_device(self, dialog, user_selected, permanent_choice, device):
""" User has chosen whether or not to use a device to download from """
dialog.destroy()
-
+
path = device.get_path()
-
+
if user_selected:
if permanent_choice and path not in self.prefs.device_whitelist:
# do NOT do a list append operation here without the assignment,
@@ -1941,37 +1970,37 @@ class RapidApp(dbus.service.Object):
self.prefs.device_whitelist = [path]
scan_pid = self.start_device_scan(device)
self.mounts_by_path[path] = scan_pid
-
+
elif permanent_choice and path not in self.prefs.device_blacklist:
# do not do a list append operation here without the assignment, or the preferences will not be updated!
if len(self.prefs.device_blacklist):
self.prefs.device_blacklist = self.prefs.device_blacklist + [path]
else:
- self.prefs.device_blacklist = [path]
-
+ self.prefs.device_blacklist = [path]
+
def search_for_PSD(self):
"""
- Check to see if user preferences are to automatically search for
+ Check to see if user preferences are to automatically search for
Portable Storage Devices or not
"""
return self.prefs.device_autodetection_psd and self.prefs.device_autodetection
def check_if_backup_mount(self, path):
"""
- Checks to see if backups are enabled and path represents a valid backup
+ 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)
+ (False, None)
"""
if self.prefs.backup_images:
if self.prefs.backup_device_autodetection:
- # Determine if the auto-detected backup device is
+ # 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
+ # 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)
@@ -1991,7 +2020,7 @@ class RapidApp(dbus.service.Object):
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
+ # 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:
@@ -2013,22 +2042,22 @@ class RapidApp(dbus.service.Object):
#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",
+ 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.download_tracker.set_no_backup_devices(self.no_photo_backup_devices,
self.no_video_backup_devices)
def refresh_backup_media(self):
"""
Setup the backup media
-
- Assumptions: this is being called after the user has changed their
+
+ Assumptions: this is being called after the user has changed their
preferences AND download media has already been setup
"""
-
+
# terminate any running backup processes
self.backup_manager.request_termination()
-
+
self.backup_devices = {}
if self.prefs.backup_images:
if not self.prefs.backup_device_autodetection:
@@ -2043,22 +2072,22 @@ class RapidApp(dbus.service.Object):
# is a backup volume
if path not in self.backup_devices:
self.backup_devices[path] = (mount, backup_file_type)
-
+
self._add_backup_devices()
self.update_no_backup_devices()
self.display_free_space()
-
+
def using_volume_monitor(self):
"""
Returns True if programs needs to use gio volume monitor
"""
-
- return (self.prefs.device_autodetection or
- (self.prefs.backup_images and
+
+ return (self.prefs.device_autodetection or
+ (self.prefs.backup_images and
self.prefs.backup_device_autodetection
))
-
+
def on_mount_added(self, vmonitor, mount):
"""
callback run when gio indicates a new volume
@@ -2069,7 +2098,7 @@ class RapidApp(dbus.service.Object):
if mount.is_shadowed():
# ignore this type of mount
return
-
+
path = mount.get_root().get_path()
if path is not None:
@@ -2078,7 +2107,7 @@ class RapidApp(dbus.service.Object):
'device': mount.get_name(), 'path': path})
else:
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
@@ -2087,84 +2116,86 @@ class RapidApp(dbus.service.Object):
self.update_no_backup_devices()
self.display_free_space()
- elif self.prefs.device_autodetection and (dv.is_DCIM_device(path) or
+ elif self.prefs.device_autodetection and (dv.is_DCIM_device(path) or
self.search_for_PSD()):
-
+
self.auto_start_is_on = self.prefs.auto_download_upon_device_insertion
device = dv.Device(path=path, mount=mount)
- if self.search_for_PSD() and path not in self.prefs.device_whitelist:
- # prompt user if device should be used or not
- self.get_use_device(device)
- else:
- scan_pid = self.start_device_scan(device)
- self.mounts_by_path[path] = scan_pid
-
+
+ if not self._device_already_detected(device):
+ if self.search_for_PSD() and path not in self.prefs.device_whitelist:
+ # prompt user if device should be used or not
+ self.get_use_device(device)
+ else:
+ scan_pid = self.start_device_scan(device)
+ self.mounts_by_path[path] = scan_pid
+
def on_mount_removed(self, vmonitor, mount):
"""
callback run when gio indicates a new volume
has been mounted
"""
-
+
path = mount.get_root().get_path()
# three scenarios -
# the mount has been scanned but downloading has not yet started
# files are being downloaded from mount (it must be a messy unmount)
# files have finished downloading from mount
-
+
if path in self.mounts_by_path:
scan_pid = self.mounts_by_path[path]
del self.mounts_by_path[path]
# temp directory should be cleaned by finishing of process
-
- self.thumbnails.clear_all(scan_pid = scan_pid,
+
+ self.thumbnails.clear_all(scan_pid = scan_pid,
keep_downloaded_files = True)
self.device_collection.remove_device(scan_pid)
-
-
-
+
+
+
# remove backup volumes
elif path in self.backup_devices:
del self.backup_devices[path]
self.display_free_space()
self.backup_manager.remove_device(path)
self.update_no_backup_devices()
-
+
# may need to disable download button and menu
self.set_download_action_sensitivity()
-
+
def clear_non_running_downloads(self):
"""
Clears the display of downloads that are currently not running
"""
-
+
# Stop any processes currently scanning or creating thumbnails
self._terminate_processes(terminate_file_copies=False)
-
+
# Remove them from the user interface
for scan_pid in self.device_collection.get_all_displayed_processes():
if scan_pid not in self.download_active_by_scan_pid:
self.device_collection.remove_device(scan_pid)
self.thumbnails.clear_all(scan_pid=scan_pid)
-
-
-
+
+
+
# # #
# Download and help buttons, and menu items
# # #
-
+
def on_download_action_activate(self, action):
"""
Called when a download is activated
"""
-
+
if self.copy_files_manager.paused:
logger.debug("Download resumed")
self.resume_download()
else:
logger.debug("Download activated")
-
+
if self.download_action_is_download:
if self.need_job_code_for_naming and not self.prompting_for_job_code:
self.get_job_code()
@@ -2173,18 +2204,18 @@ class RapidApp(dbus.service.Object):
else:
self.pause_download()
-
+
def on_help_action_activate(self, action):
webbrowser.open("http://www.damonlynch.net/rapid/documentation")
-
+
def on_preferences_action_activate(self, action):
preferencesdialog.PreferencesDialog(self)
-
+
def set_download_action_sensitivity(self):
"""
Sets sensitivity of Download action to enable or disable it
-
+
Affects download button and menu item
"""
if not self.download_is_occurring():
@@ -2192,26 +2223,26 @@ class RapidApp(dbus.service.Object):
if self.scan_manager.no_tasks == 0:
if self.thumbnails.files_are_checked_to_download():
sensitivity = True
-
+
self.download_action.set_sensitive(sensitivity)
-
+
def set_download_action_label(self, is_download):
"""
- Toggles label betwen pause and download
+ Toggles label betwen pause and download
"""
-
+
if is_download:
self.download_action.set_label(_("Download"))
self.download_action_is_download = True
else:
self.download_action.set_label(_("Pause"))
self.download_action_is_download = False
-
+
# # #
# Job codes
# # #
-
-
+
+
def _init_job_code(self):
self.job_code = self.last_chosen_job_code = ''
self.need_job_code_for_naming = self.prefs.any_pref_uses_job_code()
@@ -2219,64 +2250,64 @@ class RapidApp(dbus.service.Object):
def assign_job_code(self, code):
""" assign job code (which may be empty) to member variable and update user preferences
-
+
Update preferences only if code is not empty. Do not duplicate job code.
"""
self.job_code = code
-
+
if code:
#add this value to job codes preferences
#delete any existing value which is the same
#(this way it comes to the front, which is where it should be)
#never modify self.prefs.job_codes in place! (or prefs become screwed up)
-
+
jcs = self.prefs.job_codes
while code in jcs:
jcs.remove(code)
-
+
self.prefs.job_codes = [code] + jcs
def _get_job_code(self, post_job_code_entry_callback):
""" prompt for a job code """
-
+
if not self.prompting_for_job_code:
logger.debug("Prompting for Job Code")
self.prompting_for_job_code = True
j = preferencesdialog.JobCodeDialog(parent_window = self.rapidapp,
job_codes = self.prefs.job_codes,
- default_job_code = self.last_chosen_job_code,
+ default_job_code = self.last_chosen_job_code,
post_job_code_entry_callback=post_job_code_entry_callback,
entry_only = False)
else:
logger.debug("Already prompting for Job Code, do not prompt again")
-
+
def get_job_code(self):
self._get_job_code(self.got_job_code)
-
+
def got_job_code(self, dialog, user_chose_code, code):
dialog.destroy()
self.prompting_for_job_code = False
-
+
if user_chose_code:
if code is None:
code = ''
self.assign_job_code(code)
self.last_chosen_job_code = code
logger.debug("Job Code %s entered", self.job_code)
- self.start_download()
-
+ self.start_download()
+
else:
# user cancelled
logger.debug("No Job Code entered")
self.job_code = ''
self.auto_start_is_on = False
-
-
+
+
# # #
# Download
# # #
-
+
def _init_download_tracking(self):
"""
Initialize variables to track downloads
@@ -2285,28 +2316,28 @@ class RapidApp(dbus.service.Object):
# (Scan id acts as an index to each device. A device could be scanned
# more than once).
self.download_tracker = downloadtracker.DownloadTracker()
-
+
# Track which temporary directories are created when downloading files
self.temp_dirs_by_scan_pid = dict()
-
+
# Track which downloads are running
self.download_active_by_scan_pid = []
-
+
def modify_files_during_download(self):
""" Returns True if there is a need to modify files during download"""
return self.prefs.auto_rotate_jpeg or (self.focal_length is not None)
-
+
def start_download(self, scan_pid=None):
"""
Start download, renaming and backup of files.
-
+
If scan_pid is specified, only files matching it will be downloaded
"""
-
+
files_by_scan_pid = self.thumbnails.get_files_checked_for_download(scan_pid)
folders_valid, invalid_dirs = self.check_download_folder_validity(files_by_scan_pid)
-
+
if not folders_valid:
if len(invalid_dirs) > 1:
msg = _("These download folders are invalid:\n%(folder1)s\n%(folder2)s") % {
@@ -2319,40 +2350,40 @@ class RapidApp(dbus.service.Object):
# set time download is starting if it is not already set
# it is unset when all downloads are completed
if self.download_start_time is None:
- self.download_start_time = datetime.datetime.now()
+ self.download_start_time = datetime.datetime.now()
- # Set status to download pending
+ # Set status to download pending
self.thumbnails.mark_download_pending(files_by_scan_pid)
-
+
# disable refresh and preferences change while download is occurring
self.enable_prefs_and_refresh(enabled=False)
-
+
for scan_pid in files_by_scan_pid:
files = files_by_scan_pid[scan_pid]
# if generating thumbnails for this scan_pid, stop it
if self.thumbnails.terminate_thumbnail_generation(scan_pid):
self.thumbnails.mark_thumbnails_needed(files)
-
+
self.download_files(files, scan_pid)
-
+
self.set_download_action_label(is_download = False)
-
+
def pause_download(self):
-
+
self.copy_files_manager.pause()
-
+
# set action to display Download
if not self.download_action_is_download:
self.set_download_action_label(is_download = True)
-
+
self.time_check.pause()
-
+
def resume_download(self):
for scan_pid in self.download_active_by_scan_pid:
self.time_remaining.set_time_mark(scan_pid)
-
+
self.time_check.set_download_mark()
-
+
self.copy_files_manager.start()
def download_files(self, files, scan_pid):
@@ -2360,16 +2391,16 @@ class RapidApp(dbus.service.Object):
Initiate downloading and renaming of files
"""
# Check which file types will be downloaded for this particular process
- no_photos_to_download = 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 DOWNLOAD_VIDEO:
- no_videos_to_download = self.files_of_type_present(files,
+ no_videos_to_download = self.files_of_type_present(files,
rpdfile.FILE_TYPE_VIDEO,
return_file_count=True)
if no_videos_to_download:
@@ -2379,30 +2410,30 @@ class RapidApp(dbus.service.Object):
else:
video_download_folder = None
no_videos_to_download = 0
-
+
photo_download_size, video_download_size = self.size_files_to_be_downloaded(files)
- self.download_tracker.init_stats(scan_pid=scan_pid,
- photo_size_in_bytes=photo_download_size,
+ self.download_tracker.init_stats(scan_pid=scan_pid,
+ 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 + ((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()
-
+
self.download_active_by_scan_pid.append(scan_pid)
-
-
+
+
if len(self.download_active_by_scan_pid) > 1:
self.display_summary_notification = True
-
+
if self.auto_start_is_on and self.prefs.generate_thumbnails:
for rpd_file in files:
rpd_file.generate_thumbnail = True
@@ -2414,13 +2445,13 @@ class RapidApp(dbus.service.Object):
else:
modify_pipe = None
-
+
# Initiate copy files process
- self.copy_files_manager.add_task((photo_download_folder,
+ self.copy_files_manager.add_task((photo_download_folder,
video_download_folder, scan_pid,
files, modify_files_during_download,
modify_pipe))
-
+
def copy_files_results(self, source, condition):
"""
Handle results from copy files process
@@ -2434,7 +2465,7 @@ 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)
-
+
# Report which temporary directories are being used for this
# download
if photo_temp_dir and video_temp_dir:
@@ -2445,10 +2476,10 @@ class RapidApp(dbus.service.Object):
photo_temp_dir)
else:
logger.debug("Using temp dir %s (videos)",
- video_temp_dir)
+ 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,
+ self.download_tracker.set_total_bytes_copied(scan_pid,
total_downloaded)
self.time_check.increment(bytes_downloaded=chunk_downloaded)
percent_complete = self.download_tracker.get_percent_complete(scan_pid)
@@ -2457,39 +2488,39 @@ class RapidApp(dbus.service.Object):
self.time_remaining.update(scan_pid, bytes_downloaded=chunk_downloaded)
elif msg_type == rpdmp.MSG_FILE:
self.copy_file_results_single_file(data)
-
+
return True
else:
# Process is complete, i.e. conn_type == rpdmp.CONN_COMPLETE
connection.close()
return False
-
+
def copy_file_results_single_file(self, data):
"""
Handles results from one of two processes:
1. copy_files
2. file_modify
-
+
Operates after a single file has been copied from the download device
to the local folder.
-
+
Calls the process to rename files and create subfolders (subfolderfile)
"""
-
+
download_succeeded, rpd_file, download_count, temp_full_file_name, thumbnail_icon, thumbnail = data
-
+
if thumbnail is not None or thumbnail_icon is not None:
- self.thumbnails.update_thumbnail((rpd_file.unique_id,
- thumbnail_icon,
+ self.thumbnails.update_thumbnail((rpd_file.unique_id,
+ thumbnail_icon,
thumbnail))
-
+
self.download_tracker.set_download_count_for_file(
rpd_file.unique_id, download_count)
self.download_tracker.set_download_count(
rpd_file.scan_pid, download_count)
rpd_file.download_start_time = self.download_start_time
-
+
if download_succeeded:
# Insert preference values needed for name generation
rpd_file = prefsrapid.insert_pref_lists(self.prefs, rpd_file)
@@ -2498,21 +2529,21 @@ class RapidApp(dbus.service.Object):
rpd_file.download_conflict_resolution = self.prefs.download_conflict_resolution
rpd_file.synchronize_raw_jpg = self.prefs.must_synchronize_raw_jpg()
rpd_file.job_code = self.job_code
-
+
self.subfolder_file_manager.rename_file_and_move_to_subfolder(
- download_succeeded,
- download_count,
+ download_succeeded,
+ download_count,
rpd_file
- )
+ )
def file_modify_results(self, source, condition):
"""
- 'file modify' is a process that runs immediately after 'copy files',
- meaning there can be more than one at one time.
-
+ 'file modify' is a process that runs immediately after 'copy files',
+ meaning there can be more than one at one time.
+
It runs before the renaming process.
"""
connection = self.file_modify_manager.get_pipe(source)
-
+
conn_type, data = connection.recv()
if conn_type == rpdmp.CONN_PARTIAL:
self.copy_file_results_single_file(data)
@@ -2520,31 +2551,31 @@ class RapidApp(dbus.service.Object):
else:
# Process is complete, i.e. conn_type == rpdmp.CONN_COMPLETE
connection.close()
- return False
+ return False
+
-
def download_is_occurring(self):
- """Returns True if a file is currently being downloaded, renamed or
+ """Returns True if a file is currently being downloaded, renamed or
backed up
"""
return not len(self.download_active_by_scan_pid) == 0
-
+
# # #
# Create folder and file names for downloaded files
# # #
-
- def subfolder_file_results(self, move_succeeded, rpd_file):
+
+ def subfolder_file_results(self, move_succeeded, rpd_file, download_count):
"""
Handle results of subfolder creation and file renaming
"""
-
+
scan_pid = rpd_file.scan_pid
unique_id = rpd_file.unique_id
-
+
if rpd_file.status == config.STATUS_DOWNLOADED_WITH_WARNING:
- self.log_error(config.WARNING, rpd_file.error_title,
+ self.log_error(config.WARNING, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
-
+
if self.prefs.backup_images and len(self.backup_devices):
if self.prefs.backup_device_autodetection:
if rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO:
@@ -2553,23 +2584,24 @@ class RapidApp(dbus.service.Object):
path_suffix = self.prefs.video_backup_identifier
else:
path_suffix = None
-
- self.backup_manager.backup_file(move_succeeded, rpd_file,
+
+ self.backup_manager.backup_file(move_succeeded, rpd_file,
path_suffix,
- self.prefs.backup_duplicate_overwrite)
+ self.prefs.backup_duplicate_overwrite,
+ download_count)
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
+ return ((file_type == rpdfile.FILE_TYPE_PHOTO and
self.no_photo_backup_devices > 1) or
- (file_type == rpdfile.FILE_TYPE_VIDEO and
+ (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
@@ -2578,30 +2610,30 @@ class RapidApp(dbus.service.Object):
conn_type, msg_data = connection.recv()
if conn_type == rpdmp.CONN_PARTIAL:
msg_type, data = msg_data
-
+
if msg_type == rpdmp.MSG_BYTES:
scan_pid, backup_pid, total_downloaded, chunk_downloaded = data
- self.download_tracker.increment_bytes_backed_up(scan_pid,
+ self.download_tracker.increment_bytes_backed_up(scan_pid,
chunk_downloaded)
self.time_check.increment(bytes_downloaded=chunk_downloaded)
percent_complete = self.download_tracker.get_percent_complete(scan_pid)
self.device_collection.update_progress(scan_pid, percent_complete,
None, None)
self.time_remaining.update(scan_pid, bytes_downloaded=chunk_downloaded)
-
+
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
+ # 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,
+ 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,
rpd_file.file_type):
@@ -2610,7 +2642,7 @@ class RapidApp(dbus.service.Object):
else:
return False
-
+
def file_download_finished(self, succeeded, rpd_file):
"""
Called when a file has been downloaded i.e. copied, renamed, and backed up
@@ -2619,23 +2651,23 @@ class RapidApp(dbus.service.Object):
unique_id = rpd_file.unique_id
# Update error log window if neccessary
if not succeeded and not self.multiple_backup_devices(rpd_file.file_type):
- self.log_error(config.SERIOUS_ERROR, rpd_file.error_title,
+ self.log_error(config.SERIOUS_ERROR, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
elif self.prefs.auto_delete:
- # record which files to automatically delete when download
+ # record which files to automatically delete when download
# completes
self.download_tracker.add_to_auto_delete(rpd_file)
-
+
self.thumbnails.update_status_post_download(rpd_file)
- self.download_tracker.file_downloaded_increment(scan_pid,
+ self.download_tracker.file_downloaded_increment(scan_pid,
rpd_file.file_type,
rpd_file.status)
-
+
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()
-
+
if completed:
# Last file for this scan pid has been downloaded, so clean temp directory
logger.debug("Purging temp directories")
@@ -2649,63 +2681,63 @@ class RapidApp(dbus.service.Object):
self.notify_downloaded_from_device(scan_pid)
if files_remaining == 0 and self.prefs.auto_unmount:
self.device_collection.unmount(scan_pid)
-
-
+
+
if not self.download_is_occurring():
logger.debug("Download completed")
self.enable_prefs_and_refresh(enabled=True)
self.notify_download_complete()
self.download_progressbar.set_fraction(0.0)
-
+
self.prefs.stored_sequence_no = self.stored_sequence_value.value
self.downloads_today_tracker.set_raw_downloads_today_from_int(self.downloads_today_value.value)
self.downloads_today_tracker.set_raw_downloads_today_date(self.downloads_today_date_value.value)
self.prefs.set_downloads_today_from_tracker(self.downloads_today_tracker)
- if ((self.prefs.auto_exit and self.download_tracker.no_errors_or_warnings())
+ if ((self.prefs.auto_exit and self.download_tracker.no_errors_or_warnings())
or self.prefs.auto_exit_force):
if not self.thumbnails.files_remain_to_download():
self._terminate_processes()
gtk.main_quit()
-
+
self.download_tracker.purge_all()
self.speed_label.set_label(" ")
-
+
self.display_free_space()
-
+
self.set_download_action_label(is_download=True)
self.set_download_action_sensitivity()
-
+
self.job_code = ''
self.download_start_time = None
-
-
+
+
def update_time_remaining(self):
update, download_speed = self.time_check.check_for_update()
if update:
self.speed_label.set_text(download_speed)
-
+
time_remaining = self.time_remaining.time_remaining()
if time_remaining:
secs = int(time_remaining)
-
+
if secs == 0:
message = ""
elif secs == 1:
message = _("About 1 second remaining")
elif secs < 60:
- message = _("About %i seconds remaining") % secs
+ message = _("About %i seconds remaining") % secs
elif secs == 60:
message = _("About 1 minute remaining")
else:
- # Translators: in the text '%(minutes)i:%(seconds)02i', only the : should be translated, if needed.
+ # Translators: in the text '%(minutes)i:%(seconds)02i', only the : should be translated, if needed.
# '%(minutes)i' and '%(seconds)02i' should not be modified or left out. They are used to format and display the amount
# of time the download has remainging, e.g. 'About 5:36 minutes remaining'
message = _("About %(minutes)i:%(seconds)02i minutes remaining") % {'minutes': secs / 60, 'seconds': secs % 60}
-
+
self.rapid_statusbar.pop(self.statusbar_context_id)
- self.rapid_statusbar.push(self.statusbar_context_id, message)
-
+ self.rapid_statusbar.push(self.statusbar_context_id, message)
+
def auto_delete(self, scan_pid):
"""Delete files from download device at completion of download"""
for file in self.download_tracker.get_files_to_auto_delete(scan_pid):
@@ -2714,10 +2746,10 @@ class RapidApp(dbus.service.Object):
f.delete(cancellable=None)
except gio.Error, inst:
logger.error("Failure deleting file %s", file)
- logger.error(inst)
-
+ logger.error(inst)
+
def file_types_by_number(self, no_photos, no_videos):
- """
+ """
returns a string to be displayed to the user that can be used
to show if a value refers to photos or videos or both, or just one
of each
@@ -2740,14 +2772,14 @@ class RapidApp(dbus.service.Object):
def notify_downloaded_from_device(self, scan_pid):
device = self.device_collection.get_device(scan_pid)
-
+
if device.mount is None:
notification_name = PROGRAM_NAME
icon = self.application_icon
else:
notification_name = device.get_name()
icon = device.get_icon(self.notification_icon_size)
-
+
no_photos_downloaded = self.download_tracker.get_no_files_downloaded(
scan_pid, rpdfile.FILE_TYPE_PHOTO)
no_videos_downloaded = self.download_tracker.get_no_files_downloaded(
@@ -2759,36 +2791,37 @@ class RapidApp(dbus.service.Object):
no_files_downloaded = no_photos_downloaded + no_videos_downloaded
no_files_failed = no_photos_failed + no_videos_failed
no_warnings = self.download_tracker.get_no_warnings(scan_pid)
-
+
file_types = self.file_types_by_number(no_photos_downloaded, no_videos_downloaded)
file_types_failed = self.file_types_by_number(no_photos_failed, no_videos_failed)
message = _("%(noFiles)s %(filetypes)s downloaded") % \
{'noFiles':no_files_downloaded, 'filetypes': file_types}
-
+
if no_files_failed:
message += "\n" + _("%(noFiles)s %(filetypes)s failed to download") % {'noFiles':no_files_failed, 'filetypes':file_types_failed}
-
+
if no_warnings:
- message = "%s\n%s " % (message, no_warnings) + _("warnings")
-
- n = pynotify.Notification(notification_name, message)
- n.set_icon_from_pixbuf(icon)
-
- n.show()
-
+ message = "%s\n%s " % (message, no_warnings) + _("warnings")
+
+ if use_pynotify:
+ n = pynotify.Notification(notification_name, message)
+ n.set_icon_from_pixbuf(icon)
+
+ n.show()
+
def notify_download_complete(self):
if self.display_summary_notification:
message = _("All downloads complete")
-
+
# photo downloads
photo_downloads = self.download_tracker.total_photos_downloaded
if photo_downloads:
filetype = self.file_types_by_number(photo_downloads, 0)
message += "\n" + _("%(number)s %(numberdownloaded)s") % \
- {'number': photo_downloads,
+ {'number': photo_downloads,
'numberdownloaded': _("%(filetype)s downloaded") % \
{'filetype': filetype}}
-
+
# photo failures
photo_failures = self.download_tracker.total_photo_failures
if photo_failures:
@@ -2797,16 +2830,16 @@ class RapidApp(dbus.service.Object):
{'number': photo_failures,
'numberdownloaded': _("%(filetype)s failed to download") % \
{'filetype': filetype}}
-
+
# video downloads
video_downloads = self.download_tracker.total_videos_downloaded
if video_downloads:
filetype = self.file_types_by_number(0, video_downloads)
message += "\n" + _("%(number)s %(numberdownloaded)s") % \
- {'number': video_downloads,
+ {'number': video_downloads,
'numberdownloaded': _("%(filetype)s downloaded") % \
{'filetype': filetype}}
-
+
# video failures
video_failures = self.download_tracker.total_video_failures
if video_failures:
@@ -2815,65 +2848,66 @@ class RapidApp(dbus.service.Object):
{'number': video_failures,
'numberdownloaded': _("%(filetype)s failed to download") % \
{'filetype': filetype}}
-
+
# warnings
- warnings = self.download_tracker.total_warnings
+ warnings = self.download_tracker.total_warnings
if warnings:
message += "\n" + _("%(number)s %(numberdownloaded)s") % \
- {'number': warnings,
+ {'number': warnings,
'numberdownloaded': _("warnings")}
-
- n = pynotify.Notification(PROGRAM_NAME, message)
- n.set_icon_from_pixbuf(self.application_icon)
- n.show()
+
+ if use_pynotify:
+ n = pynotify.Notification(PROGRAM_NAME, message)
+ n.set_icon_from_pixbuf(self.application_icon)
+ n.show()
self.display_summary_notification = False # don't show it again unless needed
-
-
+
+
def _update_file_download_device_progress(self, scan_pid, unique_id, file_type):
"""
Increments the progress bar for an individual device
-
+
Returns if the download is completed for that scan_pid
It also returns the number of files remaining for the scan_pid, BUT
this value is valid ONLY if the download is completed
"""
-
+
files_downloaded = self.download_tracker.get_download_count_for_file(unique_id)
files_to_download = self.download_tracker.get_no_files_in_download(scan_pid)
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, file_type)
-
+
if completed:
files_remaining = self.thumbnails.get_no_files_remaining(scan_pid)
else:
files_remaining = 0
-
+
if completed and files_remaining:
# e.g.: 3 of 205 photos and videos (202 remaining)
progress_bar_text = _("%(number)s of %(total)s %(filetypes)s (%(remaining)s remaining)") % {
- 'number': files_downloaded,
+ 'number': files_downloaded,
'total': files_to_download,
'filetypes': file_types,
'remaining': files_remaining}
else:
# e.g.: 205 of 205 photos and videos
progress_bar_text = _("%(number)s of %(total)s %(filetypes)s") % \
- {'number': files_downloaded,
+ {'number': files_downloaded,
'total': files_to_download,
'filetypes': file_types}
percent_complete = self.download_tracker.get_percent_complete(scan_pid)
self.device_collection.update_progress(scan_pid=scan_pid,
percent_complete=percent_complete,
- progress_bar_text=progress_bar_text,
+ progress_bar_text=progress_bar_text,
bytes_downloaded=None)
-
+
percent_complete = self.download_tracker.get_overall_percent_complete()
self.download_progressbar.set_fraction(percent_complete)
-
+
return (completed, files_remaining)
-
+
def _clean_all_temp_dirs(self):
"""
@@ -2882,10 +2916,10 @@ class RapidApp(dbus.service.Object):
for scan_pid in self.temp_dirs_by_scan_pid:
for temp_dir in self.temp_dirs_by_scan_pid[scan_pid]:
self._purge_dir(temp_dir)
-
+
self.temp_dirs_by_scan_pid = {}
-
-
+
+
def _clean_temp_dirs_for_scan_pid(self, scan_pid):
"""
Deletes temp files and folders used in download
@@ -2897,10 +2931,10 @@ class RapidApp(dbus.service.Object):
def _purge_dir(self, directory):
"""
Deletes all files in the directory, and the directory itself.
-
+
Does not recursively traverse any subfolders in the directory.
"""
-
+
if directory:
try:
path = gio.File(directory)
@@ -2916,16 +2950,16 @@ class RapidApp(dbus.service.Object):
except gio.Error, inst:
logger.error("Failure deleting temporary folder %s", directory)
logger.error(inst)
-
-
- # # #
+
+
+ # # #
# Preferences
# # #
-
-
- def _init_prefs(self, auto_detect, device_location):
+
+
+ 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:
@@ -2933,82 +2967,82 @@ class RapidApp(dbus.service.Object):
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
+
+ # flag to indicate whether the user changed some preferences that
# indicate the image and backup devices should be setup again
self.rerun_setup_available_image_and_video_media = False
self.rerun_setup_available_backup_media = False
-
- # flag to indicate that the preferences dialog window is being
+
+ # flag to indicate that the preferences dialog window is being
# displayed to the user
self.preferences_dialog_displayed = False
# flag to indicate that the user has modified the download today
# related values in the preferences dialog window
self.refresh_downloads_today = False
-
- # these values are used to track the number of backup devices /
+
+ # 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()
if downloads_today > 0:
logger.info("Downloads that have occurred so far today: %s", downloads_today)
else:
- logger.info("No downloads have occurred so far today")
+ logger.info("No downloads have occurred so far today")
- self.downloads_today_value = Value(c_int,
+ self.downloads_today_value = Value(c_int,
self.downloads_today_tracker.get_raw_downloads_today())
self.downloads_today_date_value = Array(c_char,
self.downloads_today_tracker.get_raw_downloads_today_date())
- self.day_start_value = Array(c_char,
+ self.day_start_value = Array(c_char,
self.downloads_today_tracker.get_raw_day_start())
self.refresh_downloads_today_value = Value(c_bool, False)
self.stored_sequence_value = Value(c_int, self.prefs.stored_sequence_no)
self.uses_stored_sequence_no_value = Value(c_bool, self.prefs.any_pref_uses_stored_sequence_no())
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):
self.uses_stored_sequence_no_value.value = self.prefs.any_pref_uses_stored_sequence_no()
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()
-
+ 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
+ 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
+ # 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",
+ 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
@@ -3017,90 +3051,89 @@ class RapidApp(dbus.service.Object):
if key == 'show_log_dialog':
self.menu_log_window.set_active(value)
- elif key in ['device_autodetection', 'device_autodetection_psd',
+ elif key in ['device_autodetection', 'device_autodetection_psd',
'device_location', 'ignored_paths',
'use_re_ignored_paths', 'device_blacklist']:
self.rerun_setup_available_image_and_video_media = True
self._set_from_toolbar_state()
if not self.preferences_dialog_displayed:
self.post_preference_change()
-
-
- elif key in ['backup_images', 'backup_device_autodetection',
- 'backup_location', 'backup_video_location',
+
+
+ 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()
-
+
# Downloads today and stored sequence numbers are kept in shared memory,
# so that the subfolderfile daemon process can access and modify them
-
+
# Note, totally ignore any changes in downloads today, as it
# is modified in a special manner via a tracking class
-
+
elif key == 'stored_sequence_no':
if type(value) <> types.IntType:
logger.critical("Stored sequence number value is malformed")
else:
self.stored_sequence_value.value = value
-
+
elif key in ['image_rename', 'subfolder', 'video_rename', 'video_subfolder']:
self.need_job_code_for_naming = self.prefs.any_pref_uses_job_code()
# Check if stored sequence no is being used
self._check_for_sequence_value_use()
-
+
elif key in ['download_folder', 'video_download_folder']:
self._set_to_toolbar_values()
self.display_free_space()
-
+
def post_preference_change(self):
if self.rerun_setup_available_image_and_video_media:
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)
self._set_device_collection_size()
-
+
if self.main_notebook.get_current_page() == 1: # preview of file
self.main_notebook.set_current_page(0)
-
+
self.rerun_setup_available_image_and_video_media = False
-
+
if self.rerun_setup_available_backup_media:
if self.using_volume_monitor():
- self.start_volume_monitor()
+ self.start_volume_monitor()
logger.info("Backup preferences were changed.")
-
+
self.refresh_backup_media()
-
+
self.rerun_setup_available_backup_media = False
-
+
if self.refresh_downloads_today:
self.downloads_today_value.value = self.downloads_today_tracker.get_raw_downloads_today()
self.downloads_today_date_value.value = self.downloads_today_tracker.get_raw_downloads_today_date()
self.day_start_value.value = self.downloads_today_tracker.get_raw_day_start()
self.refresh_downloads_today_value.value = True
self.prefs.set_downloads_today_from_tracker(self.downloads_today_tracker)
-
-
+
+
# # #
# Main app window management and setup
# # #
-
+
def _init_pynotify(self):
"""
Initialize system notification messages
"""
-
+
if not pynotify.init("TestCaps"):
logger.warning("There might be problems using pynotify.")
- #~ sys.exit(1)
do_not_size_icon = False
- self.notification_icon_size = 48
+ self.notification_icon_size = 48
try:
info = pynotify.get_server_info()
except:
@@ -3111,7 +3144,7 @@ class RapidApp(dbus.service.Object):
do_not_size_icon = True
except:
pass
-
+
if do_not_size_icon:
self.application_icon = gtk.gdk.pixbuf_new_from_file(
paths.share_dir('glade3/rapid-photo-downloader.svg'))
@@ -3137,7 +3170,7 @@ class RapidApp(dbus.service.Object):
self.main_notebook = builder.get_object("main_notebook")
self.download_action = builder.get_object("download_action")
self.download_button = builder.get_object("download_button")
-
+
self.download_progressbar = builder.get_object("download_progressbar")
self.rapid_statusbar = builder.get_object("rapid_statusbar")
self.statusbar_context_id = self.rapid_statusbar.get_context_id("progress")
@@ -3148,71 +3181,71 @@ class RapidApp(dbus.service.Object):
self.speed_label = builder.get_object("speed_label")
self.refresh_action = builder.get_object("refresh_action")
self.preferences_action = builder.get_object("preferences_action")
-
+
# Only enable this action when actually displaying a preview
self.next_image_action.set_sensitive(False)
self.prev_image_action.set_sensitive(False)
-
+
self._init_toolbars()
-
+
# About dialog
builder.add_from_file(paths.share_dir("glade3/about.ui"))
self.about = builder.get_object("about")
-
+
builder.connect_signals(self)
-
+
self.preview_image = PreviewImage(self, builder)
thumbnails_scrolledwindow = builder.get_object('thumbnails_scrolledwindow')
self.thumbnails = ThumbnailDisplay(self)
- thumbnails_scrolledwindow.add(self.thumbnails)
-
+ thumbnails_scrolledwindow.add(self.thumbnails)
+
#collection of devices from which to download
self.device_collection_viewport = builder.get_object("device_collection_viewport")
self.device_collection = DeviceCollection(self)
self.device_collection_viewport.add(self.device_collection)
-
+
#error log window
self.error_log = errorlog.ErrorLog(self)
-
+
# monitor to handle mounts and dismounts
self.vmonitor = None
# track scan ids for mount paths - very useful when a device is unmounted
self.mounts_by_path = {}
-
+
# Download action state
self.download_action_is_download = True
-
+
# Track the time a download commences
self.download_start_time = None
-
+
# Whether a system wide notifcation message should be shown
# after a download has occurred in parallel
self.display_summary_notification = False
-
+
# Values used to display how much longer a download will take
self.time_remaining = downloadtracker.TimeRemaining()
self.time_check = downloadtracker.TimeCheck()
-
+
def _init_toolbars(self):
""" Setup the 3 vertical toolbars on the main screen """
self._setup_from_toolbar()
self._setup_copy_move_toolbar()
self._setup_dest_toolbar()
-
+
# size label widths so they are equal, or else the left border of the file chooser will not match
self.photo_dest_label.realize()
self._make_widget_widths_equal(self.photo_dest_label, self.video_dest_label)
self.photo_dest_label.set_alignment(xalign=0.0, yalign=0.5)
self.video_dest_label.set_alignment(xalign=0.0, yalign=0.5)
-
+
# size copy / move buttons so they are equal in length, so arrows align
self._make_widget_widths_equal(self.copy_button, self.move_button)
-
+
def _setup_from_toolbar(self):
self.from_toolbar.set_style(gtk.TOOLBAR_TEXT)
self.from_toolbar.set_border_width(5)
-
+
from_label = gtk.Label()
from_label.set_markup("<i>" + _("From") + "</i>")
self.from_toolbar_label = gtk.ToolItem()
@@ -3229,32 +3262,32 @@ class RapidApp(dbus.service.Object):
_("Select a folder containing %(file_types)s") % {'file_types':file_types_to_download()})
self.from_filechooser_button.set_action(
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
-
+
self.from_filechooser = gtk.ToolItem()
self.from_filechooser.set_is_important(True)
self.from_filechooser.add(self.from_filechooser_button)
self.from_filechooser.set_expand(True)
self.from_toolbar.insert(self.from_filechooser, 2)
-
+
self._set_from_toolbar_state()
-
+
#set events after having initialized the values
self.auto_detect_button.connect("toggled", self.on_auto_detect_button_toggled_event)
- self.from_filechooser_button.connect("selection-changed",
+ self.from_filechooser_button.connect("selection-changed",
self.on_from_filechooser_button_selection_changed)
-
+
self.from_toolbar.show_all()
-
+
def _setup_copy_move_toolbar(self):
self.copy_toolbar.set_style(gtk.TOOLBAR_TEXT)
self.copy_toolbar.set_border_width(5)
-
+
copy_move_label = gtk.Label(" ")
self.copy_move_toolbar_label = gtk.ToolItem()
self.copy_move_toolbar_label.add(copy_move_label)
self.copy_move_toolbar_label.set_is_important(True)
self.copy_toolbar.insert(self.copy_move_toolbar_label, 0)
-
+
self.copy_hbox = gtk.HBox()
self.move_hbox = gtk.HBox()
self.forward_image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_SMALL_TOOLBAR)
@@ -3265,7 +3298,7 @@ class RapidApp(dbus.service.Object):
self.forward_label2 = gtk.Label(" ")
self.forward_label3 = gtk.Label(" ")
self.forward_label4 = gtk.Label(" ")
-
+
self.copy_button = gtk.RadioToolButton()
self.copy_button.set_label(_("Copy"))
self.copy_button.set_is_important(True)
@@ -3278,7 +3311,7 @@ class RapidApp(dbus.service.Object):
copy_box = gtk.ToolItem()
copy_box.add(self.copy_hbox)
self.copy_toolbar.insert(copy_box, 1)
-
+
self.move_button = gtk.RadioToolButton(self.copy_button)
self.move_button.set_label(_("Move"))
self.move_button.set_is_important(True)
@@ -3290,27 +3323,27 @@ class RapidApp(dbus.service.Object):
move_box = gtk.ToolItem()
move_box.add(self.move_hbox)
self.copy_toolbar.insert(move_box, 2)
-
+
self.move_button.set_active(self.prefs.auto_delete)
- self.copy_button.connect("toggled", self.on_copy_button_toggle_event)
-
+ self.copy_button.connect("toggled", self.on_copy_button_toggle_event)
+
self.copy_toolbar.show_all()
- self._set_copy_toolbar_active_arrows()
-
+ self._set_copy_toolbar_active_arrows()
+
def _setup_dest_toolbar(self):
#Destination Toolbar
self.dest_toolbar.set_border_width(5)
-
+
dest_label = gtk.Label()
dest_label.set_markup("<i>" + _("To") + "</i>")
self.dest_toolbar_label = gtk.ToolItem()
self.dest_toolbar_label.add(dest_label)
self.dest_toolbar_label.set_is_important(True)
self.dest_toolbar.insert(self.dest_toolbar_label, 0)
-
+
photo_dest_hbox = gtk.HBox()
self.photo_dest_label = gtk.Label(_("Photos:"))
-
+
self.to_photo_filechooser_button = gtk.FileChooserButton(
_("Select a folder to download photos to"))
self.to_photo_filechooser_button.set_action(
@@ -3336,9 +3369,9 @@ class RapidApp(dbus.service.Object):
self.to_video_filechooser.set_expand(True)
self.to_video_filechooser.add(video_dest_hbox)
self.dest_toolbar.insert(self.to_video_filechooser, 2)
-
+
self._set_to_toolbar_values()
- self.to_photo_filechooser_button.connect("selection-changed",
+ self.to_photo_filechooser_button.connect("selection-changed",
self.on_to_photo_filechooser_button_selection_changed)
self.to_video_filechooser_button.connect("selection-changed",
self.on_to_video_filechooser_button_selection_changed)
@@ -3346,14 +3379,14 @@ class RapidApp(dbus.service.Object):
def _make_widget_widths_equal(self, widget1, widget2):
"""takes two widgets and sets a width for both equal to widest one"""
-
+
x1, y1, w1, h1 = widget1.get_allocation()
x2, y2, w2, h2 = widget2.get_allocation()
w = max(w1, w2)
h = max(h1, h2)
widget1.set_size_request(w,h)
widget2.set_size_request(w,h)
-
+
def _set_copy_toolbar_active_arrows(self):
if self.copy_button.get_active():
self.forward_image.set_visible(True)
@@ -3377,36 +3410,36 @@ class RapidApp(dbus.service.Object):
def on_copy_button_toggle_event(self, radio_button):
self._set_copy_toolbar_active_arrows()
self.prefs.auto_delete = not self.copy_button.get_active()
-
+
def _set_from_toolbar_state(self):
logger.debug("_set_from_toolbar_state")
self.auto_detect_button.set_active(self.prefs.device_autodetection)
if self.prefs.device_autodetection:
self.from_filechooser_button.set_sensitive(False)
self.from_filechooser_button.set_current_folder(self.prefs.device_location)
-
+
def on_auto_detect_button_toggled_event(self, button):
logger.debug("on_auto_detect_button_toggled_event")
self.from_filechooser_button.set_sensitive(not button.get_active())
if not self.rerun_setup_available_image_and_video_media:
self.prefs.device_autodetection = button.get_active()
-
+
def on_from_filechooser_button_selection_changed(self, filechooserbutton):
logger.debug("on_from_filechooser_button_selection_changed")
path = filechooserbutton.get_current_folder()
if path and not self.rerun_setup_available_image_and_video_media:
self.prefs.device_location = path
-
+
def on_to_photo_filechooser_button_selection_changed(self, filechooserbutton):
path = filechooserbutton.get_current_folder()
if path:
self.prefs.download_folder = path
-
+
def on_to_video_filechooser_button_selection_changed(self, filechooserbutton):
path = filechooserbutton.get_current_folder()
if path:
self.prefs.video_download_folder = path
-
+
def _set_to_toolbar_values(self):
self.to_photo_filechooser_button.set_current_folder(self.prefs.download_folder)
self.to_video_filechooser_button.set_current_folder(self.prefs.video_download_folder)
@@ -3419,20 +3452,20 @@ class RapidApp(dbus.service.Object):
def _set_window_size(self):
"""
Remember the window size from the last time the program was run, or
- set a default size
+ set a default size
"""
-
+
if self.prefs.main_window_maximized:
self.rapidapp.maximize()
- self.rapidapp.set_default_size(config.DEFAULT_WINDOW_WIDTH,
+ self.rapidapp.set_default_size(config.DEFAULT_WINDOW_WIDTH,
config.DEFAULT_WINDOW_HEIGHT)
elif self.prefs.main_window_size_x > 0:
self.rapidapp.set_default_size(self.prefs.main_window_size_x, self.prefs.main_window_size_y)
else:
# set a default size
- self.rapidapp.set_default_size(config.DEFAULT_WINDOW_WIDTH,
+ self.rapidapp.set_default_size(config.DEFAULT_WINDOW_WIDTH,
config.DEFAULT_WINDOW_HEIGHT)
-
+
def _set_device_collection_size(self):
"""
@@ -3445,36 +3478,36 @@ class RapidApp(dbus.service.Object):
else:
# don't allow the media collection to be absolutely empty
self.device_collection_scrolledwindow.set_size_request(-1, 47)
-
-
+
+
def on_rapidapp_window_state_event(self, widget, event):
""" Records the window maximization state in the preferences."""
-
+
if event.changed_mask & gdk.WINDOW_STATE_MAXIMIZED:
self.prefs.main_window_maximized = event.new_window_state & gdk.WINDOW_STATE_MAXIMIZED
-
+
def _setup_buttons(self):
thumbnails_button = self.builder.get_object("thumbnails_button")
image = gtk.image_new_from_file(paths.share_dir('glade3/thumbnails_icon.png'))
thumbnails_button.set_image(image)
-
+
preview_button = self.builder.get_object("preview_button")
image = gtk.image_new_from_file(paths.share_dir('glade3/photo_icon.png'))
preview_button.set_image(image)
-
+
next_image_button = self.builder.get_object("next_image_button")
image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON)
next_image_button.set_image(image)
-
+
prev_image_button = self.builder.get_object("prev_image_button")
image = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_BUTTON)
prev_image_button.set_image(image)
-
+
def _setup_icons(self):
icons = ['rapid-photo-downloader-jobcode',]
- icon_list = [(icon, paths.share_dir('glade3/%s.svg' % icon)) for icon in icons]
+ icon_list = [(icon, paths.share_dir('glade3/%s.svg' % icon)) for icon in icons]
register_iconsets(icon_list)
-
+
def _setup_error_icons(self):
"""
hide display of warning and error symbols in the taskbar until they
@@ -3486,7 +3519,7 @@ class RapidApp(dbus.service.Object):
self.error_image.hide()
self.warning_image.hide()
self.warning_vseparator.hide()
-
+
def enable_prefs_and_refresh(self, enabled):
"""
If enable is true, then the user is able to activate the preferences
@@ -3495,20 +3528,20 @@ class RapidApp(dbus.service.Object):
"""
self.refresh_action.set_sensitive(enabled)
self.preferences_action.set_sensitive(enabled)
-
+
def statusbar_message(self, msg):
self.rapid_statusbar.push(self.statusbar_context_id, msg)
-
+
def statusbar_message_remove(self):
self.rapid_statusbar.pop(self.statusbar_context_id)
def display_backup_mounts(self):
"""
- Create a message to be displayed to the user showing which backup
+ Create a message to be displayed to the user showing which backup
mounts will be used
"""
message = ''
-
+
paths = self.backup_devices.keys()
i = 0
v = len(paths)
@@ -3521,22 +3554,22 @@ class RapidApp(dbus.service.Object):
prefix = " " + _("and") + " "
i += 1
message = "%s%s'%s'" % (message, prefix, self.backup_devices[b][0].get_name())
-
+
if v > 1:
message = _("Using backup devices") + " %s" % message
elif v == 1:
message = _("Using backup device") + " %s" % message
else:
message = _("No backup devices detected")
-
+
return message
-
+
def display_free_space(self):
"""
- Displays the amount of space free on the filesystem the files will be
+ Displays the amount of space free on the filesystem the files will be
downloaded to.
-
- Also displays backup volumes / path being used.
+
+ Also displays backup volumes / path being used.
"""
photo_dir = self.is_valid_download_dir(path=self.prefs.download_folder, is_photo_dir=True, show_error_in_log=True)
video_dir = self.is_valid_download_dir(path=self.prefs.video_download_folder, is_photo_dir=False, show_error_in_log=True)
@@ -3545,17 +3578,17 @@ class RapidApp(dbus.service.Object):
self.prefs.video_download_folder)
else:
same_file_system = False
-
+
dirs = []
if photo_dir:
dirs.append((self.prefs.download_folder, _("photos")))
if video_dir and not same_file_system:
dirs.append((self.prefs.video_download_folder, _("videos")))
-
+
msg = ''
if len(dirs) > 1:
msg = ' ' + _('Free space:') + ' '
-
+
for i in range(len(dirs)):
dir_info = dirs[i]
folder = gio.File(dir_info[0])
@@ -3563,13 +3596,13 @@ class RapidApp(dbus.service.Object):
size = file_info.get_attribute_uint64(gio.FILE_ATTRIBUTE_FILESYSTEM_FREE)
free = format_size_for_user(bytes=size)
if len(dirs) > 1:
- #(videos) or (photos) will be appended to the free space message displayed to the
+ #(videos) or (photos) will be appended to the free space message displayed to the
#user in the status bar.
- #you should only translate this if your language does not use parantheses
+ #you should only translate this if your language does not use parantheses
file_type = _("(%(file_type)s)") % {'file_type': dir_info[1]}
#Freespace available on the filesystem for downloading to
- #Displayed in status bar message on main window
+ #Displayed in status bar message on main window
msg += _("%(free)s %(file_type)s") % {'free': free, 'file_type': file_type}
if i == 0:
#Inserted in the middle of the statusbar message concerning the amount of freespace
@@ -3579,17 +3612,17 @@ class RapidApp(dbus.service.Object):
elif not self.prefs.backup_images:
#Inserted at the end of the statusbar message concerning the amount of freespace
#Used to differentiate between two different file systems
- #e.g. Free space: 21.3GB (photos); 14.7GB (videos).
+ #e.g. Free space: 21.3GB (photos); 14.7GB (videos).
msg += _(".")
-
+
else:
#Freespace available on the filesystem for downloading to
#Displayed in status bar message on main window
#e.g. 14.7GB available
msg = " " + _("%(free)s free") % {'free': free}
-
-
- if self.prefs.backup_images:
+
+
+ if self.prefs.backup_images:
if not self.prefs.backup_device_autodetection:
if self.prefs.backup_location == self.prefs.backup_video_location:
if DOWNLOAD_VIDEO:
@@ -3604,29 +3637,29 @@ class RapidApp(dbus.service.Object):
'path':self.prefs.backup_location,
'path2': self.prefs.backup_video_location}
else:
- msg2 = self.display_backup_mounts()
-
+ msg2 = self.display_backup_mounts()
+
if msg:
msg = _("%(freespace)s. %(backuppaths)s.") % {'freespace': msg, 'backuppaths': msg2}
else:
msg = msg2
-
+
msg = msg.rstrip()
-
+
self.statusbar_message(msg)
-
+
def log_error(self, severity, problem, details, extra_detail=None):
"""
Display error and warning messages to user in log window
"""
self.error_log.add_message(severity, problem, details, extra_detail)
-
-
+
+
def on_error_eventbox_button_press_event(self, widget, event):
self.prefs.show_log_dialog = True
- self.error_log.widget.show()
-
-
+ self.error_log.widget.show()
+
+
def on_menu_log_window_toggled(self, widget):
active = widget.get_active()
self.prefs.show_log_dialog = active
@@ -3634,14 +3667,14 @@ class RapidApp(dbus.service.Object):
self.error_log.widget.show()
else:
self.error_log.widget.hide()
-
+
def notify_prefs_are_invalid(self, details):
title = _("Program preferences are invalid")
logger.critical(title)
self.log_error(severity=config.CRITICAL_ERROR, problem=title,
details=details)
-
-
+
+
# # #
# Utility functions
# # #
@@ -3650,7 +3683,7 @@ class RapidApp(dbus.service.Object):
"""
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
"""
@@ -3665,7 +3698,7 @@ class RapidApp(dbus.service.Object):
return False
else:
return i
-
+
def size_files_to_be_downloaded(self, files):
"""
Returns the total sizes of the photos and videos to be downloaded in bytes
@@ -3679,12 +3712,12 @@ class RapidApp(dbus.service.Object):
video_size += rpd_file.size
return (photo_size, video_size)
-
+
def check_download_folder_validity(self, files_by_scan_pid):
"""
Checks validity of download folders based on the file types the user
is attempting to download.
-
+
If valid, returns a tuple of True and an empty list.
If invalid, returns a tuple of False and a list of the invalid directores.
"""
@@ -3702,27 +3735,27 @@ class RapidApp(dbus.service.Object):
if not need_video_folder:
if self.files_of_type_present(files, rpdfile.FILE_TYPE_VIDEO):
need_video_folder = True
-
+
# second, check validity
if need_photo_folder:
- if not self.is_valid_download_dir(self.prefs.download_folder,
+ if not self.is_valid_download_dir(self.prefs.download_folder,
is_photo_dir=True):
valid = False
invalid_dirs.append(self.prefs.download_folder)
else:
- logger.debug("Photo download folder is valid: %s",
+ 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):
+ is_photo_dir=False):
valid = False
invalid_dirs.append(self.prefs.video_download_folder)
else:
- logger.debug("Video download folder is valid: %s",
+ logger.debug("Video download folder is valid: %s",
self.prefs.video_download_folder)
-
+
return (valid, invalid_dirs)
def same_file_system(self, file1, file2):
@@ -3735,27 +3768,27 @@ class RapidApp(dbus.service.Object):
f2_info = f2.query_info(gio.FILE_ATTRIBUTE_ID_FILESYSTEM)
f2_id = f2_info.get_attribute_string(gio.FILE_ATTRIBUTE_ID_FILESYSTEM)
return f1_id == f2_id
-
-
+
+
def same_file(self, file1, file2):
"""Returns True if the files / directories are the same
"""
f1 = gio.File(file1)
f2 = gio.File(file2)
-
+
file_attributes = "id::file"
f1_info = f1.query_filesystem_info(file_attributes)
f1_id = f1_info.get_attribute_string(gio.FILE_ATTRIBUTE_ID_FILE)
f2_info = f2.query_filesystem_info(file_attributes)
f2_id = f2_info.get_attribute_string(gio.FILE_ATTRIBUTE_ID_FILE)
return f1_id == f2_id
-
+
def is_valid_download_dir(self, path, is_photo_dir, show_error_in_log=False):
"""
Checks the following conditions:
Does the directory exist?
Is it writable?
-
+
if show_error_in_log is True, then display warning in log window, using
is_photo_dir, which if true means the download directory is for photos,
if false, for Videos
@@ -3765,11 +3798,11 @@ class RapidApp(dbus.service.Object):
download_folder_type = _("Photo")
else:
download_folder_type = _("Video")
-
+
try:
d = gio.File(path)
if not d.query_exists(cancellable=None):
- logger.error("%s download folder does not exist: %s",
+ logger.error("%s download folder does not exist: %s",
download_folder_type, path)
if show_error_in_log:
severity = config.WARNING
@@ -3781,16 +3814,16 @@ class RapidApp(dbus.service.Object):
file_attributes = "standard::type,access::can-read,access::can-write"
file_info = d.query_filesystem_info(file_attributes)
file_type = file_info.get_file_type()
-
+
if file_type != gio.FILE_TYPE_DIRECTORY and file_type != gio.FILE_TYPE_UNKNOWN:
- logger.error("%s download folder is invalid: %s",
+ logger.error("%s download folder is invalid: %s",
download_folder_type, path)
if show_error_in_log:
severity = config.WARNING
problem = _("%(file_type)s download folder is invalid") % {
'file_type': download_folder_type}
details = _("Folder: %s") % path
- self.log_error(severity, problem, details)
+ self.log_error(severity, problem, details)
else:
# is the directory writable?
try:
@@ -3803,7 +3836,7 @@ class RapidApp(dbus.service.Object):
problem = _("%(file_type)s download folder is not writable") % {
'file_type': download_folder_type}
details = _("Folder: %s") % path
- self.log_error(severity, problem, details)
+ self.log_error(severity, problem, details)
else:
f = gio.File(temp_dir)
f.delete(cancellable=None)
@@ -3811,66 +3844,66 @@ class RapidApp(dbus.service.Object):
except gio.Error, inst:
logger.error("Error checking download directory %s", path)
logger.error(inst)
-
+
return valid
-
-
-
+
+
+
# # #
# Process results and management
# # #
-
-
+
+
def _start_process_managers(self):
"""
Set up process managers.
-
+
A task such as scanning a device or copying files is handled in its
own process.
"""
-
+
self.batch_size = 10
self.batch_size_MB = 2
-
+
sequence_values = (self.downloads_today_value,
self.downloads_today_date_value,
self.day_start_value,
self.refresh_downloads_today_value,
- self.stored_sequence_value,
+ self.stored_sequence_value,
self.uses_stored_sequence_no_value,
self.uses_session_sequece_no_value,
self.uses_sequence_letter_value)
-
- # daemon process to rename files and create subfolders
+
+ # daemon process to rename files and create subfolders
self.subfolder_file_manager = SubfolderFileManager(
self.subfolder_file_results,
sequence_values)
-
+
# process to scan source devices / paths
- self.scan_manager = ScanManager(self.scan_results, self.batch_size,
+ self.scan_manager = ScanManager(self.scan_results, self.batch_size,
self.device_collection.add_device)
-
+
#process to copy files from source to destination
- self.copy_files_manager = CopyFilesManager(self.copy_files_results,
+ self.copy_files_manager = CopyFilesManager(self.copy_files_results,
self.batch_size_MB)
-
+
#process to back files up
self.backup_manager = BackupFilesManager(self.backup_results,
self.batch_size_MB)
-
+
#process to enhance files after they've been copied and before they're
#renamed
self.file_modify_manager = FileModifyManager(self.file_modify_results)
-
-
+
+
def scan_results(self, source, condition):
"""
Receive results from scan processes
"""
connection = self.scan_manager.get_pipe(source)
-
+
conn_type, data = connection.recv()
-
+
if conn_type == rpdmp.CONN_COMPLETE:
connection.close()
self.scan_manager.no_tasks -= 1
@@ -3883,7 +3916,7 @@ class RapidApp(dbus.service.Object):
self.device_collection.update_device(scan_pid, size)
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
self.prefs.generate_thumbnails):
self.download_progressbar.set_text(_("Thumbnails"))
@@ -3897,7 +3930,7 @@ class RapidApp(dbus.service.Object):
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:
@@ -3910,20 +3943,20 @@ class RapidApp(dbus.service.Object):
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,
+ self.thumbnails.add_file(rpd_file=rpd_file,
generate_thumbnail = not self.auto_start_is_on)
-
+
# must return True for this method to be called again
return True
-
+
@dbus.service.method (config.DBUS_NAME,
in_signature='', out_signature='b')
def is_running (self):
return self.running
-
+
@dbus.service.method (config.DBUS_NAME,
in_signature='', out_signature='')
def start (self):
@@ -3932,14 +3965,14 @@ class RapidApp(dbus.service.Object):
else:
self.running = True
gtk.main()
-
+
def start():
is_beta = config.version.find('~') > 0
-
+
parser = OptionParser(version= "%%prog %s" % utilities.human_readable_version(config.version))
parser.set_defaults(verbose=is_beta, extensions=False)
- # Translators: this text is displayed to the user when they request information on the command line options.
+ # Translators: this text is displayed to the user when they request information on the command line options.
# The text %default should not be modified or left out.
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help=_("display program information on the command line as the program runs (default: %default)"))
parser.add_option("-d", "--debug", action="store_true", dest="debug", help=_('display debugging information when run from the command line'))
@@ -3951,26 +3984,26 @@ def start():
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()
-
+
if options.debug:
logging_level = logging.DEBUG
elif options.verbose:
logging_level = logging.INFO
else:
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]=='/':
@@ -3987,15 +4020,15 @@ def start():
v += '%s, ' % e.upper()
v = file_type + " " + v[:-1] + ' '+ (_('and %s') % exts[-1].upper())
print v
-
+
sys.exit(0)
-
+
if options.reset:
prefs = prefsrapid.RapidPreferences()
prefs.reset()
print _("All settings and preferences have been reset")
sys.exit(0)
-
+
if options.focal_length:
focal_length = options.focal_length
else:
@@ -4017,7 +4050,7 @@ 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:
+ if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS:
app = RapidApp(bus, '/', config.DBUS_NAME, focal_length=focal_length,
auto_detect=auto_detect, device_location=device_location)
else:
@@ -4025,8 +4058,8 @@ def start():
print "Rapid Photo Downloader is already running"
object = bus.get_object (config.DBUS_NAME, "/")
app = dbus.Interface (object, config.DBUS_NAME)
-
- app.start()
+
+ app.start()
if __name__ == "__main__":
start()