view libervia/desktop_kivy/core/file_chooser.py @ 514:d78728d7fd6a

plugin wid calls, core: implements WebRTC DataChannel file transfer: - Add a new "file" icon in call UI to send a file via WebRTC. - Handle new preflight mechanism, and WebRTC file transfer. - Native file chooser handling has been moved to new `core.file_chooser` module, and now supports "save" and "dir" modes (based on `plyer`). rel 442
author Goffi <goffi@goffi.org>
date Sat, 06 Apr 2024 13:37:27 +0200
parents
children
line wrap: on
line source

#!/usr/bin/env python3

# Libervia Desktop-Kivy
# Copyright (C) 2016-2024 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import asyncio
import threading

from libervia.backend.core import exceptions
from libervia.backend.core.i18n import _

from kivy import properties
from kivy.clock import Clock
from kivy.event import EventDispatcher
from plyer import filechooser, storagepath


class FileChooser(EventDispatcher):
    callback = properties.ObjectProperty()
    cancel_cb = properties.ObjectProperty()
    native_filechooser = True
    default_path = properties.StringProperty(storagepath.get_home_dir())
    mode = properties.OptionProperty("open", options=["open", "save", "dir"])
    title = properties.StringProperty(_("Please select a file to upload"))

    def open(self):
        """Open the file selection dialog in a separate thread"""
        thread = threading.Thread(target=self._native_file_chooser)
        thread.start()

    @classmethod
    async def a_open(cls, **kwargs) -> str | None:
        """Open the file selection dialog asynchronously

        @return: The path of the selected file
            None if the dialog has been cancelled.
        """
        future = asyncio.Future()

        def on_success(file_path):
            if not future.done():
                future.set_result(file_path)

        def on_cancel(__):
            if not future.done():
                future.set_result(None)

        file_chooser = cls(
            **kwargs,
            callback=on_success,
            cancel_cb=on_cancel
        )

        file_chooser.open()

        return await future

    def _native_file_chooser(self, *args, **kwargs):
        match self.mode:
            case "open":
                method = filechooser.open_file
            case "save":
                method = filechooser.save_file
            case "dir":
                method = filechooser.choose_dir
            case _:
                raise exceptions.InternalError("Should never be reached.")
        files = method(
            title=self.title, path=self.default_path, multiple=False, preview=True
        )
        # we want to leave the thread when calling on_files, so we use Clock
        Clock.schedule_once(lambda *args: self.on_files(files=files), 0)

    def on_files(self, files):
        if files:
            self.callback(files[0])
        else:
            self.cancel_cb(self)