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)))