Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 21 Sep 2019 18:45:22 +0000 (UTC)
From:      Kurt Jaeger <pi@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r512533 - in head/graphics: . rapid-photo-downloader rapid-photo-downloader/files
Message-ID:  <201909211845.x8LIjMT5072748@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: pi
Date: Sat Sep 21 18:45:22 2019
New Revision: 512533
URL: https://svnweb.freebsd.org/changeset/ports/512533

Log:
  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

Added:
  head/graphics/rapid-photo-downloader/
  head/graphics/rapid-photo-downloader/Makefile   (contents, props changed)
  head/graphics/rapid-photo-downloader/distinfo   (contents, props changed)
  head/graphics/rapid-photo-downloader/files/
  head/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py   (contents, props changed)
  head/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py   (contents, props changed)
  head/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py   (contents, props changed)
  head/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py   (contents, props changed)
  head/graphics/rapid-photo-downloader/pkg-descr   (contents, props changed)
Modified:
  head/graphics/Makefile

Modified: head/graphics/Makefile
==============================================================================
--- head/graphics/Makefile	Sat Sep 21 18:42:40 2019	(r512532)
+++ head/graphics/Makefile	Sat Sep 21 18:45:22 2019	(r512533)
@@ -928,6 +928,7 @@
     SUBDIR += quesoglc
     SUBDIR += radiance
     SUBDIR += radius-engine
+    SUBDIR += rapid-photo-downloader
     SUBDIR += raster3d
     SUBDIR += rawtherapee
     SUBDIR += rayshade

Added: head/graphics/rapid-photo-downloader/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/Makefile	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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>

Added: head/graphics/rapid-photo-downloader/distinfo
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/distinfo	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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

Added: head/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/files/patch-raphodo_cache.py	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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

Added: head/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/files/patch-raphodo_rapid.py	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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)

Added: head/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/files/patch-raphodo_storage.py	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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
+     """

Added: head/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/files/patch-raphodo_utilities.py	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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'

Added: head/graphics/rapid-photo-downloader/pkg-descr
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/graphics/rapid-photo-downloader/pkg-descr	Sat Sep 21 18:45:22 2019	(r512533)
@@ -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/



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201909211845.x8LIjMT5072748>