summaryrefslogtreecommitdiff
path: root/rapid
diff options
context:
space:
mode:
authorJulien Valroff <julien@kirya.net>2011-04-16 16:39:17 +0200
committerJulien Valroff <julien@kirya.net>2011-04-16 16:39:17 +0200
commit5539b9c5aa11891c66104fe51ebb5e9b4ad31538 (patch)
treea08efd10faf1ac622a62e29d17da6d4f1f0aeac3 /rapid
parent07934178861343ee213674120db367b8faeef1be (diff)
parent75b28642dd41fb4a7925b42cb24274de90a4f52c (diff)
Merge commit 'upstream/0.4.0_beta1' into experimental
Diffstat (limited to 'rapid')
-rw-r--r--rapid/ChangeLog50
-rw-r--r--rapid/config.py4
-rw-r--r--rapid/copyfiles.py33
-rw-r--r--rapid/downloadtracker.py199
-rw-r--r--rapid/glade3/media-eject.pngbin0 -> 431 bytes
-rw-r--r--rapid/glade3/rapid-photo-downloader-download-pending.pngbin0 -> 815 bytes
-rw-r--r--rapid/glade3/rapid-photo-downloader-download-pending.svg187
-rw-r--r--rapid/glade3/rapid.ui6
-rw-r--r--rapid/preferencesdialog.py7
-rw-r--r--rapid/prefsrapid.py35
-rwxr-xr-xrapid/rapid.py670
-rw-r--r--rapid/rpdmultiprocessing.py1
-rw-r--r--rapid/subfolderfile.py6
-rw-r--r--rapid/thumbnail.py3
14 files changed, 833 insertions, 368 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog
index 86037ed..c9437d5 100644
--- a/rapid/ChangeLog
+++ b/rapid/ChangeLog
@@ -1,3 +1,51 @@
+Version 0.4.0 beta 1
+--------------------
+
+2011-04-10
+
+Features added since alpha 4:
+
+* Job Code functionality, mimicking that found in version 0.2.3.
+* Eject device button for each unmountable device in main window.
+* When not all files have been downloaded from a device, the number remaining
+ is displayed in the device's progress bar
+* Overall download progress is displayed in progress bar at bottom of window
+* Time remaining and download speed are displayed in the status bar
+* System notification messages
+* Automation features:
+ * Automatically start a download at program startup or when a device
+ is inserted. When this is enabled, to optimize performance instead of
+ thumbnails being generated before the files are downloaded, they are
+ generated during the download.
+ * Eject a device when all files have been downloaded from it.
+ * Exit when all files have been downloaded.
+
+The automation feature to delete downloaded files from a device will be added
+only when the non-alpha/beta of version 0.4.0 is released.
+
+The major feature currently not implemented is backups.
+
+Note: if videos are downloaded, the device may not be able to be unmounted
+until Rapid Photo Downloader is exited. See bug #744012 for details.
+
+Bug fix: adjust vertical pane position when additional devices are inserted
+Bug fix: display file and subfolder naming warnings in error log
+
+Updated Czech, French and Russian translations.
+
+
+Version 0.3.6
+-------------
+
+2011-04-05
+
+This release contains a minor fix to allow program preferences to be changed
+on upcoming Linux distributions like Ubuntu 11.04 and Fedora 15.
+
+It also contains a minor packaging change so it can be installed in Ubuntu
+11.04.
+
+
Version 0.4.0 alpha 4
---------------------
@@ -86,7 +134,7 @@ program startup.
Thanks go to Robert Park for refreshing the translations code.
Added Romanian translation.
-
+
Version 0.3.5
-------------
diff --git a/rapid/config.py b/rapid/config.py
index d020562..da2f5c0 100644
--- a/rapid/config.py
+++ b/rapid/config.py
@@ -15,7 +15,7 @@
### along with this program; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-version = '0.4.0~a4'
+version = '0.4.0~b1'
GCONF_KEY="/apps/rapid-photo-downloader"
@@ -51,7 +51,7 @@ STATUS_DOWNLOAD_FAILED = 6 # tried to download but failed
STATUS_WARNING = 7 # warning (shown in pre-download preview)
STATUS_CANNOT_DOWNLOAD = 8 # cannot be downloaded
-DEFAULT_WINDOW_WIDTH = 730
+DEFAULT_WINDOW_WIDTH = 670
DEFAULT_WINDOW_HEIGHT = 650
diff --git a/rapid/copyfiles.py b/rapid/copyfiles.py
index 78fe8a3..08daafe 100644
--- a/rapid/copyfiles.py
+++ b/rapid/copyfiles.py
@@ -30,6 +30,7 @@ import rpdmultiprocessing as rpdmp
import rpdfile
import problemnotification as pn
import config
+import thumbnail as tn
from gettext import gettext as _
@@ -37,7 +38,7 @@ from gettext import gettext as _
class CopyFiles(multiprocessing.Process):
def __init__(self, photo_download_folder, video_download_folder, files,
- scan_pid,
+ generate_thumbnails, scan_pid,
batch_size_MB, results_pipe, terminate_queue,
run_event):
multiprocessing.Process.__init__(self)
@@ -47,6 +48,7 @@ class CopyFiles(multiprocessing.Process):
self.photo_download_folder = photo_download_folder
self.video_download_folder = video_download_folder
self.files = files
+ self.generate_thumbnails = generate_thumbnails
self.scan_pid = scan_pid
self.no_files= len(self.files)
self.run_event = run_event
@@ -69,17 +71,17 @@ class CopyFiles(multiprocessing.Process):
# it is - cancel the current copy
self.cancel_copy.cancel()
else:
- if (amount_downloaded - self.bytes_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
- chunk_downloaded = amount_downloaded - self.bytes_downloaded
+ chunk_downloaded = amount_downloaded - self.bytes_downloaded
+ if (chunk_downloaded > self.batch_size_bytes) or (amount_downloaded == total):
self.bytes_downloaded = amount_downloaded
- self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.total_downloaded + amount_downloaded))))
+ if amount_downloaded == total:
+ # this function is called a couple of times when total is reached
+ chunk_downloaded = 0
+ self.results_pipe.send((rpdmp.CONN_PARTIAL, (rpdmp.MSG_BYTES, (self.scan_pid, self.total_downloaded + amount_downloaded, chunk_downloaded))))
+ if amount_downloaded == total:
+ self.bytes_downloaded = 0
def progress_callback(self, amount_downloaded, total):
-
- #~ if self.check_termination_request():
- #~ # FIXME: cancel copy
- #~ pass
-
self.update_progress(amount_downloaded, total)
@@ -102,6 +104,10 @@ class CopyFiles(multiprocessing.Process):
self.video_temp_dir))))
if self.photo_temp_dir or self.video_temp_dir:
+
+ if self.generate_thumbnails:
+ self.thumbnail_maker = tn.Thumbnail()
+
for i in range(len(self.files)):
rpd_file = self.files[i]
@@ -145,6 +151,15 @@ class CopyFiles(multiprocessing.Process):
# succeeded or not. It's neccessary to keep the user informed.
self.total_downloaded += rpd_file.size
+ if copy_succeeded and self.generate_thumbnails:
+ thumbnail, thumbnail_icon = self.thumbnail_maker.get_thumbnail(
+ temp_full_file_name,
+ rpd_file.file_type,
+ (160, 120), (100,100))
+ self.results_pipe.send((rpdmp.CONN_PARTIAL,
+ (rpdmp.MSG_THUMB, (rpd_file.unique_id,
+ thumbnail_icon, thumbnail))))
+
if rpd_file.metadata is not None:
rpd_file.metadata = None
diff --git a/rapid/downloadtracker.py b/rapid/downloadtracker.py
index f2c80e2..309da71 100644
--- a/rapid/downloadtracker.py
+++ b/rapid/downloadtracker.py
@@ -17,29 +17,93 @@
### along with this program; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import time
+from rpdfile import FILE_TYPE_PHOTO, FILE_TYPE_VIDEO
+from config import STATUS_DOWNLOAD_FAILED, STATUS_DOWNLOADED_WITH_WARNING
+
+from gettext import gettext as _
+
class DownloadTracker:
+ """
+ Track file downloads - their size, number, and any problems
+ """
def __init__(self):
self.size_of_download_in_bytes_by_scan_pid = dict()
self.total_bytes_copied_in_bytes_by_scan_pid = dict()
self.no_files_in_download_by_scan_pid = dict()
self.file_types_present_by_scan_pid = dict()
+ # 'Download count' tracks the index of the file being downloaded
+ # into the list of files that need to be downloaded -- much like
+ # a counter in a for loop, e.g. 'for i in list', where i is the counter
self.download_count_for_file_by_unique_id = dict()
self.download_count_by_scan_pid = dict()
self.rename_chunk = dict()
self.files_downloaded = dict()
+ self.photos_downloaded = dict()
+ self.videos_downloaded = dict()
+ self.photo_failures = dict()
+ self.video_failures = dict()
+ self.warnings = dict()
+ self.total_photos_downloaded = 0
+ self.total_photo_failures = 0
+ self.total_videos_downloaded = 0
+ self.total_video_failures = 0
+ self.total_warnings = 0
+ self.total_bytes_to_download = 0
def init_stats(self, scan_pid, bytes, no_files):
self.no_files_in_download_by_scan_pid[scan_pid] = no_files
self.rename_chunk[scan_pid] = bytes / 10 / no_files
self.size_of_download_in_bytes_by_scan_pid[scan_pid] = bytes + self.rename_chunk[scan_pid] * no_files
+ self.total_bytes_to_download += self.size_of_download_in_bytes_by_scan_pid[scan_pid]
self.files_downloaded[scan_pid] = 0
+ self.photos_downloaded[scan_pid] = 0
+ self.videos_downloaded[scan_pid] = 0
+ self.photo_failures[scan_pid] = 0
+ self.video_failures[scan_pid] = 0
+ self.warnings[scan_pid] = 0
def get_no_files_in_download(self, scan_pid):
return self.no_files_in_download_by_scan_pid[scan_pid]
- def file_downloaded_increment(self, scan_pid):
+
+ def get_no_files_downloaded(self, scan_pid, file_type):
+ if file_type == FILE_TYPE_PHOTO:
+ return self.photos_downloaded.get(scan_pid, 0)
+ else:
+ return self.videos_downloaded.get(scan_pid, 0)
+
+ def get_no_files_failed(self, scan_pid, file_type):
+ if file_type == FILE_TYPE_PHOTO:
+ return self.photo_failures.get(scan_pid, 0)
+ else:
+ return self.video_failures.get(scan_pid, 0)
+
+ def get_no_warnings(self, scan_pid):
+ return self.warnings.get(scan_pid, 0)
+
+ def file_downloaded_increment(self, scan_pid, file_type, status):
self.files_downloaded[scan_pid] += 1
+ if status <> STATUS_DOWNLOAD_FAILED:
+ if file_type == FILE_TYPE_PHOTO:
+ self.photos_downloaded[scan_pid] += 1
+ self.total_photos_downloaded += 1
+ else:
+ self.videos_downloaded[scan_pid] += 1
+ self.total_videos_downloaded += 1
+
+ if status == STATUS_DOWNLOADED_WITH_WARNING:
+ self.warnings[scan_pid] += 1
+ self.total_warnings += 1
+ else:
+ if file_type == FILE_TYPE_PHOTO:
+ self.photo_failures[scan_pid] += 1
+ self.total_photo_failures += 1
+ else:
+ self.video_failures[scan_pid] += 1
+ self.total_video_failures += 1
+
def get_percent_complete(self, scan_pid):
"""
@@ -54,6 +118,16 @@ class DownloadTracker:
/ self.size_of_download_in_bytes_by_scan_pid[scan_pid]) * 100
return percent_complete
+ def get_overall_percent_complete(self):
+ total = 0
+ for scan_pid in self.total_bytes_copied_in_bytes_by_scan_pid:
+ total += (self.total_bytes_copied_in_bytes_by_scan_pid[scan_pid] +
+ (self.rename_chunk[scan_pid] *
+ self.files_downloaded[scan_pid]))
+
+ percent_complete = float(total) / self.total_bytes_to_download
+ return percent_complete
+
def set_total_bytes_copied(self, scan_pid, total_bytes):
self.total_bytes_copied_in_bytes_by_scan_pid[scan_pid] = total_bytes
@@ -72,7 +146,130 @@ class DownloadTracker:
def set_file_types_present(self, scan_pid, file_types_present):
self.file_types_present_by_scan_pid[scan_pid] = file_types_present
+ def no_errors_or_warnings(self):
+ """
+ Return True if there were no errors or warnings in the download
+ else return False
+ """
+ return (self.total_warnings == 0 and
+ self.photo_failures == 0 and
+ self.video_failures == 0)
+
def purge(self, scan_pid):
del self.no_files_in_download_by_scan_pid[scan_pid]
del self.size_of_download_in_bytes_by_scan_pid[scan_pid]
+ del self.photos_downloaded[scan_pid]
+ del self.videos_downloaded[scan_pid]
+ del self.files_downloaded[scan_pid]
+ del self.photo_failures[scan_pid]
+ del self.video_failures[scan_pid]
+ del self.warnings[scan_pid]
+
+ def purge_all(self):
+ self.__init__()
+
+
+
+class TimeCheck:
+ """
+ Record times downloads commmence and pause - used in calculating time
+ remaining.
+
+ Also tracks and reports download speed.
+
+ Note: This is completely independent of the file / subfolder naming
+ preference "download start time"
+ """
+
+ def __init__(self):
+ # set the number of seconds gap with which to measure download time remaing
+ self.download_time_gap = 3
+
+ self.reset()
+
+ def reset(self):
+ self.mark_set = False
+ self.total_downloaded_so_far = 0
+ self.total_download_size = 0
+ self.size_mark = 0
+
+ def increment(self, bytes_downloaded):
+ self.total_downloaded_so_far += bytes_downloaded
+
+ def set_download_mark(self):
+ if not self.mark_set:
+ self.mark_set = True
+
+ self.time_mark = time.time()
+
+ def pause(self):
+ self.mark_set = False
+
+ def check_for_update(self):
+ now = time.time()
+ update = now > (self.download_time_gap + self.time_mark)
+
+ if update:
+ amt_time = now - self.time_mark
+ self.time_mark = now
+ amt_downloaded = self.total_downloaded_so_far - self.size_mark
+ self.size_mark = self.total_downloaded_so_far
+ download_speed = "%1.1f" % (amt_downloaded / 1048576 / amt_time) +_("MB/s")
+ else:
+ download_speed = None
+
+ return (update, download_speed)
+
+class TimeForDownload:
+ # used to store variables, see below
+ pass
+
+class TimeRemaining:
+ """
+ Calculate how much time is remaining to finish a download
+ """
+ gap = 3
+ def __init__(self):
+ self.clear()
+
+ def set(self, scan_pid, size):
+ t = TimeForDownload()
+ t.time_remaining = None
+ t.size = size
+ t.downloaded = 0
+ t.size_mark = 0
+ t.time_mark = time.time()
+ self.times[scan_pid] = t
+
+ def update(self, scan_pid, total_size):
+ if scan_pid in self.times:
+ self.times[scan_pid].downloaded = total_size
+ now = time.time()
+ tm = self.times[scan_pid].time_mark
+ amt_time = now - tm
+ if amt_time > self.gap:
+ self.times[scan_pid].time_mark = now
+ amt_downloaded = self.times[scan_pid].downloaded - self.times[scan_pid].size_mark
+ self.times[scan_pid].size_mark = self.times[scan_pid].downloaded
+ timefraction = amt_downloaded / float(amt_time)
+ amt_to_download = float(self.times[scan_pid].size) - self.times[scan_pid].downloaded
+ if timefraction:
+ self.times[scan_pid].time_remaining = amt_to_download / timefraction
+
+ def _time_estimates(self):
+ for t in self.times:
+ yield self.times[t].time_remaining
+
+ def time_remaining(self):
+ return max(self._time_estimates())
+
+ def set_time_mark(self, scan_pid):
+ if scan_pid in self.times:
+ self.times[scan_pid].time_mark = time.time()
+
+ def clear(self):
+ self.times = {}
+ def remove(self, scan_pid):
+ if scan_pid in self.times:
+ del self.times[scan_pid]
diff --git a/rapid/glade3/media-eject.png b/rapid/glade3/media-eject.png
new file mode 100644
index 0000000..0ff107e
--- /dev/null
+++ b/rapid/glade3/media-eject.png
Binary files differ
diff --git a/rapid/glade3/rapid-photo-downloader-download-pending.png b/rapid/glade3/rapid-photo-downloader-download-pending.png
new file mode 100644
index 0000000..e12cc69
--- /dev/null
+++ b/rapid/glade3/rapid-photo-downloader-download-pending.png
Binary files differ
diff --git a/rapid/glade3/rapid-photo-downloader-download-pending.svg b/rapid/glade3/rapid-photo-downloader-download-pending.svg
deleted file mode 100644
index d6127b7..0000000
--- a/rapid/glade3/rapid-photo-downloader-download-pending.svg
+++ /dev/null
@@ -1,187 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- version="1.0"
- width="16"
- height="16"
- id="svg4136"
- inkscape:version="0.47 r22583"
- sodipodi:docname="rapid-photo-downloader-image-loading.svg">
- <metadata
- id="metadata33">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1920"
- inkscape:window-height="1089"
- id="namedview31"
- showgrid="false"
- inkscape:zoom="9.8333333"
- inkscape:cx="12"
- inkscape:cy="12"
- inkscape:window-x="0"
- inkscape:window-y="24"
- inkscape:window-maximized="1"
- inkscape:current-layer="svg4136" />
- <defs
- id="defs4138">
- <inkscape:perspective
- sodipodi:type="inkscape:persp3d"
- inkscape:vp_x="0 : 12 : 1"
- inkscape:vp_y="0 : 1000 : 0"
- inkscape:vp_z="24 : 12 : 1"
- inkscape:persp3d-origin="12 : 8 : 1"
- id="perspective35" />
- <linearGradient
- id="linearGradient8838">
- <stop
- id="stop8840"
- style="stop-color:black;stop-opacity:1"
- offset="0" />
- <stop
- id="stop8842"
- style="stop-color:black;stop-opacity:0"
- offset="1" />
- </linearGradient>
- <radialGradient
- cx="62.625"
- cy="4.625"
- r="10.625"
- fx="62.625"
- fy="4.625"
- id="radialGradient5323"
- xlink:href="#linearGradient8838"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1,0,0,0.341176,0,3.047059)" />
- <linearGradient
- id="linearGradient5354">
- <stop
- id="stop5356"
- style="stop-color:#3f3f3f;stop-opacity:1"
- offset="0" />
- <stop
- id="stop5358"
- style="stop-color:black;stop-opacity:1"
- offset="1" />
- </linearGradient>
- <linearGradient
- x1="19.176617"
- y1="13.479795"
- x2="19.176617"
- y2="45.358662"
- id="linearGradient5130"
- xlink:href="#linearGradient5354"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
- <linearGradient
- id="linearGradient37935">
- <stop
- id="stop37937"
- style="stop-color:#929292;stop-opacity:1"
- offset="0" />
- <stop
- id="stop37939"
- style="stop-color:#4a4a4a;stop-opacity:1"
- offset="1" />
- </linearGradient>
- <linearGradient
- x1="28.771276"
- y1="12.91806"
- x2="28.771276"
- y2="45.347591"
- id="linearGradient5128"
- xlink:href="#linearGradient37935"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.4916775,0,0,0.4916774,0.6986745,-0.3018277)" />
- <linearGradient
- id="linearGradient2145">
- <stop
- id="stop2147"
- style="stop-color:#fffffd;stop-opacity:1"
- offset="0" />
- <stop
- id="stop2149"
- style="stop-color:#cbcbc9;stop-opacity:1"
- offset="1" />
- </linearGradient>
- <radialGradient
- cx="11.901996"
- cy="10.045444"
- r="29.292715"
- fx="11.901996"
- fy="10.045444"
- id="radialGradient5350"
- xlink:href="#linearGradient2145"
- gradientUnits="userSpaceOnUse" />
- </defs>
- <g
- id="layer1"
- transform="matrix(0.652174,0,0,0.65491337,-0.15217376,0.2820791)">
- <path
- d="m 73.25,4.625 a 10.625,3.625 0 1 1 -21.25,0 10.625,3.625 0 1 1 21.25,0 z"
- transform="matrix(1.0823528,0,0,1.2906765,-55.282346,13.351919)"
- id="path2774"
- style="opacity:0.56043958;fill:url(#radialGradient5323);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999988;marker:none;visibility:visible;display:inline;overflow:visible" />
- <path
- d="m 12.492145,1.4999735 c -5.5190685,0 -9.9921705,4.473101 -9.9921705,9.9921715 0,5.519069 4.473102,10.007882 9.9921705,10.007881 5.519068,0 10.007887,-4.488812 10.007882,-10.007881 0,-5.5190705 -4.488814,-9.9921715 -10.007882,-9.9921715 z"
- id="path2555"
- style="fill:url(#linearGradient5128);fill-opacity:1;stroke:url(#linearGradient5130);stroke-width:0.99994898;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
- <path
- d="m 31.160714,16.910715 a 14.910714,14.910714 0 1 1 -29.8214281,0 14.910714,14.910714 0 1 1 29.8214281,0 z"
- transform="matrix(0.5700599,0,0,0.5700599,3.2365269,1.8598792)"
- id="path35549"
- style="fill:url(#radialGradient5350);fill-opacity:1;fill-rule:evenodd;stroke:none" />
- <path
- d="m 12.5,6.4999999 c 0,-1.3926725 0,-1.5690116 0,-1.5690116"
- id="path2308"
- style="fill:#1f1f1f;fill-opacity:1;fill-rule:evenodd;stroke:#727272;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="M 12.194529,11.713855 17.596253,6.3122524"
- id="path2312"
- style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="M 13.071561,11.753718 9.4637681,8.1460064"
- id="path2314"
- style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="M 12.314125,11.434805 18.465311,9.7866365"
- id="path2316"
- style="fill:#ff0000;fill-rule:evenodd;stroke:#ff0000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 12.5,18.284507 c 0,-1.392673 0,-1.569012 0,-1.569012"
- id="path5368"
- style="fill:#121212;fill-opacity:1;fill-rule:evenodd;stroke:#121212;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.5,11.5 c 1.392673,0 1.569013,0 1.569013,0"
- id="path5370"
- style="fill:#1f1f1f;fill-opacity:1;fill-rule:evenodd;stroke:#1f1f1f;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 5.7331351,11.5 c 1.3613563,0 1.5337301,0 1.5337301,0"
- id="path5372"
- style="fill:#1f1f1f;fill-opacity:1;fill-rule:evenodd;stroke:#5f5f5f;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- </g>
-</svg>
diff --git a/rapid/glade3/rapid.ui b/rapid/glade3/rapid.ui
index 1b587b4..bd9638e 100644
--- a/rapid/glade3/rapid.ui
+++ b/rapid/glade3/rapid.ui
@@ -253,8 +253,8 @@
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
- <accelerator key="equal" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <accelerator key="equal" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@@ -405,8 +405,8 @@
<object class="GtkScrolledWindow" id="device_collection_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">never</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkViewport" id="device_collection_viewport">
<property name="visible">True</property>
diff --git a/rapid/preferencesdialog.py b/rapid/preferencesdialog.py
index 4909820..0f46406 100644
--- a/rapid/preferencesdialog.py
+++ b/rapid/preferencesdialog.py
@@ -313,7 +313,6 @@ class PreferenceWidgets:
Checks preferences validity
"""
- #~ print logger.debug(dir(self))
return check_pref_valid(self.pref_defn_L0, self.pref_list)
class PhotoNamePrefs(PreferenceWidgets):
@@ -911,6 +910,9 @@ class PreferencesDialog():
If use_dummy_data is True, then samples will not attempt to get
data from actual download files
"""
+ job_code = self.prefs.most_recent_job_code()
+ if job_code is None:
+ job_code = _("Job Code")
self.downloads_today_tracker = DownloadsTodayTracker(
day_start = self.prefs.day_start,
downloads_today = self.prefs.downloads_today[1],
@@ -934,6 +936,8 @@ class PreferencesDialog():
if self.sample_photo is None:
self.sample_photo = rpdfile.SamplePhoto(sequences=self.sequences)
+
+ self.sample_photo.job_code = job_code
self.sample_video = None
if metadatavideo.DOWNLOAD_VIDEO:
@@ -945,6 +949,7 @@ class PreferencesDialog():
self.sample_video.download_start_time = datetime.datetime.now()
if self.sample_video is None:
self.sample_video = rpdfile.SampleVideo(sequences=self.sequences)
+ self.sample_video.job_code = job_code
diff --git a/rapid/prefsrapid.py b/rapid/prefsrapid.py
index 491d75d..81a425b 100644
--- a/rapid/prefsrapid.py
+++ b/rapid/prefsrapid.py
@@ -218,6 +218,12 @@ class RapidPreferences(prefs.Preferences):
return True
return False
+ def most_recent_job_code(self):
+ if len(self.job_codes) > 0:
+ return self.job_codes[0]
+ else:
+ return None
+
def get_pref_lists_by_file_type(self, file_type):
"""
Returns tuple of subfolder and file rename pref lists for the given
@@ -355,20 +361,27 @@ def check_prefs_for_validity(prefs):
"""
Checks preferences for validity (called at program startup)
- Returns true if the passed in preferences are valid, else returns False
+ Returns tuple with two values:
+ 1. true if the passed in preferences are valid, else returns False
+ 2. message if prefs are invalid
"""
- try:
- tests = ((prefs.image_rename, pd.PhotoNamePrefs),
- (prefs.subfolder, pd.PhotoSubfolderPrefs),
- (prefs.video_rename, pd.VideoNamePrefs),
- (prefs.video_subfolder, pd.VideoSubfolderPrefs))
- for pref, pref_widgets in tests:
- p = pref_widgets(pref)
+
+ msg = ''
+ valid = True
+ tests = ((prefs.image_rename, pd.PhotoNamePrefs),
+ (prefs.subfolder, pd.PhotoSubfolderPrefs),
+ (prefs.video_rename, pd.VideoNamePrefs),
+ (prefs.video_subfolder, pd.VideoSubfolderPrefs))
+ for pref, pref_widgets in tests:
+ p = pref_widgets(pref)
+ try:
p.check_prefs_for_validity()
- except:
- return False
- return True
+ except pd.PrefError as e:
+ valid = False
+ msg += e.msg + "\n"
+
+ return (valid, msg)
def insert_pref_lists(prefs, rpd_file):
"""
diff --git a/rapid/rapid.py b/rapid/rapid.py
index 3603025..df60790 100755
--- a/rapid/rapid.py
+++ b/rapid/rapid.py
@@ -36,6 +36,7 @@ import webbrowser
import sys, time, types, os, datetime
import gobject, pango, cairo, array, pangocairo, gio
+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
@@ -127,9 +128,12 @@ class DeviceCollection(gtk.TreeView):
self.parent_app = parent_app
# device icon & name, size of images on the device (human readable),
- # copy progress (%), copy text
- self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, float, str)
+ # copy progress (%), copy text, eject button (None if irrelevant),
+ # process id
+ self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, float, str,
+ gtk.gdk.Pixbuf, int)
self.map_process_to_row = {}
+ self.devices_by_scan_pid = {}
gtk.TreeView.__init__(self, self.liststore)
@@ -145,11 +149,14 @@ class DeviceCollection(gtk.TreeView):
pixbuf_renderer = gtk.CellRendererPixbuf()
text_renderer = gtk.CellRendererText()
text_renderer.props.ellipsize = pango.ELLIPSIZE_MIDDLE
- text_renderer.set_fixed_size(160, -1)
+ text_renderer.set_fixed_size(160, -1)
+ eject_renderer = gtk.CellRendererPixbuf()
column0.pack_start(pixbuf_renderer, expand=False)
column0.pack_start(text_renderer, expand=True)
+ column0.pack_end(eject_renderer, expand=False)
column0.add_attribute(pixbuf_renderer, 'pixbuf', 0)
column0.add_attribute(text_renderer, 'text', 1)
+ column0.add_attribute(eject_renderer, 'pixbuf', 5)
self.append_column(column0)
@@ -165,19 +172,49 @@ class DeviceCollection(gtk.TreeView):
self.append_column(column2)
self.show_all()
+ icontheme = gtk.icon_theme_get_default()
+ try:
+ 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,
progress,
- progress_bar_text))
+ progress_bar_text,
+ eject,
+ process_id))
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
+ # e.g. when starting with 3 cards, it could be 18, but when adding 2 cards to the already running program
+ # (with one card at startup), it could be 21
+ row_height = self.get_background_area(0, self.get_column(0))[3] + 1
+ height = (len(self.map_process_to_row) + 1) * row_height
+ self.parent_app.device_collection_scrolledwindow.set_size_request(-1, height)
def update_device(self, process_id, total_size_files):
"""
@@ -187,13 +224,17 @@ class DeviceCollection(gtk.TreeView):
iter = self._get_process_map(process_id)
self.liststore.set_value(iter, 2, total_size_files)
else:
- logger.error("This device is unknown")
+ 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):
"""
@@ -237,6 +278,44 @@ class DeviceCollection(gtk.TreeView):
pass
#~ logger.info("Implement update overall progress")
+ def button_clicked(self, widget, event):
+ """
+ Look for left single click on eject button
+ """
+ if event.button == 1:
+ x = int(event.x)
+ y = int(event.y)
+ path, column, cell_x, cell_y = self.get_path_at_pos(x, y)
+ if path is not None:
+ if column == self.get_column(0):
+ if cell_x >= column.get_width() - self.eject_pixbuf.get_width():
+ 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()
+
+ try:
+ mount.unmount_finish(result)
+ 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()
+
def create_cairo_image_surface(pil_image, image_width, image_height):
imgd = pil_image.tostring("raw","BGRA", 0, 1)
@@ -283,7 +362,6 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
w = cell_area.width
h = cell_area.height
-
#constrain operations to cell area, allowing for a 1 pixel border
#either side
#~ cairo_context.rectangle(x-1, y-1, w+2, h+2)
@@ -316,9 +394,8 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
cairo_context.stroke()
# draw a thin border around each cell
- # ouch - nasty hardcoding :(
- #~ cairo_context.set_source_rgb(0.33, 0.33, 0.33)
- #~ cairo_context.rectangle(x-6.5, y-9.5, w+14, h+31)
+ #~ cairo_context.set_source_rgb(0.33,0.33,0.33)
+ #~ cairo_context.rectangle(x, y, w, h)
#~ cairo_context.stroke()
#place the image
@@ -362,7 +439,6 @@ class ThumbnailCellRenderer(gtk.CellRenderer):
cairo_context.paint()
def do_get_size(self, widget, cell_area):
- #~ return (0, 0, self.image_area_size, self.image_area_size + self.checkbutton_height + 10)
return (0, 0, self.image_area_size, self.image_area_size + self.text_area_size - self.checkbutton_height + 4)
@@ -372,6 +448,10 @@ gobject.type_register(ThumbnailCellRenderer)
class ThumbnailDisplay(gtk.IconView):
def __init__(self, parent_app):
gtk.IconView.__init__(self)
+ self.set_spacing(0)
+ self.set_row_spacing(5)
+ self.set_margin(25)
+
self.rapid_app = parent_app
self.batch_size = 10
@@ -384,7 +464,7 @@ class ThumbnailDisplay(gtk.IconView):
self.rpd_files = {}
- self.total_files = 0
+ self.total_thumbs_to_generate = 0
self.thumbnails_generated = 0
self.thumbnails = {}
@@ -414,16 +494,17 @@ class ThumbnailDisplay(gtk.IconView):
gtk.gdk.Pixbuf, # 8 status icon
)
-
self.clear()
self.set_model(self.liststore)
+
checkbutton = gtk.CellRendererToggle()
checkbutton.set_radio(False)
checkbutton.props.activatable = True
checkbutton.props.xalign = 0.0
checkbutton.connect('toggled', self.on_checkbutton_toggled)
self.pack_end(checkbutton, expand=False)
+
self.add_attribute(checkbutton, "active", 1)
self.add_attribute(checkbutton, "visible", 6)
@@ -431,29 +512,22 @@ class ThumbnailDisplay(gtk.IconView):
checkbutton_height = checkbutton_size[3]
checkbutton_width = checkbutton_size[2]
- #~ status_icon = gtk.CellRendererPixbuf()
- #~ self.pack_start(status_icon, expand=False)
- #~ self.add_attribute(status_icon, "pixbuf", self.STATUS_ICON_COL)
-
image = ThumbnailCellRenderer(checkbutton_height)
self.pack_start(image, expand=True)
self.add_attribute(image, "image", 0)
self.add_attribute(image, "filename", 3)
self.add_attribute(image, "status", 8)
+
#set the background color to a darkish grey
self.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color('#444444'))
- self.set_spacing(0)
- #~ self.set_column_spacing(0)
- self.set_row_spacing(5)
- #~ self.set_row_spacing(0)
- self.set_margin(25)
+ self.show_all()
self._setup_icons()
-
- self.show_all()
+
+
self.connect('item-activated', self.on_item_activated)
@@ -474,7 +548,7 @@ class ThumbnailDisplay(gtk.IconView):
paths.share_dir('glade3/rapid-photo-downloader-downloaded.svg'),
size, size)
self.download_pending_icon = gtk.gdk.pixbuf_new_from_file_at_size(
- paths.share_dir('glade3/rapid-photo-downloader-download-pending.svg'),
+ paths.share_dir('glade3/rapid-photo-downloader-download-pending.png'),
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'),
@@ -523,7 +597,7 @@ class ThumbnailDisplay(gtk.IconView):
iter = self.get_iter_from_unique_id(unique_id)
self.liststore.set_value(iter, self.SELECTED_COL, value)
- def add_file(self, rpd_file):
+ def add_file(self, rpd_file, generate_thumbnail):
thumbnail_icon = self.get_stock_icon(rpd_file.file_type)
unique_id = rpd_file.unique_id
@@ -553,7 +627,8 @@ class ThumbnailDisplay(gtk.IconView):
self.treerow_index[unique_id] = treerowref
self.rpd_files[unique_id] = rpd_file
- self.total_files += 1
+ 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
@@ -701,24 +776,56 @@ class ThumbnailDisplay(gtk.IconView):
return True
return False
- def get_files_checked_for_download(self):
+ 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()
- for row in self.liststore:
- if row[self.SELECTED_COL]:
- rpd_file = self.rpd_files[row[self.UNIQUE_ID_COL]]
+ if scan_pid is None:
+ for row in self.liststore:
+ if row[self.SELECTED_COL]:
+ rpd_file = self.rpd_files[row[self.UNIQUE_ID_COL]]
+ if rpd_file.status not in DOWNLOADED:
+ scan_pid = rpd_file.scan_pid
+ if scan_pid in files:
+ files[scan_pid].append(rpd_file)
+ else:
+ files[scan_pid] = [rpd_file,]
+ else:
+ files[scan_pid] = []
+ for unique_id in self.process_index[scan_pid]:
+ rpd_file = self.rpd_files[unique_id]
if rpd_file.status not in DOWNLOADED:
- scan_pid = rpd_file.scan_pid
- if scan_pid in files:
+ iter = self.get_iter_from_unique_id(unique_id)
+ if self.liststore.get_value(iter, self.SELECTED_COL):
files[scan_pid].append(rpd_file)
- else:
- files[scan_pid] = [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
+ scan_pid
+ """
+ i = 0
+ for unique_id in self.process_index[scan_pid]:
+ rpd_file = self.rpd_files[unique_id]
+ 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
+ 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):
"""
@@ -729,7 +836,11 @@ class ThumbnailDisplay(gtk.IconView):
unique_id = rpd_file.unique_id
self.rpd_files[unique_id].status = STATUS_DOWNLOAD_PENDING
iter = self.get_iter_from_unique_id(unique_id)
- self.liststore.set_value(iter, self.CHECKBUTTON_VISIBLE_COL, False)
+ if not self.rapid_app.auto_start_is_on:
+ # don't make the checkbox invisible immediately when on auto start
+ # otherwise the box can be rendred at the wrong size, as it is
+ # realized after the checkbox has already been made invisible
+ self.liststore.set_value(iter, self.CHECKBUTTON_VISIBLE_COL, False)
self.liststore.set_value(iter, self.SELECTED_COL, False)
self.liststore.set_value(iter, self.DOWNLOAD_STATUS_COL, STATUS_DOWNLOAD_PENDING)
icon = self.get_status_icon(STATUS_DOWNLOAD_PENDING)
@@ -752,6 +863,7 @@ class ThumbnailDisplay(gtk.IconView):
self.liststore.set_value(iter, self.DOWNLOAD_STATUS_COL, rpd_file.status)
icon = self.get_status_icon(rpd_file.status)
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):
@@ -803,12 +915,15 @@ class ThumbnailDisplay(gtk.IconView):
# clear progress bar information if all thumbnails have been
# extracted
- if self.thumbnails_generated == self.total_files:
+ if self.thumbnails_generated == self.total_thumbs_to_generate:
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
+
else:
self.rapid_app.download_progressbar.set_fraction(
- float(self.thumbnails_generated) / self.total_files)
+ float(self.thumbnails_generated) / self.total_thumbs_to_generate)
return True
@@ -865,11 +980,13 @@ class TaskManager:
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
@@ -982,10 +1099,12 @@ class CopyFilesManager(TaskManager):
video_download_folder = task[1]
scan_pid = task[2]
files = task[3]
+ generate_thumbnails = task[4]
copy_files = copyfiles.CopyFiles(photo_download_folder,
video_download_folder,
- files, scan_pid, self.batch_size,
+ files, generate_thumbnails,
+ scan_pid, self.batch_size,
task_process_conn, terminate_queue, run_event)
copy_files.start()
self._processes.append((copy_files, terminate_queue, run_event))
@@ -1188,6 +1307,7 @@ class RapidApp(dbus.service.Object):
# Initialize widgets in the main window, and variables that point to them
self._init_widgets()
+ self._init_pynotify()
# Initialize job code handling
self._init_job_code()
@@ -1205,8 +1325,9 @@ class RapidApp(dbus.service.Object):
self.rapidapp.show()
# Check program preferences - don't allow auto start if there is a problem
- prefs_valid = prefsrapid.check_prefs_for_validity(self.prefs)
- do_not_allow_auto_start = prefs_valid
+ 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()
@@ -1218,12 +1339,10 @@ class RapidApp(dbus.service.Object):
# Setup devices from which to download from and backup to
self.setup_devices(on_startup=True, on_preference_change=False,
- do_not_allow_auto_start=do_not_allow_auto_start)
+ block_auto_start=not prefs_valid)
# Ensure the device collection scrolled window is not too small
self._set_device_collection_size()
-
- #~ preferencesdialog.PreferencesDialog(self)
def on_rapidapp_destroy(self, widget, data=None):
@@ -1312,7 +1431,7 @@ class RapidApp(dbus.service.Object):
def on_refresh_action_activate(self, action):
self.setup_devices(on_startup=False, on_preference_change=False,
- do_not_allow_auto_start=True)
+ block_auto_start=True)
def on_get_help_action_activate(self, action):
webbrowser.open("http://www.damonlynch.net/rapid/help.html")
@@ -1379,7 +1498,7 @@ class RapidApp(dbus.service.Object):
self.vmonitor.connect("mount-removed", self.on_mount_removed)
- def setup_devices(self, on_startup, on_preference_change, do_not_allow_auto_start):
+ def setup_devices(self, on_startup, on_preference_change, block_auto_start):
"""
Setup devices from which to download from and backup to
@@ -1392,6 +1511,9 @@ class RapidApp(dbus.service.Object):
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
"""
@@ -1449,7 +1571,7 @@ class RapidApp(dbus.service.Object):
# Display amount of free space in a status bar message
self.display_free_space()
- if do_not_allow_auto_start:
+ if block_auto_start:
self.auto_start_is_on = False
else:
self.auto_start_is_on = ((not on_preference_change) and
@@ -1475,6 +1597,8 @@ class RapidApp(dbus.service.Object):
scan_pid = self.scan_manager.add_task(device)
if mount is not None:
self.mounts_by_path[path] = scan_pid
+ if not mounts:
+ self.set_download_action_sensitivity()
def get_use_device(self, device):
""" Prompt user whether or not to download from this device """
@@ -1553,28 +1677,30 @@ class RapidApp(dbus.service.Object):
return
path = mount.get_root().get_path()
+ if path is not None:
- if path in self.prefs.device_blacklist and self.search_for_PSD():
- logger.info("Device %(device)s (%(path)s) ignored" % {
- 'device': mount.get_name(), 'path': path})
- else:
- is_backup_mount = self.check_if_backup_mount(path)
-
- if is_backup_mount:
- if path not in self.backup_devices:
- self.backup_devices[path] = mount
- self.display_free_space()
-
- elif self.prefs.device_autodetection and (dv.is_DCIM_device(path) or
- self.search_for_PSD()):
-
- 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.scan_manager.add_task(device)
- self.mounts_by_path[path] = scan_pid
+ if path in self.prefs.device_blacklist and self.search_for_PSD():
+ logger.info("Device %(device)s (%(path)s) ignored" % {
+ 'device': mount.get_name(), 'path': path})
+ else:
+ is_backup_mount = self.check_if_backup_mount(path)
+
+ if is_backup_mount:
+ if path not in self.backup_devices:
+ self.backup_devices[path] = mount
+ self.display_free_space()
+
+ 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.scan_manager.add_task(device)
+ self.mounts_by_path[path] = scan_pid
def on_mount_removed(self, vmonitor, mount):
"""
@@ -1643,7 +1769,10 @@ class RapidApp(dbus.service.Object):
logger.debug("Download activated")
if self.download_action_is_download:
- self.start_download()
+ if self.need_job_code_for_naming and not self.prompting_for_job_code:
+ self.get_job_code()
+ else:
+ self.start_download()
else:
self.pause_download()
@@ -1663,10 +1792,10 @@ class RapidApp(dbus.service.Object):
"""
if not self.download_is_occurring():
sensitivity = False
- if self.scan_manager.get_no_active_processes() == 0:
+ 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):
@@ -1687,21 +1816,19 @@ class RapidApp(dbus.service.Object):
def _init_job_code(self):
- self.job_code = None
+ self.job_code = self.last_chosen_job_code = ''
self.need_job_code_for_naming = self.prefs.any_pref_uses_job_code()
-
- def assign_job_code(self, code):
- """ assign job code (which may be empty) to global variable and update user preferences
+ self.prompting_for_job_code = False
+
+ 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.
"""
- # FIXME
- #~ global job_code
- if code == None:
- code = ''
- job_code = code
+
+ self.job_code = code
- if job_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)
@@ -1713,7 +1840,7 @@ class RapidApp(dbus.service.Object):
self.prefs.job_codes = [code] + jcs
- def _get_job_code(self, post_job_code_entry_callback, autoStart, downloadSelected):
+ def _get_job_code(self, post_job_code_entry_callback):
""" prompt for a job code """
if not self.prompting_for_job_code:
@@ -1735,29 +1862,19 @@ class RapidApp(dbus.service.Object):
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
- #~ self.selection_vbox.selection_treeview.apply_job_code(code, overwrite=False, to_all_rows = not downloadSelected)
- #~ threads = self.selection_vbox.selection_treeview.set_status_to_download_pending(selected_only = downloadSelected)
- #~ if downloadSelected or not autoStart:
- #~ logger.debug("Starting downloads")
- #~ self.startDownload(threads)
- #~ else:
- #~ # autostart is true
- #~ logger.debug("Starting downloads that have been waiting for a Job Code")
- #~ for w in workers.getWaitingForJobCodeWorkers():
- #~ w.startStop()
+ logger.debug("Job Code %s entered", self.job_code)
+ self.start_download()
else:
# user cancelled
- pass
- #~ logger.debug("No Job Code entered")
- #~ for w in workers.getWaitingForJobCodeWorkers():
- #~ w.waitingForJobCode = False
- #~
- #~ if autoStart:
- #~ for w in workers.getAutoStartWorkers():
- #~ w.autoStart = False
+ logger.debug("No Job Code entered")
+ self.job_code = ''
+ self.auto_start_is_on = False
+
# # #
# Download
@@ -1780,13 +1897,14 @@ class RapidApp(dbus.service.Object):
- def start_download(self):
+ 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
"""
- self.download_start_time = datetime.datetime.now()
- files_by_scan_pid = self.thumbnails.get_files_checked_for_download()
+ 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:
@@ -1798,6 +1916,11 @@ class RapidApp(dbus.service.Object):
self.log_error(config.CRITICAL_ERROR, _("Download cannot proceed"),
msg)
else:
+ # 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.thumbnails.mark_download_pending(files_by_scan_pid)
for scan_pid in files_by_scan_pid:
files = files_by_scan_pid[scan_pid]
@@ -1813,7 +1936,14 @@ class RapidApp(dbus.service.Object):
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):
@@ -1831,16 +1961,25 @@ class RapidApp(dbus.service.Object):
video_download_folder = self.prefs.video_download_folder
else:
video_download_folder = None
-
+
+ download_size = self.size_files_to_be_downloaded(files)
self.download_tracker.init_stats(scan_pid=scan_pid,
- bytes=self.size_files_to_be_downloaded(files),
+ bytes=download_size,
no_files=len(files))
+
+ 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
+
# Initiate copy files process
self.copy_files_manager.add_task((photo_download_folder,
video_download_folder, scan_pid,
- files))
+ files, self.auto_start_is_on))
def copy_files_results(self, source, condition):
"""
@@ -1856,12 +1995,14 @@ class RapidApp(dbus.service.Object):
scan_pid, photo_temp_dir, video_temp_dir = data
self.temp_dirs_by_scan_pid[scan_pid] = (photo_temp_dir, video_temp_dir)
elif msg_type == rpdmp.MSG_BYTES:
- scan_pid, total_downloaded = data
+ scan_pid, total_downloaded, chunk_downloaded = data
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)
self.device_collection.update_progress(scan_pid, percent_complete,
None, None)
+ self.time_remaining.update(scan_pid, total_downloaded)
elif msg_type == rpdmp.MSG_FILE:
download_succeeded, rpd_file, download_count, temp_full_file_name = data
@@ -1877,14 +2018,18 @@ class RapidApp(dbus.service.Object):
rpd_file.strip_characters = self.prefs.strip_characters
rpd_file.download_folder = self.prefs.get_download_folder_for_file_type(rpd_file.file_type)
rpd_file.download_conflict_resolution = self.prefs.download_conflict_resolution
- rpd_file.synchronize_raw_jpg = self.prefs.must_synchronize_raw_jpg()
+ 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,
rpd_file
)
-
+ elif msg_type == rpdmp.MSG_THUMB:
+ #~ unique_id, thumbnail, thumbnail_icon = data
+ #~ thumbnail_data = (unique_id
+ self.thumbnails.update_thumbnail(data)
return True
else:
@@ -1897,7 +2042,9 @@ class RapidApp(dbus.service.Object):
def download_is_occurring(self):
"""Returns True if a file is currently being downloaded or renamed
"""
- return not len(self.download_active_by_scan_pid) == 0
+ v = not len(self.download_active_by_scan_pid) == 0
+ #~ logger.info("Download is occurring: %s", v)
+ return v
# # #
# Create folder and file names for downloaded files
@@ -1921,48 +2068,233 @@ class RapidApp(dbus.service.Object):
self.log_error(config.WARNING, rpd_file.error_title,
rpd_file.error_msg, rpd_file.error_extra_detail)
- self.download_tracker.file_downloaded_increment(scan_pid)
- self._update_file_download_device_progress(scan_pid, unique_id)
+ 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)
+
+ if self.download_is_occurring():
+ self.update_time_remaining()
- download_count = self.download_tracker.get_download_count_for_file(unique_id)
- if download_count == self.download_tracker.get_no_files_in_download(scan_pid):
- # Last file has been downloaded, so clean temp directory
+ if completed:
+ # Last file for this scan pid has been downloaded, so clean temp directory
logger.debug("Purging temp directories")
self._clean_temp_dirs_for_scan_pid(scan_pid)
- self.download_tracker.purge(scan_pid)
self.download_active_by_scan_pid.remove(scan_pid)
+ self.time_remaining.remove(scan_pid)
+ 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.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())
+ or self.prefs.auto_exit_force):
+ if not self.thumbnails.files_remain_to_download():
+ 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
+ elif secs == 60:
+ message = _("About 1 minute remaining")
+ else:
+ # 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)
+
+ 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
+ """
+ if (no_videos > 0) and (no_photos > 0):
+ v = _('photos and videos')
+ elif (no_videos == 0) and (no_photos == 0):
+ v = _('photos or videos')
+ elif no_videos > 0:
+ if no_videos > 1:
+ v = _('videos')
+ else:
+ v = _('video')
else:
- pass
- #~ logger.info("Download count: %s", download_count)
-
+ if no_photos > 1:
+ v = _('photos')
+ else:
+ v = _('photo')
+ return v
+ def notify_downloaded_from_device(self, scan_pid):
+ device = self.device_collection.get_device(scan_pid)
+
+ if device.mount is None:
+ notificationName = PROGRAM_NAME
+ else:
+ notificationName = device.get_name()
+
+ 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(
+ scan_pid, rpdfile.FILE_TYPE_VIDEO)
+ no_photos_failed = self.download_tracker.get_no_files_failed(
+ scan_pid, rpdfile.FILE_TYPE_PHOTO)
+ no_videos_failed = self.download_tracker.get_no_files_failed(
+ scan_pid, rpdfile.FILE_TYPE_VIDEO)
+ 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(notificationName, message)
+ n.set_icon_from_pixbuf(device.get_icon(self.notification_icon_size))
+
+ 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,
+ 'numberdownloaded': _("%(filetype)s downloaded") % \
+ {'filetype': filetype}}
+
+ # photo failures
+ photo_failures = self.download_tracker.total_photo_failures
+ if photo_failures:
+ filetype = self.file_types_by_number(photo_failures, 0)
+ message += "\n" + _("%(number)s %(numberdownloaded)s") % \
+ {'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,
+ 'numberdownloaded': _("%(filetype)s downloaded") % \
+ {'filetype': filetype}}
+
+ # video failures
+ video_failures = self.download_tracker.total_video_failures
+ if video_failures:
+ filetype = self.file_types_by_number(0, video_failures)
+ message += "\n" + _("%(number)s %(numberdownloaded)s") % \
+ {'number': video_failures,
+ 'numberdownloaded': _("%(filetype)s failed to download") % \
+ {'filetype': filetype}}
+
+ # warnings
+ warnings = self.download_tracker.total_warnings
+ if warnings:
+ message += "\n" + _("%(number)s %(numberdownloaded)s") % \
+ {'number': warnings,
+ 'numberdownloaded': _("warnings")}
+
+ 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):
"""
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
"""
- progress_bar_text = _("%(number)s of %(total)s %(filetypes)s") % \
- {'number': self.download_tracker.get_download_count_for_file(unique_id),
- 'total': self.download_tracker.get_no_files_in_download(scan_pid),
- 'filetypes': self.download_tracker.get_file_types_present(scan_pid)}
+
+ 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:
+ 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,
+ '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,
+ '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,
- bytes_downloaded=None)
+ 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):
"""
@@ -1999,7 +2331,6 @@ class RapidApp(dbus.service.Object):
children = path.enumerate_children(file_attributes)
for child in children:
f = path.get_child(child.get_name())
- #~ logger.info("Deleting %s", child.get_name())
f.delete(cancellable=None)
path.delete(cancellable=None)
logger.debug("Deleted directory %s", directory)
@@ -2011,20 +2342,6 @@ class RapidApp(dbus.service.Object):
# # #
# Preferences
# # #
-
- def check_prefs_on_startup(self):
- """
- Checks the image & video rename, and subfolder prefs for validity.
-
- Returns True if no problem, false otherwise.
- """
- prefs_ok = prefsrapid.check_prefs_for_validity(self.prefs.image_rename,
- self.prefs.subfolder,
- self.prefs.video_rename,
- self.prefs.video_subfolder)
- if not prefs_ok:
- logger.error("There is an error in the program preferences relating to file renaming and subfolder creation. Some preferences will be reset.")
- return prefs_ok
def _init_prefs(self):
@@ -2115,12 +2432,13 @@ class RapidApp(dbus.service.Object):
def post_preference_change(self):
if self.rerun_setup_available_image_and_video_media:
- if self.using_volume_monitor():
- self.start_volume_monitor()
+
logger.info("Download device settings preferences were changed.")
self.thumbnails.clear_all()
- self.setup_devices(on_startup = False, on_preference_change = True, do_not_allow_auto_start = True)
+ 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)
@@ -2148,6 +2466,36 @@ class RapidApp(dbus.service.Object):
# Main app window management and setup
# # #
+ def _init_pynotify(self):
+ """
+ Initialize system notification messages
+ """
+
+ if not pynotify.init("TestCaps"):
+ logger.critical("Problem using pynotify.")
+ gtk.main_quit()
+
+ do_not_size_icon = False
+ self.notification_icon_size = 48
+ try:
+ info = pynotify.get_server_info()
+ except:
+ logger.warning("Desktop environment notification server is incorrectly configured.")
+ else:
+ try:
+ if info["name"] == 'notify-osd':
+ 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'))
+ else:
+ self.application_icon = gtk.gdk.pixbuf_new_from_file_at_size(
+ paths.share_dir('glade3/rapid-photo-downloader.svg'),
+ self.notification_icon_size, self.notification_icon_size)
+
def _init_widgets(self):
"""
Initialize widgets in the main window, and variables that point to them
@@ -2167,6 +2515,7 @@ class RapidApp(dbus.service.Object):
self.next_image_action = builder.get_object("next_image_action")
self.prev_image_action = builder.get_object("prev_image_action")
self.menu_log_window = builder.get_object("menu_log_window")
+ self.speed_label = builder.get_object("speed_label")
# Only enable this action when actually displaying a preview
self.next_image_action.set_sensitive(False)
@@ -2200,9 +2549,17 @@ class RapidApp(dbus.service.Object):
# Download action state
self.download_action_is_download = True
- #job code initialization
- self.last_chosen_job_code = None
- self.prompting_for_job_code = False
+ # 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 _set_window_size(self):
"""
@@ -2375,6 +2732,12 @@ 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)
# # #
@@ -2576,6 +2939,7 @@ class RapidApp(dbus.service.Object):
if conn_type == rpdmp.CONN_COMPLETE:
connection.close()
+ self.scan_manager.no_tasks -= 1
size, file_type_counter, scan_pid = data
size = format_size_for_user(bytes=size)
results_summary, file_types_present = file_type_counter.summarize_file_count()
@@ -2585,13 +2949,20 @@ 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)
self.testing_auto_exit_trip_counter += 1
+ self.set_download_action_sensitivity()
+
if self.testing_auto_exit_trip_counter == self.testing_auto_exit_trip and self.testing_auto_exit:
self.on_rapidapp_destroy(self.rapidapp)
else:
- if not self.testing_auto_exit:
+ if not self.testing_auto_exit and not self.auto_start_is_on:
self.download_progressbar.set_text(_("Thumbnails"))
self.thumbnails.generate_thumbnails(scan_pid)
- self.set_download_action_sensitivity()
+ elif self.auto_start_is_on:
+ if self.need_job_code_for_naming and not self.job_code:
+ self.get_job_code()
+ else:
+ self.start_download(scan_pid=scan_pid)
+
self.set_thumbnail_sort()
# signal that no more data is coming, finishing io watch for this pipe
@@ -2601,7 +2972,8 @@ class RapidApp(dbus.service.Object):
logger.critical("incoming pipe length is unexpectedly long: %s" % len(data))
else:
for rpd_file in data:
- self.thumbnails.add_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
diff --git a/rapid/rpdmultiprocessing.py b/rapid/rpdmultiprocessing.py
index 7fdd252..4a06fc9 100644
--- a/rapid/rpdmultiprocessing.py
+++ b/rapid/rpdmultiprocessing.py
@@ -21,5 +21,6 @@ CONN_COMPLETE = 1
MSG_BYTES = 0
MSG_FILE = 1
MSG_TEMP_DIRS = 2
+MSG_THUMB = 3
MSG_SEQUENCE_VALUE = 0
diff --git a/rapid/subfolderfile.py b/rapid/subfolderfile.py
index c55b6d8..7e4a495 100644
--- a/rapid/subfolderfile.py
+++ b/rapid/subfolderfile.py
@@ -328,7 +328,11 @@ class SubfolderFile(multiprocessing.Process):
if rpd_file.has_problem():
rpd_file.status = config.STATUS_DOWNLOADED_WITH_WARNING
-
+ rpd_file.error_title = rpd_file.problem.get_title()
+ rpd_file.error_msg = _("%(problem)s\nFile: %(file)s") % \
+ {'problem': rpd_file.problem.get_problems(),
+ 'file': rpd_file.full_file_name}
+
# Check for any errors
if not rpd_file.download_subfolder or not rpd_file.download_name:
if not rpd_file.download_subfolder and not rpd_file.download_name:
diff --git a/rapid/thumbnail.py b/rapid/thumbnail.py
index 9df2147..e9c7e66 100644
--- a/rapid/thumbnail.py
+++ b/rapid/thumbnail.py
@@ -374,11 +374,9 @@ class GenerateThumbnails(multiprocessing.Process):
f.file_type,
(160, 120), (100,100))
- #~ logger.debug("Appending results for %s" %f.full_file_name)
self.results.append((f.unique_id, thumbnail_icon, thumbnail))
counter += 1
if counter == self.batch_size:
- #~ logger.debug("Sending results....")
self.results_pipe.send((rpdmp.CONN_PARTIAL, self.results))
self.results = []
counter = 0
@@ -386,7 +384,6 @@ class GenerateThumbnails(multiprocessing.Process):
if counter > 0:
# send any remaining results
- #~ logger.debug("Sending final results....")
self.results_pipe.send((rpdmp.CONN_PARTIAL, self.results))
self.results_pipe.send((rpdmp.CONN_COMPLETE, None))
self.results_pipe.close()