view libervia/web/pages/_browser/file_uploader.py @ 1532:106945841fbc

_browser (album): moved code to upload file to a separate `file_uploader` module: this way, uploading logic can be re-used elsewhere.
author Goffi <goffi@goffi.org>
date Thu, 22 Jun 2023 16:36:01 +0200
parents
children
line wrap: on
line source

import json
from bridge import Bridge
from browser import console as log, window
import dialog
from template import Template
bridge = Bridge()

log.warning = log.warn


class FileUploader:
    """Class handling HTTP upload and progress display"""

    def __init__(self, files_service, template_file, files_path=None, on_delete_cb=None):
        self.files_service = files_service
        self.files_path = files_path
        self.item_tpl = Template(template_file)
        self.on_delete_cb = on_delete_cb
        self.uploaded = {}

    def on_progress(self, ev, item_elt):
        if ev.lengthComputable:
            percent = int(ev.loaded/ev.total*100)
            self.update_progress(item_elt, percent)

    def on_load(self, file_, item_elt):
        self.update_progress(item_elt, 100)
        item_elt.classList.add("progress_finished")
        item_elt.classList.remove("progress_started")
        if self.on_delete_cb is not None:
            delete_btn = item_elt.select_one('.action_delete')
            if delete_btn is not None:
                delete_btn.bind("click", self.on_delete_cb)
        log.info(f"file {file_.name} uploaded correctly")

    def on_error(self, failure, file_, item_elt):
        dialog.notification.show(
            f"can't upload {file_.name}: {failure}",
            level="error"
        )

    def update_progress(self, item_elt, new_value):
        progress_elt = item_elt.select_one("progress")
        if progress_elt is not None:
            progress_elt.value = new_value
            progress_elt.text = f"{new_value}%"

    def on_slot_cb(self, file_, upload_slot, item_elt):
        put_url, get_url, headers = upload_slot
        xhr = window.XMLHttpRequest.new()
        xhr.open("PUT", put_url, True)
        xhr.upload.bind('progress', lambda ev: self.on_progress(ev, item_elt))
        xhr.upload.bind('load', lambda ev: self.on_load(file_, item_elt))
        xhr.upload.bind('error', lambda ev: self.on_error(xhr.response, file_, item_elt))
        if self.files_path is not None:
            xhr.setRequestHeader(
                'Xmpp-File-Path',
                window.encodeURIComponent(self.files_path)
            )
            xhr.setRequestHeader('Xmpp-File-No-Http', "true")
        xhr.send(file_)

        # we update file data with the get URL
        file_data = json.loads(item_elt.getAttribute("data-file"))
        file_data["url"] = get_url
        item_elt.setAttribute("data-file", json.dumps(file_data))

    def on_slot_eb(self, file_, failure, item_elt):
        dialog.notification.show(
            f"Can't get upload slot: {failure['message']}",
            level="error"
        )
        item_elt.remove()

    def upload_files(self, files, container_elt):
        """Start file upload, and add item_tpl to container_elt"""
        log.info(f"uploading {len(files)} files")
        for file_ in files:
            url = window.URL.createObjectURL(file_)

            file_data = {
                "name": file_.name,
                "mime_type": file_.type,
            }
            template_file_data = file_data.copy()
            # we don't want to open the file on click, it's not yet the
            # uploaded URL
            template_file_data["url"] = url
            # we have no thumb yet, so we use the whole image
            # TODO: reduce image for preview
            template_file_data["thumb_url"] = url

            item_elt = self.item_tpl.get_elt({
                "file": template_file_data,
                "uploading": True,
            })
            item_elt.setAttribute("data-file", json.dumps(file_data))
            item_elt.classList.add("progress_started")
            container_elt <= item_elt

            bridge.file_http_upload_get_slot(
                file_.name,
                file_.size,
                file_.type or '',
                self.files_service,
                callback=lambda upload_slot, file_=file_, item_elt=item_elt:
                    self.on_slot_cb(file_, upload_slot, item_elt),
                errback=lambda failure, file_=file_, item_elt=item_elt:
                    self.on_slot_eb(file_, failure, item_elt),
            )