aboutsummaryrefslogtreecommitdiff
path: root/graphics/rapid-photo-downloader
diff options
context:
space:
mode:
authorKurt Jaeger <pi@FreeBSD.org>2019-09-21 18:45:22 +0000
committerKurt Jaeger <pi@FreeBSD.org>2019-09-21 18:45:22 +0000
commitafc03086202ce696e54c801d26c215310f4e2e31 (patch)
tree499d2ee3d97c9fc488cdeeaf3faf719b839a252f /graphics/rapid-photo-downloader
parente6fb62b39052db178d65bec4b03ea125f203774f (diff)
downloadports-afc03086202ce696e54c801d26c215310f4e2e31.tar.gz
ports-afc03086202ce696e54c801d26c215310f4e2e31.zip
New port: graphics/rapid-photo-downloader
Rapid Photo Downloader imports photos and videos from cameras, phones, memory cards and other devices at high speed. It can be configured to rename photos and videos with meaningful filenames you specify. It can also back up photos and videos as they are downloaded. It downloads from and backs up to multiple devices simultaneously. WWW: http://www.damonlynch.net/rapid/ PR: 240601 Submitted by: m.ne@gmx.net Reviewed by: koobs
Notes
Notes: svn path=/head/; revision=512533
Diffstat (limited to 'graphics/rapid-photo-downloader')
-rw-r--r--graphics/rapid-photo-downloader/Makefile52
-rw-r--r--graphics/rapid-photo-downloader/distinfo3
-rw-r--r--graphics/rapid-photo-downloader/files/patch-raphodo_cache.py11
-rw-r--r--graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py140
-rw-r--r--graphics/rapid-photo-downloader/files/patch-raphodo_storage.py319
-rw-r--r--graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py62
-rw-r--r--graphics/rapid-photo-downloader/pkg-descr7
7 files changed, 594 insertions, 0 deletions
diff --git a/graphics/rapid-photo-downloader/Makefile b/graphics/rapid-photo-downloader/Makefile
new file mode 100644
index 000000000000..660ea37c27db
--- /dev/null
+++ b/graphics/rapid-photo-downloader/Makefile
@@ -0,0 +1,52 @@
+# $FreeBSD$
+
+PORTNAME= rapid-photo-downloader
+DISTVERSION= 0.9.17
+CATEGORIES= graphics
+MASTER_SITES= https://launchpad.net/rapid/pyqt/${PORTVERSION}/+download/
+
+MAINTAINER= m.ne@gmx.net
+COMMENT= Import photos and videos efficiently and reliably
+
+LICENSE= GPLv3
+
+BUILD_DEPENDS= intltool-update:textproc/intltool
+LIB_DEPENDS= libgexiv2.so:graphics/gexiv2 \
+ libgudev-1.0.so:devel/libgudev
+RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}arrow>0:devel/py-arrow@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}colour>0:graphics/py-colour@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}dateutil>2.2:devel/py-dateutil@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}easygui>=0:x11-toolkits/py-easygui@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}gobject3>0:devel/py-gobject3@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}gphoto2>=1.4.0:graphics/py-gphoto2@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}gstreamer1>=1.0:multimedia/py-gstreamer1@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}notify2>0:devel/py-notify2@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}psutil>=3.4.2:sysutils/py-psutil@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}pymediainfo>=1.0:multimedia/py-pymediainfo@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}pyzmq>0:net/py-pyzmq@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}rawkit>0:graphics/py-rawkit@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}requests>=0:www/py-requests@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}sortedcontainers>0:devel/py-sortedcontainers@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}tenacity>0:devel/py-tenacity@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}tornado>=4.1:www/py-tornado@${PY_FLAVOR} \
+ ${PYTHON_PKGNAMEPREFIX}xdg>0:devel/py-xdg@${PY_FLAVOR} \
+ exiftool:graphics/p5-Image-ExifTool
+
+USES= desktop-file-utils gettext pyqt:5 python:3.4+ qt:5
+USE_PYTHON= autoplist distutils
+USE_QT= imageformats_run
+USE_PYQT= core gui network sip widgets
+
+BINARY_ALIAS= python3=${PYTHON_VERSION}
+NO_ARCH= yes
+
+OPTIONS_DEFINE= COLOUR PROGRESS
+OPTIONS_DEFAULT= COLOUR PROGRESS
+
+COLOUR_DESC= generates coloured program output
+PROGRESS_DESC= shows a progress bar on the command line
+
+COLOUR_RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}colorlog>0:devel/py-colorlog@${PY_FLAVOR}
+PROGRESS_RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}pyprind>=1.4.0:misc/py-pyprind@${PY_FLAVOR}
+
+.include <bsd.port.mk>
diff --git a/graphics/rapid-photo-downloader/distinfo b/graphics/rapid-photo-downloader/distinfo
new file mode 100644
index 000000000000..6258f9cb7a78
--- /dev/null
+++ b/graphics/rapid-photo-downloader/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1568565754
+SHA256 (rapid-photo-downloader-0.9.17.tar.gz) = 26dbce5d2e775af39ce8f17224a862ed71a86a47768a7ebb04193d96535c7883
+SIZE (rapid-photo-downloader-0.9.17.tar.gz) = 6877573
diff --git a/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py b/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py
new file mode 100644
index 000000000000..ee5116b0934e
--- /dev/null
+++ b/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py
@@ -0,0 +1,11 @@
+--- raphodo/cache.py.orig 2019-07-31 17:16:30 UTC
++++ raphodo/cache.py
+@@ -131,7 +131,7 @@ class Cache:
+ not be generated)
+ """
+
+- assert sys.platform.startswith('linux')
++ assert sys.platform.startswith('linux') or sys.platform.startswith('freebsd')
+ self.cache_dir = cache_dir
+ self.failure_dir = failure_dir
+ assert self.cache_dir
diff --git a/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py b/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py
new file mode 100644
index 000000000000..26fc07391c24
--- /dev/null
+++ b/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py
@@ -0,0 +1,140 @@
+--- raphodo/rapid.py.orig 2019-08-18 03:58:11 UTC
++++ raphodo/rapid.py
+@@ -98,7 +98,7 @@ from PyQt5.QtNetwork import QLocalSocket, QLocalServer
+ import sip
+
+ from raphodo.storage import (
+- ValidMounts, CameraHotplug, UDisks2Monitor, GVolumeMonitor, have_gio,
++ ValidMounts, CameraHotplug, GVolumeMonitor, have_gio,
+ has_one_or_more_folders, mountPaths, get_desktop_environment, get_desktop,
+ gvfs_controls_mounts, get_default_file_manager, validate_download_folder,
+ validate_source_folder, get_fdo_cache_thumb_base_directory, WatchDownloadDirs, get_media_dir,
+@@ -584,12 +584,12 @@ class RapidWindow(QMainWindow):
+ self.prefs.backup_files = backup
+ else:
+ logging.info("Backing up files: %s", self.prefs.backup_files)
+-
++
+ if backup_auto_detect is not None:
+ self.prefs.backup_device_autodetection = backup_auto_detect
+ elif self.prefs.backup_files:
+ logging.info("Backup device auto detection: %s", self.prefs.backup_device_autodetection)
+-
++
+ if photo_backup_identifier is not None:
+ self.prefs.photo_backup_identifier = photo_backup_identifier
+ elif self.prefs.backup_files and self.prefs.backup_device_autodetection:
+@@ -599,7 +599,7 @@ class RapidWindow(QMainWindow):
+ self.prefs.video_backup_identifier = video_backup_identifier
+ elif self.prefs.backup_files and self.prefs.backup_device_autodetection:
+ logging.info("video backup identifier: %s", self.prefs.video_backup_identifier)
+-
++
+ if photo_backup_location is not None:
+ self.prefs.backup_photo_location = photo_backup_location
+ elif self.prefs.backup_files and not self.prefs.backup_device_autodetection:
+@@ -934,18 +934,6 @@ class RapidWindow(QMainWindow):
+ logging.debug("Starting camera hotplug monitor...")
+ QTimer.singleShot(0, self.cameraHotplugThread.start)
+
+- # Monitor when the user adds or removes a partition
+- self.udisks2Monitor = UDisks2Monitor(self.validMounts)
+- self.udisks2MonitorThread = QThread()
+- self.udisks2MonitorThread.started.connect(self.udisks2Monitor.startMonitor)
+- self.udisks2Unmount.connect(self.udisks2Monitor.unmount_volume)
+- self.udisks2Monitor.moveToThread(self.udisks2MonitorThread)
+- self.udisks2Monitor.partitionMounted.connect(self.partitionMounted)
+- self.udisks2Monitor.partitionUnmounted.connect(self.partitionUmounted)
+- # Start the monitor only on the thread it will be running on
+- logging.debug("Starting UDisks2 monitor...")
+- QTimer.singleShot(0, self.udisks2MonitorThread.start)
+-
+ if self.gvfsControlsMounts:
+ # Gio.VolumeMonitor must be in the main thread, according to
+ # Gnome documentation
+@@ -2119,7 +2107,7 @@ class RapidWindow(QMainWindow):
+ select_text=_('Select a destination folder')
+ )
+ self.photoDestination.addWidget(self.photoDestinationWidget)
+-
++
+ self.videoDestinationDisplay = DestinationDisplay(
+ menu=True, file_type=FileType.video, parent=self
+ )
+@@ -2582,11 +2570,11 @@ class RapidWindow(QMainWindow):
+
+ body = _(
+ r"""Please report the problem at <a href="{website}">{website}</a>.<br><br>
+- Include in your bug report the program's log files. The bug report must include
+- <i>{log_file}</i>, but attaching the other log files is often helpful.<br><br>
++ Include in your bug report the program's log files. The bug report must include
++ <i>{log_file}</i>, but attaching the other log files is often helpful.<br><br>
+ If possible, please also include the program's configuration file
+- <i>{config_file}</i>.<br><br>
+- Click <a href="{log_path}">here</a> to open the log directory, and
++ <i>{config_file}</i>.<br><br>
++ Click <a href="{log_path}">here</a> to open the log directory, and
+ <a href="{config_path}">here</a> to open the configuration directory.
+ """
+ ).format(
+@@ -2622,7 +2610,7 @@ class RapidWindow(QMainWindow):
+
+ :param message: the text to display
+ :param rich_text: whether it text to display is in HTML format
+- :param title: optional title for message box, else defaults to
++ :param title: optional title for message box, else defaults to
+ localized 'Rapid Photo Downloader'
+ :return: the message box
+ """
+@@ -4652,8 +4640,6 @@ Do you want to proceed with the download?
+ self.sendTerminateToThread(self.backup_controller)
+
+ if not self.gvfsControlsMounts:
+- self.udisks2MonitorThread.quit()
+- self.udisks2MonitorThread.wait()
+ self.cameraHotplugThread.quit()
+ self.cameraHotplugThread.wait()
+ else:
+@@ -5254,7 +5240,7 @@ Do you want to proceed with the download?
+ After a preference change, rescan already scanned devices
+ :param ignore_cameras: if True, don't rescan cameras
+ :param rescan_path: if True, include manually specified paths
+- (i.e. This Computer)
++ (i.e. This Computer)
+ """
+
+ if rescan_path:
+@@ -6243,7 +6229,7 @@ def main():
+ logger = iplogging.setup_main_process_logging(logging_level=logging_level)
+
+ logging.info("Rapid Photo Downloader is starting")
+-
++
+ if args.photo_renaming:
+ photo_rename = args.photo_renaming == 'on'
+ if photo_rename:
+@@ -6252,7 +6238,7 @@ def main():
+ logging.info("Photo renaming turned off from command line")
+ else:
+ photo_rename = None
+-
++
+ if args.video_renaming:
+ video_rename = args.video_renaming == 'on'
+ if video_rename:
+@@ -6313,13 +6299,13 @@ def main():
+ logging.info("This Computer path set from command line: %s", this_computer_location)
+ else:
+ this_computer_location=None
+-
++
+ if args.photo_location:
+ photo_location = os.path.abspath(args.photo_location)
+ logging.info("Photo location set from command line: %s", photo_location)
+ else:
+ photo_location=None
+-
++
+ if args.video_location:
+ video_location = os.path.abspath(args.video_location)
+ logging.info("video location set from command line: %s", video_location)
diff --git a/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py b/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py
new file mode 100644
index 000000000000..095d0263348c
--- /dev/null
+++ b/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py
@@ -0,0 +1,319 @@
+--- raphodo/storage.py.orig 2019-07-09 21:12:19 UTC
++++ raphodo/storage.py
+@@ -68,10 +68,9 @@ import xdg
+ import gi
+
+ gi.require_version('GUdev', '1.0')
+-gi.require_version('UDisks', '2.0')
+ gi.require_version('GExiv2', '0.10')
+ gi.require_version('GLib', '2.0')
+-from gi.repository import GUdev, UDisks, GLib
++from gi.repository import GUdev, GLib
+
+ from gettext import gettext as _
+
+@@ -170,7 +169,7 @@ def get_media_dir() -> str:
+
+ """
+
+- if sys.platform.startswith('linux'):
++ if sys.platform.startswith('linux') or sys.platform.startswith('freebsd'):
+ media_dir = '/media/{}'.format(get_user_name())
+ run_media_dir = '/run{}'.format(media_dir)
+ distro = get_distro()
+@@ -278,7 +277,7 @@ class ValidMounts():
+ self.validMountFolders, e.g. /media/<USER>, etc.
+ """
+
+- if not sys.platform.startswith('linux'):
++ if not sys.platform.startswith('linux') and not sys.platform.startswith('freebsd'):
+ raise ("Mounts.setValidMountPoints() not implemented on %s", sys.platform())
+ else:
+ try:
+@@ -646,7 +645,7 @@ def get_default_file_manager() -> Tuple[Optional[str],
+
+ _default_file_manager_probed = True
+
+- assert sys.platform.startswith('linux')
++ assert sys.platform.startswith('linux') or sys.platform.startswith('freebsd')
+ cmd = shlex.split('xdg-mime query default inode/directory')
+ try:
+ desktop_file = subprocess.check_output(cmd, universal_newlines=True) # type: str
+@@ -791,7 +790,7 @@ def validate_download_folder(path: Optional[str],
+
+ :param path: path to analyze
+ :param write_on_waccesss_failure: if os.access reports path is not writable, test
+- nonetheless to see if it's writable by writing and deleting a test file
++ nonetheless to see if it's writable by writing and deleting a test file
+ :return: Tuple indicating validity and path made absolute
+
+ >>> validate_download_folder('/some/bogus/and/ridiculous/path')
+@@ -1008,259 +1007,6 @@ class CameraHotplug(QObject):
+ self.cameraRemoved.emit()
+
+
+-class UDisks2Monitor(QObject):
+- # Most of this class is Copyright 2008-2015 Canonical
+-
+- partitionMounted = pyqtSignal(str, list, bool)
+- partitionUnmounted = pyqtSignal(str)
+-
+- loop_prefix = '/org/freedesktop/UDisks2/block_devices/loop'
+- not_interesting = (
+- '/org/freedesktop/UDisks2/block_devices/dm_',
+- '/org/freedesktop/UDisks2/block_devices/ram',
+- '/org/freedesktop/UDisks2/block_devices/zram',
+- )
+-
+- def __init__(self, validMounts: ValidMounts) -> None:
+- super().__init__()
+- self.validMounts = validMounts
+-
+- @pyqtSlot()
+- def startMonitor(self) -> None:
+- self.udisks = UDisks.Client.new_sync(None)
+- self.manager = self.udisks.get_object_manager()
+- self.manager.connect('object-added',
+- lambda man, obj: self._udisks_obj_added(obj))
+- self.manager.connect('object-removed',
+- lambda man, obj: self._device_removed(obj))
+-
+- # Track the paths of the mount points, which is useful when unmounting
+- # objects.
+- self.known_mounts = {} # type: Dict[str, str]
+- for obj in self.manager.get_objects():
+- path = obj.get_object_path()
+- fs = obj.get_filesystem()
+- if fs:
+- mount_points = fs.get_cached_property('MountPoints').get_bytestring_array()
+- if mount_points:
+- self.known_mounts[path] = mount_points[0]
+- logging.debug("... UDisks2 monitor started")
+-
+- def _udisks_obj_added(self, obj) -> None:
+- path = obj.get_object_path()
+- for boring in self.not_interesting:
+- if path.startswith(boring):
+- return
+- block = obj.get_block()
+- if not block:
+- return
+-
+- drive = self._get_drive(block)
+-
+- part = obj.get_partition()
+- is_system = block.get_cached_property('HintSystem').get_boolean()
+- is_loop = path.startswith(self.loop_prefix) and not \
+- block.get_cached_property('ReadOnly').get_boolean()
+- if not is_system or is_loop:
+- if part:
+- self._udisks_partition_added(obj, block, drive, path)
+-
+- def _get_drive(self, block) -> Optional[UDisks.Drive]:
+- drive_name = block.get_cached_property('Drive').get_string()
+- if drive_name != '/':
+- return self.udisks.get_object(drive_name).get_drive()
+- else:
+- return None
+-
+- def _udisks_partition_added(self, obj, block, drive, path) -> None:
+- logging.debug('UDisks: partition added: %s' % path)
+- fstype = block.get_cached_property('IdType').get_string()
+- logging.debug('Udisks: id-type: %s' % fstype)
+-
+- fs = obj.get_filesystem()
+-
+- if fs:
+- icon_names = self.get_icon_names(obj)
+-
+- if drive is not None:
+- ejectable = drive.get_property('ejectable')
+- else:
+- ejectable = False
+- mount_point = ''
+- mount_points = fs.get_cached_property('MountPoints').get_bytestring_array()
+- if len(mount_points) == 0:
+- try:
+- logging.debug("UDisks: attempting to mount %s", path)
+- mount_point = self.retry_mount(fs, fstype)
+- if not mount_point:
+- raise Exception
+- else:
+- logging.debug("UDisks: successfully mounted at %s", mount_point)
+- except Exception:
+- logging.error('UDisks: could not mount the device: %s', path)
+- return
+- else:
+- mount_point = mount_points[0]
+- logging.debug("UDisks: already mounted at %s", mount_point)
+-
+- self.known_mounts[path] = mount_point
+- if self.validMounts.pathIsValidMountPoint(mount_point):
+- self.partitionMounted.emit(mount_point, icon_names, ejectable)
+-
+- else:
+- logging.debug("Udisks: partition has no file system %s", path)
+-
+- def retry_mount(self, fs, fstype) -> str:
+- # Variant parameter construction Copyright Bernard Baeyens, and is
+- # licensed under GNU General Public License Version 2 or higher.
+- # https://github.com/berbae/udisksvm
+- list_options = ''
+- if fstype == 'vfat':
+- list_options = 'flush'
+- elif fstype == 'ext2':
+- list_options = 'sync'
+- G_VARIANT_TYPE_VARDICT = GLib.VariantType.new('a{sv}')
+- param_builder = GLib.VariantBuilder.new(G_VARIANT_TYPE_VARDICT)
+- optname = GLib.Variant.new_string('fstype') # s
+- value = GLib.Variant.new_string(fstype)
+- vvalue = GLib.Variant.new_variant(value) # v
+- newsv = GLib.Variant.new_dict_entry(optname, vvalue) # {sv}
+- param_builder.add_value(newsv)
+- optname = GLib.Variant.new_string('options')
+- value = GLib.Variant.new_string(list_options)
+- vvalue = GLib.Variant.new_variant(value)
+- newsv = GLib.Variant.new_dict_entry(optname, vvalue)
+- param_builder.add_value(newsv)
+- vparam = param_builder.end() # a{sv}
+-
+- # Try to mount until it does not fail with "Busy"
+- timeout = 10
+- while timeout >= 0:
+- try:
+- return fs.call_mount_sync(vparam, None)
+- except GLib.GError as e:
+- if not 'UDisks2.Error.DeviceBusy' in e.message:
+- raise
+- logging.debug('Udisks: Device busy.')
+- time.sleep(0.3)
+- timeout -= 1
+- return ''
+-
+- def get_icon_names(self, obj: UDisks.Object) -> List[str]:
+- # Get icon information, if possible
+- icon_names = []
+- if have_gio:
+- info = self.udisks.get_object_info(obj)
+- icon = info.get_icon()
+- if isinstance(icon, Gio.ThemedIcon):
+- icon_names = icon.get_names()
+- return icon_names
+-
+- # Next four class member functions from Damon Lynch, not Canonical
+- def _device_removed(self, obj: UDisks.Object) -> None:
+- # path here refers to the udev / udisks path, not the mount point
+- path = obj.get_object_path()
+- if path in self.known_mounts:
+- mount_point = self.known_mounts[path]
+- del self.known_mounts[path]
+- self.partitionUnmounted.emit(mount_point)
+-
+- def get_can_eject(self, obj: UDisks.Object) -> bool:
+- block = obj.get_block()
+- drive = self._get_drive(block)
+- if drive is not None:
+- return drive.get_property('ejectable')
+- return False
+-
+- def get_device_props(self, device_path: str) -> Tuple[List[str], bool]:
+- """
+- Given a device, get the icon names suggested by udev, and
+- determine whether the mount is ejectable or not.
+- :param device_path: system path of the device to check,
+- e.g. /dev/sdc1
+- :return: icon names and eject boolean
+- """
+-
+- object_path = '/org/freedesktop/UDisks2/block_devices/{}'.format(
+- os.path.split(device_path)[1])
+- obj = self.udisks.get_object(object_path)
+- icon_names = self.get_icon_names(obj)
+- can_eject = self.get_can_eject(obj)
+- return (icon_names, can_eject)
+-
+- @pyqtSlot(str)
+- def unmount_volume(self, mount_point: str) -> None:
+-
+- G_VARIANT_TYPE_VARDICT = GLib.VariantType.new('a{sv}')
+- param_builder = GLib.VariantBuilder.new(G_VARIANT_TYPE_VARDICT)
+-
+- # Variant parameter construction Copyright Bernard Baeyens, and is
+- # licensed under GNU General Public License Version 2 or higher.
+- # https://github.com/berbae/udisksvm
+-
+- optname = GLib.Variant.new_string('force')
+- value = GLib.Variant.new_boolean(False)
+- vvalue = GLib.Variant.new_variant(value)
+- newsv = GLib.Variant.new_dict_entry(optname, vvalue)
+- param_builder.add_value(newsv)
+-
+- vparam = param_builder.end() # a{sv}
+-
+- path = None
+- # Get the path from the dict we keep of known mounts
+- for key, value in self.known_mounts.items():
+- if value == mount_point:
+- path = key
+- break
+- if path is None:
+- logging.error("Could not find UDisks2 path used to be able to unmount %s", mount_point)
+-
+- fs = None
+- for obj in self.manager.get_objects():
+- opath = obj.get_object_path()
+- if path == opath:
+- fs = obj.get_filesystem()
+- if fs is None:
+- logging.error("Could not find UDisks2 filesystem used to be able to unmount %s",
+- mount_point)
+-
+- logging.debug("Unmounting %s...", mount_point)
+- try:
+- fs.call_unmount(vparam, None, self.umount_volume_callback, (mount_point, fs))
+- except GLib.GError:
+- value = sys.exc_info()[1]
+- logging.error('Unmounting failed with error:')
+- logging.error("%s", value)
+-
+- def umount_volume_callback(self, source_object: UDisks.FilesystemProxy,
+- result: Gio.AsyncResult,
+- user_data: Tuple[str, UDisks.Filesystem]) -> None:
+- """
+- Callback for asynchronous unmount operation.
+-
+- :param source_object: the FilesystemProxy object
+- :param result: result of the unmount
+- :param user_data: mount_point and the file system
+- """
+-
+- mount_point, fs = user_data
+-
+- try:
+- if fs.call_unmount_finish(result):
+- logging.debug("...successfully unmounted %s", mount_point)
+- else:
+- # this is the result even when the unmount was unsuccessful
+- logging.debug("...possibly failed to unmount %s", mount_point)
+- except GLib.GError as e:
+- logging.error('Exception occurred unmounting %s', mount_point)
+- logging.exception('Traceback:')
+- except:
+- logging.error('Exception occurred unmounting %s', mount_point)
+- logging.exception('Traceback:')
+-
+- self.partitionUnmounted.emit(mount_point)
+-
+-
+ if have_gio:
+ class GVolumeMonitor(QObject):
+ r"""
+@@ -1577,7 +1323,7 @@ def get_mount_size(mount: QStorageInfo) -> Tuple[int,
+ """
+ Uses GIO to get bytes total and bytes free (available) for the mount that a
+ path is in.
+-
++
+ :param path: path located anywhere in the mount
+ :return: bytes_total, bytes_free
+ """
diff --git a/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py b/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py
new file mode 100644
index 000000000000..7d46505e0be9
--- /dev/null
+++ b/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py
@@ -0,0 +1,62 @@
+--- raphodo/utilities.py.orig 2019-08-18 03:58:11 UTC
++++ raphodo/utilities.py
+@@ -76,10 +76,11 @@ if arrow_version >= parse_version('0.14.3') and arrow_
+ # Linux specific code to ensure child processes exit when parent dies
+ # See http://stackoverflow.com/questions/19447603/
+ # how-to-kill-a-python-child-process-created-with-subprocess-check-output-when-t/
+-libc = ctypes.CDLL("libc.so.6")
++libc = ctypes.CDLL("libc.so.7")
+ def set_pdeathsig(sig = signal.SIGTERM):
+ def callable():
+- return libc.prctl(1, sig)
++ return 0
++ #return libc.procctl(0, 0, 11, sig)
+ return callable
+
+
+@@ -195,8 +196,8 @@ def show_errors():
+ # kilobytes, etc.
+ suffixes = [_('B'), _('KB'), _('MB'), _('GB'), _('TB'), _('PB'), _('EB'), _('ZB'), _('YB')]
+
+-def format_size_for_user(size_in_bytes: int,
+- zero_string: str='',
++def format_size_for_user(size_in_bytes: int,
++ zero_string: str='',
+ no_decimals: int=2) -> str:
+ r"""
+ Humanize display of bytes.
+@@ -382,12 +383,12 @@ def find_mount_point(path: str) -> str:
+ Find the mount point of a path
+ See:
+ http://stackoverflow.com/questions/4453602/how-to-find-the-mountpoint-a-file-resides-on
+-
++
+ >>> print(find_mount_point('/crazy/path'))
+ /
+-
+- :param path:
+- :return:
++
++ :param path:
++ :return:
+ """
+ path = os.path.realpath(path)
+ while not os.path.ismount(path):
+@@ -724,13 +725,13 @@ def _collect_duplicates(basenames, paths):
+
+ def make_path_end_snippets_unique(*paths) -> List[str]:
+ r"""
+- Make list of path ends unique given possible common path endings.
+-
+- A snippet starts from the end of the path, in extreme cases possibly up the path start.
++ Make list of path ends unique given possible common path endings.
+
++ A snippet starts from the end of the path, in extreme cases possibly up the path start.
++
+ :param paths: sequence of paths to generate unique end snippets for
+ :return: list of unique snippets
+-
++
+ >>> p0 = '/home/damon/photos'
+ >>> p1 = '/media/damon/backup1/photos'
+ >>> p2 = '/media/damon/backup2/photos'
diff --git a/graphics/rapid-photo-downloader/pkg-descr b/graphics/rapid-photo-downloader/pkg-descr
new file mode 100644
index 000000000000..30d4ac6563a0
--- /dev/null
+++ b/graphics/rapid-photo-downloader/pkg-descr
@@ -0,0 +1,7 @@
+Rapid Photo Downloader imports photos and videos from cameras, phones,
+memory cards and other devices at high speed. It can be configured to
+rename photos and videos with meaningful filenames you specify. It can
+also back up photos and videos as they are downloaded. It downloads
+from and backs up to multiple devices simultaneously.
+
+WWW: http://www.damonlynch.net/rapid/