comparison 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
comparison
equal deleted inserted replaced
1531:d7c78722e4f8 1532:106945841fbc
1 import json
2 from bridge import Bridge
3 from browser import console as log, window
4 import dialog
5 from template import Template
6 bridge = Bridge()
7
8 log.warning = log.warn
9
10
11 class FileUploader:
12 """Class handling HTTP upload and progress display"""
13
14 def __init__(self, files_service, template_file, files_path=None, on_delete_cb=None):
15 self.files_service = files_service
16 self.files_path = files_path
17 self.item_tpl = Template(template_file)
18 self.on_delete_cb = on_delete_cb
19 self.uploaded = {}
20
21 def on_progress(self, ev, item_elt):
22 if ev.lengthComputable:
23 percent = int(ev.loaded/ev.total*100)
24 self.update_progress(item_elt, percent)
25
26 def on_load(self, file_, item_elt):
27 self.update_progress(item_elt, 100)
28 item_elt.classList.add("progress_finished")
29 item_elt.classList.remove("progress_started")
30 if self.on_delete_cb is not None:
31 delete_btn = item_elt.select_one('.action_delete')
32 if delete_btn is not None:
33 delete_btn.bind("click", self.on_delete_cb)
34 log.info(f"file {file_.name} uploaded correctly")
35
36 def on_error(self, failure, file_, item_elt):
37 dialog.notification.show(
38 f"can't upload {file_.name}: {failure}",
39 level="error"
40 )
41
42 def update_progress(self, item_elt, new_value):
43 progress_elt = item_elt.select_one("progress")
44 if progress_elt is not None:
45 progress_elt.value = new_value
46 progress_elt.text = f"{new_value}%"
47
48 def on_slot_cb(self, file_, upload_slot, item_elt):
49 put_url, get_url, headers = upload_slot
50 xhr = window.XMLHttpRequest.new()
51 xhr.open("PUT", put_url, True)
52 xhr.upload.bind('progress', lambda ev: self.on_progress(ev, item_elt))
53 xhr.upload.bind('load', lambda ev: self.on_load(file_, item_elt))
54 xhr.upload.bind('error', lambda ev: self.on_error(xhr.response, file_, item_elt))
55 if self.files_path is not None:
56 xhr.setRequestHeader(
57 'Xmpp-File-Path',
58 window.encodeURIComponent(self.files_path)
59 )
60 xhr.setRequestHeader('Xmpp-File-No-Http', "true")
61 xhr.send(file_)
62
63 # we update file data with the get URL
64 file_data = json.loads(item_elt.getAttribute("data-file"))
65 file_data["url"] = get_url
66 item_elt.setAttribute("data-file", json.dumps(file_data))
67
68 def on_slot_eb(self, file_, failure, item_elt):
69 dialog.notification.show(
70 f"Can't get upload slot: {failure['message']}",
71 level="error"
72 )
73 item_elt.remove()
74
75 def upload_files(self, files, container_elt):
76 """Start file upload, and add item_tpl to container_elt"""
77 log.info(f"uploading {len(files)} files")
78 for file_ in files:
79 url = window.URL.createObjectURL(file_)
80
81 file_data = {
82 "name": file_.name,
83 "mime_type": file_.type,
84 }
85 template_file_data = file_data.copy()
86 # we don't want to open the file on click, it's not yet the
87 # uploaded URL
88 template_file_data["url"] = url
89 # we have no thumb yet, so we use the whole image
90 # TODO: reduce image for preview
91 template_file_data["thumb_url"] = url
92
93 item_elt = self.item_tpl.get_elt({
94 "file": template_file_data,
95 "uploading": True,
96 })
97 item_elt.setAttribute("data-file", json.dumps(file_data))
98 item_elt.classList.add("progress_started")
99 container_elt <= item_elt
100
101 bridge.file_http_upload_get_slot(
102 file_.name,
103 file_.size,
104 file_.type or '',
105 self.files_service,
106 callback=lambda upload_slot, file_=file_, item_elt=item_elt:
107 self.on_slot_cb(file_, upload_slot, item_elt),
108 errback=lambda failure, file_=file_, item_elt=item_elt:
109 self.on_slot_eb(file_, failure, item_elt),
110 )