aboutsummaryrefslogtreecommitdiff
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
parente6fb62b39052db178d65bec4b03ea125f203774f (diff)
downloadports-afc03086202ce696e54c801d26c215310f4e2e31.tar.gz
ports-afc03086202ce696e54c801d26c215310f4e2e31.zip
Notes
-rw-r--r--graphics/Makefile1
-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
8 files changed, 595 insertions, 0 deletions
diff --git a/graphics/Makefile b/graphics/Makefile
index 3225269eb007..012a0489fa78 100644
--- a/graphics/Makefile
+++ b/graphics/Makefile
@@ -928,6 +928,7 @@
SUBDIR += quesoglc
SUBDIR += radiance
SUBDIR += radius-engine
+ SUBDIR += rapid-photo-downloader
SUBDIR += raster3d
SUBDIR += rawtherapee
SUBDIR += rayshade
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/