Mercurial > libervia-desktop-kivy
comparison cagou/plugins/plugin_wid_file_sharing.py @ 312:772c170b47a9
Python3 port:
/!\ Cagou now runs with Python 3.6+
Port has been done in the same way as for backend (check backend commit b2d067339de3
message for details).
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:14:22 +0200 |
parents | 1b835bcfa663 |
children | dddea9684a8e |
comparison
equal
deleted
inserted
replaced
311:a0d978d3ce84 | 312:772c170b47a9 |
---|---|
40 import os.path | 40 import os.path |
41 import json | 41 import json |
42 | 42 |
43 | 43 |
44 PLUGIN_INFO = { | 44 PLUGIN_INFO = { |
45 "name": _(u"file sharing"), | 45 "name": _("file sharing"), |
46 "main": "FileSharing", | 46 "main": "FileSharing", |
47 "description": _(u"share/transfer files between devices"), | 47 "description": _("share/transfer files between devices"), |
48 "icon_symbol": u"exchange", | 48 "icon_symbol": "exchange", |
49 } | 49 } |
50 MODE_VIEW = u"view" | 50 MODE_VIEW = "view" |
51 MODE_LOCAL = u"local" | 51 MODE_LOCAL = "local" |
52 SELECT_INSTRUCTIONS = _(u"Please select entities to share with") | 52 SELECT_INSTRUCTIONS = _("Please select entities to share with") |
53 | 53 |
54 if kivy_utils.platform == "android": | 54 if kivy_utils.platform == "android": |
55 from jnius import autoclass | 55 from jnius import autoclass |
56 Environment = autoclass("android.os.Environment") | 56 Environment = autoclass("android.os.Environment") |
57 base_dir = Environment.getExternalStorageDirectory().getAbsolutePath() | 57 base_dir = Environment.getExternalStorageDirectory().getAbsolutePath() |
58 def expanduser(path): | 58 def expanduser(path): |
59 if path == u'~' or path.startswith(u'~/'): | 59 if path == '~' or path.startswith('~/'): |
60 return path.replace(u'~', base_dir, 1) | 60 return path.replace('~', base_dir, 1) |
61 return path | 61 return path |
62 else: | 62 else: |
63 expanduser = os.path.expanduser | 63 expanduser = os.path.expanduser |
64 | 64 |
65 | 65 |
70 parent.bind(mode=self.on_mode) | 70 parent.bind(mode=self.on_mode) |
71 self.on_mode(parent, parent.mode) | 71 self.on_mode(parent, parent.mode) |
72 | 72 |
73 def on_mode(self, parent, new_mode): | 73 def on_mode(self, parent, new_mode): |
74 if new_mode == MODE_VIEW: | 74 if new_mode == MODE_VIEW: |
75 self.text = _(u"view shared files") | 75 self.text = _("view shared files") |
76 elif new_mode == MODE_LOCAL: | 76 elif new_mode == MODE_LOCAL: |
77 self.text = _(u"share local files") | 77 self.text = _("share local files") |
78 else: | 78 else: |
79 exceptions.InternalError(u"Unknown mode: {mode}".format(mode=new_mode)) | 79 exceptions.InternalError("Unknown mode: {mode}".format(mode=new_mode)) |
80 | 80 |
81 | 81 |
82 class PathWidget(ItemWidget): | 82 class PathWidget(ItemWidget): |
83 | 83 |
84 def __init__(self, filepath, main_wid, **kw): | 84 def __init__(self, filepath, main_wid, **kw): |
85 name = os.path.basename(filepath) | 85 name = os.path.basename(filepath) |
86 self.filepath = os.path.normpath(filepath) | 86 self.filepath = os.path.normpath(filepath) |
87 if self.filepath == u'.': | 87 if self.filepath == '.': |
88 self.filepath = u'' | 88 self.filepath = '' |
89 super(PathWidget, self).__init__(name=name, main_wid=main_wid, **kw) | 89 super(PathWidget, self).__init__(name=name, main_wid=main_wid, **kw) |
90 | 90 |
91 @property | 91 @property |
92 def is_dir(self): | 92 def is_dir(self): |
93 raise NotImplementedError | 93 raise NotImplementedError |
95 def do_item_action(self, touch): | 95 def do_item_action(self, touch): |
96 if self.is_dir: | 96 if self.is_dir: |
97 self.main_wid.current_dir = self.filepath | 97 self.main_wid.current_dir = self.filepath |
98 | 98 |
99 def open_menu(self, touch, dt): | 99 def open_menu(self, touch, dt): |
100 log.debug(_(u"opening menu for {path}").format(path=self.filepath)) | 100 log.debug(_("opening menu for {path}").format(path=self.filepath)) |
101 super(PathWidget, self).open_menu(touch, dt) | 101 super(PathWidget, self).open_menu(touch, dt) |
102 | 102 |
103 | 103 |
104 class LocalPathWidget(PathWidget): | 104 class LocalPathWidget(PathWidget): |
105 | 105 |
108 return os.path.isdir(self.filepath) | 108 return os.path.isdir(self.filepath) |
109 | 109 |
110 def getMenuChoices(self): | 110 def getMenuChoices(self): |
111 choices = [] | 111 choices = [] |
112 if self.shared: | 112 if self.shared: |
113 choices.append(dict(text=_(u'unshare'), | 113 choices.append(dict(text=_('unshare'), |
114 index=len(choices)+1, | 114 index=len(choices)+1, |
115 callback=self.main_wid.unshare)) | 115 callback=self.main_wid.unshare)) |
116 else: | 116 else: |
117 choices.append(dict(text=_(u'share'), | 117 choices.append(dict(text=_('share'), |
118 index=len(choices)+1, | 118 index=len(choices)+1, |
119 callback=self.main_wid.share)) | 119 callback=self.main_wid.share)) |
120 return choices | 120 return choices |
121 | 121 |
122 | 122 |
130 def is_dir(self): | 130 def is_dir(self): |
131 return self.type_ == C.FILE_TYPE_DIRECTORY | 131 return self.type_ == C.FILE_TYPE_DIRECTORY |
132 | 132 |
133 def do_item_action(self, touch): | 133 def do_item_action(self, touch): |
134 if self.is_dir: | 134 if self.is_dir: |
135 if self.filepath == u'..': | 135 if self.filepath == '..': |
136 self.main_wid.remote_entity = u'' | 136 self.main_wid.remote_entity = '' |
137 else: | 137 else: |
138 super(RemotePathWidget, self).do_item_action(touch) | 138 super(RemotePathWidget, self).do_item_action(touch) |
139 else: | 139 else: |
140 self.main_wid.request_item(self) | 140 self.main_wid.request_item(self) |
141 return True | 141 return True |
142 | 142 |
143 class SharingDeviceWidget(DeviceWidget): | 143 class SharingDeviceWidget(DeviceWidget): |
144 | 144 |
145 def do_item_action(self, touch): | 145 def do_item_action(self, touch): |
146 self.main_wid.remote_entity = self.entity_jid | 146 self.main_wid.remote_entity = self.entity_jid |
147 self.main_wid.remote_dir = u'' | 147 self.main_wid.remote_dir = '' |
148 | 148 |
149 | 149 |
150 class FileSharing(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior, | 150 class FileSharing(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior, |
151 TouchMenuBehaviour): | 151 TouchMenuBehaviour): |
152 SINGLE=False | 152 SINGLE=False |
153 layout = properties.ObjectProperty() | 153 layout = properties.ObjectProperty() |
154 mode = properties.OptionProperty(MODE_VIEW, options=[MODE_VIEW, MODE_LOCAL]) | 154 mode = properties.OptionProperty(MODE_VIEW, options=[MODE_VIEW, MODE_LOCAL]) |
155 local_dir = properties.StringProperty(expanduser(u'~')) | 155 local_dir = properties.StringProperty(expanduser('~')) |
156 remote_dir = properties.StringProperty(u'') | 156 remote_dir = properties.StringProperty('') |
157 remote_entity = properties.StringProperty(u'') | 157 remote_entity = properties.StringProperty('') |
158 shared_paths = properties.ListProperty() | 158 shared_paths = properties.ListProperty() |
159 signals_registered = False | 159 signals_registered = False |
160 | 160 |
161 def __init__(self, host, target, profiles): | 161 def __init__(self, host, target, profiles): |
162 quick_widgets.QuickWidget.__init__(self, host, target, profiles) | 162 quick_widgets.QuickWidget.__init__(self, host, target, profiles) |
206 | 206 |
207 def on_mode(self, instance, new_mode): | 207 def on_mode(self, instance, new_mode): |
208 self.update_view(None, self.local_dir) | 208 self.update_view(None, self.local_dir) |
209 | 209 |
210 def onHeaderInput(self): | 210 def onHeaderInput(self): |
211 if u'/' in self.header_input.text or self.header_input.text == u'~': | 211 if '/' in self.header_input.text or self.header_input.text == '~': |
212 self.current_dir = expanduser(self.header_input.text) | 212 self.current_dir = expanduser(self.header_input.text) |
213 | 213 |
214 def onHeaderInputComplete(self, wid, text, **kwargs): | 214 def onHeaderInputComplete(self, wid, text, **kwargs): |
215 """we filter items when text is entered in input box""" | 215 """we filter items when text is entered in input box""" |
216 if u'/' in text: | 216 if '/' in text: |
217 return | 217 return |
218 self.do_filter(self.layout.children, | 218 self.do_filter(self.layout.children, |
219 text, | 219 text, |
220 lambda c: c.name, | 220 lambda c: c.name, |
221 width_cb=lambda c: c.base_width, | 221 width_cb=lambda c: c.base_width, |
222 height_cb=lambda c: c.minimum_height, | 222 height_cb=lambda c: c.minimum_height, |
223 continue_tests=[lambda c: not isinstance(c, ItemWidget), | 223 continue_tests=[lambda c: not isinstance(c, ItemWidget), |
224 lambda c: c.name == u'..']) | 224 lambda c: c.name == '..']) |
225 | 225 |
226 | 226 |
227 ## remote sharing callback ## | 227 ## remote sharing callback ## |
228 | 228 |
229 def _discoFindByFeaturesCb(self, data): | 229 def _discoFindByFeaturesCb(self, data): |
230 entities_services, entities_own, entities_roster = data | 230 entities_services, entities_own, entities_roster = data |
231 for entities_map, title in ((entities_services, | 231 for entities_map, title in ((entities_services, |
232 _(u'services')), | 232 _('services')), |
233 (entities_own, | 233 (entities_own, |
234 _(u'your devices')), | 234 _('your devices')), |
235 (entities_roster, | 235 (entities_roster, |
236 _(u'your contacts devices'))): | 236 _('your contacts devices'))): |
237 if entities_map: | 237 if entities_map: |
238 self.layout.add_widget(CategorySeparator(text=title)) | 238 self.layout.add_widget(CategorySeparator(text=title)) |
239 for entity_str, entity_ids in entities_map.iteritems(): | 239 for entity_str, entity_ids in entities_map.items(): |
240 entity_jid = jid.JID(entity_str) | 240 entity_jid = jid.JID(entity_str) |
241 item = SharingDeviceWidget( | 241 item = SharingDeviceWidget( |
242 self, entity_jid, Identities(entity_ids)) | 242 self, entity_jid, Identities(entity_ids)) |
243 self.layout.add_widget(item) | 243 self.layout.add_widget(item) |
244 if not entities_services and not entities_own and not entities_roster: | 244 if not entities_services and not entities_own and not entities_roster: |
245 self.layout.add_widget(Label( | 245 self.layout.add_widget(Label( |
246 size_hint=(1, 1), | 246 size_hint=(1, 1), |
247 halign='center', | 247 halign='center', |
248 text_size=self.size, | 248 text_size=self.size, |
249 text=_(u"No sharing device found"))) | 249 text=_("No sharing device found"))) |
250 | 250 |
251 def discover_devices(self): | 251 def discover_devices(self): |
252 """Looks for devices handling file "File Information Sharing" and display them""" | 252 """Looks for devices handling file "File Information Sharing" and display them""" |
253 try: | 253 try: |
254 namespace = self.host.ns_map['fis'] | 254 namespace = self.host.ns_map['fis'] |
255 except KeyError: | 255 except KeyError: |
256 msg = _(u"can't find file information sharing namespace, " | 256 msg = _("can't find file information sharing namespace, " |
257 u"is the plugin running?") | 257 "is the plugin running?") |
258 log.warning(msg) | 258 log.warning(msg) |
259 G.host.addNote(_(u"missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR) | 259 G.host.addNote(_("missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR) |
260 return | 260 return |
261 self.host.bridge.discoFindByFeatures( | 261 self.host.bridge.discoFindByFeatures( |
262 [namespace], [], False, True, True, True, False, self.profile, | 262 [namespace], [], False, True, True, True, False, self.profile, |
263 callback=self._discoFindByFeaturesCb, | 263 callback=self._discoFindByFeaturesCb, |
264 errback=partial(G.host.errback, | 264 errback=partial(G.host.errback, |
265 title=_(u"shared folder error"), | 265 title=_("shared folder error"), |
266 message=_(u"can't check sharing devices: {msg}"))) | 266 message=_("can't check sharing devices: {msg}"))) |
267 | 267 |
268 def FISListCb(self, files_data): | 268 def FISListCb(self, files_data): |
269 for file_data in files_data: | 269 for file_data in files_data: |
270 filepath = os.path.join(self.current_dir, file_data[u'name']) | 270 filepath = os.path.join(self.current_dir, file_data['name']) |
271 item = RemotePathWidget( | 271 item = RemotePathWidget( |
272 filepath=filepath, | 272 filepath=filepath, |
273 main_wid=self, | 273 main_wid=self, |
274 type_=file_data[u'type']) | 274 type_=file_data['type']) |
275 self.layout.add_widget(item) | 275 self.layout.add_widget(item) |
276 | 276 |
277 def FISListEb(self, failure_): | 277 def FISListEb(self, failure_): |
278 self.remote_dir = u'' | 278 self.remote_dir = '' |
279 G.host.addNote( | 279 G.host.addNote( |
280 _(u"shared folder error"), | 280 _("shared folder error"), |
281 _(u"can't list files for {remote_entity}: {msg}").format( | 281 _("can't list files for {remote_entity}: {msg}").format( |
282 remote_entity=self.remote_entity, | 282 remote_entity=self.remote_entity, |
283 msg=failure_), | 283 msg=failure_), |
284 level=C.XMLUI_DATA_LVL_WARNING) | 284 level=C.XMLUI_DATA_LVL_WARNING) |
285 | 285 |
286 ## view generation ## | 286 ## view generation ## |
287 | 287 |
288 def update_view(self, *args): | 288 def update_view(self, *args): |
289 """update items according to current mode, entity and dir""" | 289 """update items according to current mode, entity and dir""" |
290 log.debug(u'updating {}, {}'.format(self.current_dir, args)) | 290 log.debug('updating {}, {}'.format(self.current_dir, args)) |
291 self.layout.clear_widgets() | 291 self.layout.clear_widgets() |
292 self.header_input.text = u'' | 292 self.header_input.text = '' |
293 self.header_input.hint_text = self.current_dir | 293 self.header_input.hint_text = self.current_dir |
294 | 294 |
295 if self.mode == MODE_LOCAL: | 295 if self.mode == MODE_LOCAL: |
296 filepath = os.path.join(self.local_dir, u'..') | 296 filepath = os.path.join(self.local_dir, '..') |
297 self.layout.add_widget(LocalPathWidget(filepath=filepath, main_wid=self)) | 297 self.layout.add_widget(LocalPathWidget(filepath=filepath, main_wid=self)) |
298 try: | 298 try: |
299 files = sorted(os.listdir(self.local_dir)) | 299 files = sorted(os.listdir(self.local_dir)) |
300 except OSError as e: | 300 except OSError as e: |
301 msg = _(u"can't list files in \"{local_dir}\": {msg}").format( | 301 msg = _("can't list files in \"{local_dir}\": {msg}").format( |
302 local_dir=self.local_dir, | 302 local_dir=self.local_dir, |
303 msg=e) | 303 msg=e) |
304 G.host.addNote( | 304 G.host.addNote( |
305 _(u"shared folder error"), | 305 _("shared folder error"), |
306 msg, | 306 msg, |
307 level=C.XMLUI_DATA_LVL_WARNING) | 307 level=C.XMLUI_DATA_LVL_WARNING) |
308 self.local_dir = expanduser(u'~') | 308 self.local_dir = expanduser('~') |
309 return | 309 return |
310 for f in files: | 310 for f in files: |
311 filepath = os.path.join(self.local_dir, f) | 311 filepath = os.path.join(self.local_dir, f) |
312 self.layout.add_widget(LocalPathWidget(filepath=filepath, | 312 self.layout.add_widget(LocalPathWidget(filepath=filepath, |
313 main_wid=self)) | 313 main_wid=self)) |
315 if not self.remote_entity: | 315 if not self.remote_entity: |
316 self.discover_devices() | 316 self.discover_devices() |
317 else: | 317 else: |
318 # we always a way to go back | 318 # we always a way to go back |
319 # so user can return to previous list even in case of error | 319 # so user can return to previous list even in case of error |
320 parent_path = os.path.join(self.remote_dir, u'..') | 320 parent_path = os.path.join(self.remote_dir, '..') |
321 item = RemotePathWidget( | 321 item = RemotePathWidget( |
322 filepath = parent_path, | 322 filepath = parent_path, |
323 main_wid=self, | 323 main_wid=self, |
324 type_ = C.FILE_TYPE_DIRECTORY) | 324 type_ = C.FILE_TYPE_DIRECTORY) |
325 self.layout.add_widget(item) | 325 self.layout.add_widget(item) |
326 self.host.bridge.FISList( | 326 self.host.bridge.FISList( |
327 unicode(self.remote_entity), | 327 str(self.remote_entity), |
328 self.remote_dir, | 328 self.remote_dir, |
329 {}, | 329 {}, |
330 self.profile, | 330 self.profile, |
331 callback=self.FISListCb, | 331 callback=self.FISListCb, |
332 errback=self.FISListEb) | 332 errback=self.FISListEb) |
333 | 333 |
334 ## Share methods ## | 334 ## Share methods ## |
335 | 335 |
336 def do_share(self, entities_jids, item): | 336 def do_share(self, entities_jids, item): |
337 if entities_jids: | 337 if entities_jids: |
338 access = {u'read': {u'type': 'whitelist', | 338 access = {'read': {'type': 'whitelist', |
339 u'jids': entities_jids}} | 339 'jids': entities_jids}} |
340 else: | 340 else: |
341 access = {} | 341 access = {} |
342 | 342 |
343 G.host.bridge.FISSharePath( | 343 G.host.bridge.FISSharePath( |
344 item.name, | 344 item.name, |
345 item.filepath, | 345 item.filepath, |
346 json.dumps(access, ensure_ascii=False), | 346 json.dumps(access, ensure_ascii=False), |
347 self.profile, | 347 self.profile, |
348 callback=lambda name: G.host.addNote( | 348 callback=lambda name: G.host.addNote( |
349 _(u"sharing folder"), | 349 _("sharing folder"), |
350 _(u"{name} is now shared").format(name=name)), | 350 _("{name} is now shared").format(name=name)), |
351 errback=partial(G.host.errback, | 351 errback=partial(G.host.errback, |
352 title=_(u"sharing folder"), | 352 title=_("sharing folder"), |
353 message=_(u"can't share folder: {msg}"))) | 353 message=_("can't share folder: {msg}"))) |
354 | 354 |
355 def share(self, menu): | 355 def share(self, menu): |
356 item = self.menu_item | 356 item = self.menu_item |
357 self.clear_menu() | 357 self.clear_menu() |
358 EntitiesSelectorMenu(instructions=SELECT_INSTRUCTIONS, | 358 EntitiesSelectorMenu(instructions=SELECT_INSTRUCTIONS, |
363 self.clear_menu() | 363 self.clear_menu() |
364 G.host.bridge.FISUnsharePath( | 364 G.host.bridge.FISUnsharePath( |
365 item.filepath, | 365 item.filepath, |
366 self.profile, | 366 self.profile, |
367 callback=lambda: G.host.addNote( | 367 callback=lambda: G.host.addNote( |
368 _(u"sharing folder"), | 368 _("sharing folder"), |
369 _(u"{name} is not shared anymore").format(name=item.name)), | 369 _("{name} is not shared anymore").format(name=item.name)), |
370 errback=partial(G.host.errback, | 370 errback=partial(G.host.errback, |
371 title=_(u"sharing folder"), | 371 title=_("sharing folder"), |
372 message=_(u"can't unshare folder: {msg}"))) | 372 message=_("can't unshare folder: {msg}"))) |
373 | 373 |
374 def fileJingleRequestCb(self, progress_id, item, dest_path): | 374 def fileJingleRequestCb(self, progress_id, item, dest_path): |
375 G.host.addNote( | 375 G.host.addNote( |
376 _(u"file request"), | 376 _("file request"), |
377 _(u"{name} download started at {dest_path}").format( | 377 _("{name} download started at {dest_path}").format( |
378 name = item.name, | 378 name = item.name, |
379 dest_path = dest_path)) | 379 dest_path = dest_path)) |
380 | 380 |
381 def request_item(self, item): | 381 def request_item(self, item): |
382 """Retrieve an item from remote entity | 382 """Retrieve an item from remote entity |
386 path, name = os.path.split(item.filepath) | 386 path, name = os.path.split(item.filepath) |
387 assert name | 387 assert name |
388 assert self.remote_entity | 388 assert self.remote_entity |
389 extra = {'path': path} | 389 extra = {'path': path} |
390 dest_path = files_utils.get_unique_name(os.path.join(G.host.downloads_dir, name)) | 390 dest_path = files_utils.get_unique_name(os.path.join(G.host.downloads_dir, name)) |
391 G.host.bridge.fileJingleRequest(unicode(self.remote_entity), | 391 G.host.bridge.fileJingleRequest(str(self.remote_entity), |
392 dest_path, | 392 dest_path, |
393 name, | 393 name, |
394 u'', | 394 '', |
395 u'', | 395 '', |
396 extra, | 396 extra, |
397 self.profile, | 397 self.profile, |
398 callback=partial(self.fileJingleRequestCb, | 398 callback=partial(self.fileJingleRequestCb, |
399 item=item, | 399 item=item, |
400 dest_path=dest_path), | 400 dest_path=dest_path), |
401 errback=partial(G.host.errback, | 401 errback=partial(G.host.errback, |
402 title = _(u"file request error"), | 402 title = _("file request error"), |
403 message = _(u"can't request file: {msg}"))) | 403 message = _("can't request file: {msg}"))) |
404 | 404 |
405 @classmethod | 405 @classmethod |
406 def shared_path_new(cls, shared_path, name, profile): | 406 def shared_path_new(cls, shared_path, name, profile): |
407 for wid in G.host.getVisibleList(cls): | 407 for wid in G.host.getVisibleList(cls): |
408 if shared_path not in wid.shared_paths: | 408 if shared_path not in wid.shared_paths: |
412 def shared_path_removed(cls, shared_path, profile): | 412 def shared_path_removed(cls, shared_path, profile): |
413 for wid in G.host.getVisibleList(cls): | 413 for wid in G.host.getVisibleList(cls): |
414 if shared_path in wid.shared_paths: | 414 if shared_path in wid.shared_paths: |
415 wid.shared_paths.remove(shared_path) | 415 wid.shared_paths.remove(shared_path) |
416 else: | 416 else: |
417 log.warning(_(u"shared path {path} not found in {widget}".format( | 417 log.warning(_("shared path {path} not found in {widget}".format( |
418 path = shared_path, widget = wid))) | 418 path = shared_path, widget = wid))) |