comparison cagou/core/platform_/android.py @ 491:203755bbe0fe

massive refactoring from camelCase -> snake_case. See backend commit log for more details
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:44:32 +0200
parents 83c67b093350
children
comparison
equal deleted inserted replaced
490:962d17c4078c 491:203755bbe0fe
91 91
92 activity.bind(on_new_intent=self.on_new_intent) 92 activity.bind(on_new_intent=self.on_new_intent)
93 self.cache.append((self.on_new_intent, mActivity.getIntent())) 93 self.cache.append((self.on_new_intent, mActivity.getIntent()))
94 self.last_selected_wid = None 94 self.last_selected_wid = None
95 self.restore_selected_wid = True 95 self.restore_selected_wid = True
96 host.addListener('profilePlugged', self.onProfilePlugged) 96 host.addListener('profile_plugged', self.on_profile_plugged)
97 host.addListener('selected', self.onSelectedWidget) 97 host.addListener('selected', self.on_selected_widget)
98 local_dir = Path(host.getConfig('', 'local_dir')).resolve() 98 local_dir = Path(host.config_get('', 'local_dir')).resolve()
99 self.tmp_dir = local_dir / 'tmp' 99 self.tmp_dir = local_dir / 'tmp'
100 # we assert to avoid disaster if `/ 'tmp'` is removed by mistake on the line 100 # we assert to avoid disaster if `/ 'tmp'` is removed by mistake on the line
101 # above 101 # above
102 assert self.tmp_dir.resolve() != local_dir 102 assert self.tmp_dir.resolve() != local_dir
103 # we reset tmp dir on each run, to be sure that there is no residual file 103 # we reset tmp dir on each run, to be sure that there is no residual file
104 if self.tmp_dir.exists(): 104 if self.tmp_dir.exists():
105 shutil.rmtree(self.tmp_dir) 105 shutil.rmtree(self.tmp_dir)
106 self.tmp_dir.mkdir(0o700, parents=True) 106 self.tmp_dir.mkdir(0o700, parents=True)
107 107
108 def on_initFrontendState(self): 108 def on_init_frontend_state(self):
109 # XXX: we use a separated socket instead of bridge because if we 109 # XXX: we use a separated socket instead of bridge because if we
110 # try to call a bridge method in on_pause method, the call data 110 # try to call a bridge method in on_pause method, the call data
111 # is not written before the actual pause 111 # is not written before the actual pause
112 s = self._frontend_status_socket = socket.socket( 112 s = self._frontend_status_socket = socket.socket(
113 socket.AF_UNIX, socket.SOCK_STREAM) 113 socket.AF_UNIX, socket.SOCK_STREAM)
114 s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE)) 114 s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE))
115 s.sendall(STATE_RUNNING) 115 s.sendall(STATE_RUNNING)
116 116
117 def profileAutoconnectGetCb(self, profile=None): 117 def profile_autoconnect_get_cb(self, profile=None):
118 if profile is not None: 118 if profile is not None:
119 G.host.options.profile = profile 119 G.host.options.profile = profile
120 G.host.postInit() 120 G.host.post_init()
121 121
122 def profileAutoconnectGetEb(self, failure_): 122 def profile_autoconnect_get_eb(self, failure_):
123 log.error(f"Error while getting profile to autoconnect: {failure_}") 123 log.error(f"Error while getting profile to autoconnect: {failure_}")
124 G.host.postInit() 124 G.host.post_init()
125 125
126 def _show_perm_warning(self, permissions): 126 def _show_perm_warning(self, permissions):
127 root_wid = G.host.app.root 127 root_wid = G.host.app.root
128 perm_warning = Label( 128 perm_warning = Label(
129 size_hint=(1, 1), 129 size_hint=(1, 1),
164 f"not all mandatory permissions are granted, requesting again: " 164 f"not all mandatory permissions are granted, requesting again: "
165 f"{perm_dict}") 165 f"{perm_dict}")
166 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb) 166 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
167 return 167 return
168 168
169 Clock.schedule_once(lambda *args: G.host.bridge.profileAutoconnectGet( 169 Clock.schedule_once(lambda *args: G.host.bridge.profile_autoconnect_get(
170 callback=self.profileAutoconnectGetCb, 170 callback=self.profile_autoconnect_get_cb,
171 errback=self.profileAutoconnectGetEb), 171 errback=self.profile_autoconnect_get_eb),
172 0) 172 0)
173 173
174 def do_postInit(self): 174 def do_post_init(self):
175 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb) 175 request_permissions(PERMISSION_MANDATORY, callback=self.permission_cb)
176 return False 176 return False
177 177
178 def privateDataGetCb(self, data_s, profile): 178 def private_data_get_cb(self, data_s, profile):
179 data = data_format.deserialise(data_s, type_check=None) 179 data = data_format.deserialise(data_s, type_check=None)
180 if data is not None and self.restore_selected_wid: 180 if data is not None and self.restore_selected_wid:
181 log.debug(f"restoring previous widget {data}") 181 log.debug(f"restoring previous widget {data}")
182 try: 182 try:
183 name = data['name'] 183 name = data['name']
185 except KeyError as e: 185 except KeyError as e:
186 log.error(f"Bad data format for selected widget: {e}\ndata={data}") 186 log.error(f"Bad data format for selected widget: {e}\ndata={data}")
187 return 187 return
188 if target: 188 if target:
189 target = jid.JID(data['target']) 189 target = jid.JID(data['target'])
190 plugin_info = G.host.getPluginInfo(name=name) 190 plugin_info = G.host.get_plugin_info(name=name)
191 if plugin_info is None: 191 if plugin_info is None:
192 log.warning("Can't restore unknown plugin: {name}") 192 log.warning("Can't restore unknown plugin: {name}")
193 return 193 return
194 factory = plugin_info['factory'] 194 factory = plugin_info['factory']
195 G.host.switchWidget( 195 G.host.switch_widget(
196 None, 196 None,
197 factory(plugin_info, target=target, profiles=[profile]) 197 factory(plugin_info, target=target, profiles=[profile])
198 ) 198 )
199 199
200 def onProfilePlugged(self, profile): 200 def on_profile_plugged(self, profile):
201 log.debug("ANDROID profilePlugged") 201 log.debug("ANDROID profile_plugged")
202 G.host.bridge.setParam( 202 G.host.bridge.param_set(
203 "autoconnect_backend", C.BOOL_TRUE, "Connection", -1, profile, 203 "autoconnect_backend", C.BOOL_TRUE, "Connection", -1, profile,
204 callback=lambda: log.info(f"profile {profile} autoconnection set"), 204 callback=lambda: log.info(f"profile {profile} autoconnection set"),
205 errback=lambda: log.error(f"can't set {profile} autoconnection")) 205 errback=lambda: log.error(f"can't set {profile} autoconnection"))
206 for method, *args in self.cache: 206 for method, *args in self.cache:
207 method(*args) 207 method(*args)
208 del self.cache 208 del self.cache
209 G.host.removeListener("profilePlugged", self.onProfilePlugged) 209 G.host.removeListener("profile_plugged", self.on_profile_plugged)
210 # we restore the stored widget if any 210 # we restore the stored widget if any
211 # user will then go back to where they was when the frontend was closed 211 # user will then go back to where they was when the frontend was closed
212 G.host.bridge.privateDataGet( 212 G.host.bridge.private_data_get(
213 "cagou", "selected_widget", profile, 213 "cagou", "selected_widget", profile,
214 callback=partial(self.privateDataGetCb, profile=profile), 214 callback=partial(self.private_data_get_cb, profile=profile),
215 errback=partial( 215 errback=partial(
216 G.host.errback, 216 G.host.errback,
217 title=_("can't get selected widget"), 217 title=_("can't get selected widget"),
218 message=_("error while retrieving selected widget: {msg}")) 218 message=_("error while retrieving selected widget: {msg}"))
219 ) 219 )
220 220
221 def onSelectedWidget(self, wid): 221 def on_selected_widget(self, wid):
222 """Store selected widget in backend, to restore it on next startup""" 222 """Store selected widget in backend, to restore it on next startup"""
223 if self.last_selected_wid == None: 223 if self.last_selected_wid == None:
224 self.last_selected_wid = wid 224 self.last_selected_wid = wid
225 # we skip the first selected widget, as we'll restore stored one if possible 225 # we skip the first selected widget, as we'll restore stored one if possible
226 return 226 return
252 data = { 252 data = {
253 "name": plugin_info["name"], 253 "name": plugin_info["name"],
254 "target": target, 254 "target": target,
255 } 255 }
256 256
257 G.host.bridge.privateDataSet( 257 G.host.bridge.private_data_set(
258 "cagou", "selected_widget", data_format.serialise(data), profile, 258 "cagou", "selected_widget", data_format.serialise(data), profile,
259 errback=partial( 259 errback=partial(
260 G.host.errback, 260 G.host.errback,
261 title=_("can set selected widget"), 261 title=_("can set selected widget"),
262 message=_("error while setting selected widget: {msg}")) 262 message=_("error while setting selected widget: {msg}"))
283 share_widget.close() 283 share_widget.close()
284 PythonActivity.moveTaskToBack(True) 284 PythonActivity.moveTaskToBack(True)
285 return True 285 return True
286 286
287 def _disconnect(self, profile): 287 def _disconnect(self, profile):
288 G.host.bridge.setParam( 288 G.host.bridge.param_set(
289 "autoconnect_backend", C.BOOL_FALSE, "Connection", -1, profile, 289 "autoconnect_backend", C.BOOL_FALSE, "Connection", -1, profile,
290 callback=lambda: log.info(f"profile {profile} autoconnection unset"), 290 callback=lambda: log.info(f"profile {profile} autoconnection unset"),
291 errback=lambda: log.error(f"can't unset {profile} autoconnection")) 291 errback=lambda: log.error(f"can't unset {profile} autoconnection"))
292 G.host.profiles.unplug(profile) 292 G.host.profiles.unplug(profile)
293 G.host.bridge.disconnect(profile) 293 G.host.bridge.disconnect(profile)
294 G.host.app.showProfileManager() 294 G.host.app.show_profile_manager()
295 G.host.closeUI() 295 G.host.close_ui()
296 296
297 def _on_disconnect(self): 297 def _on_disconnect(self):
298 current_profile = next(iter(G.host.profiles)) 298 current_profile = next(iter(G.host.profiles))
299 wid = dialog.ConfirmDialog( 299 wid = dialog.ConfirmDialog(
300 title=_("Are you sure to disconnect?"), 300 title=_("Are you sure to disconnect?"),
301 message=_( 301 message=_(
302 "If you disconnect the current user ({profile}), you won't receive " 302 "If you disconnect the current user ({profile}), you won't receive "
303 "any notification until you connect it again, is this really what you " 303 "any notification until you connect it again, is this really what you "
304 "want?").format(profile=current_profile), 304 "want?").format(profile=current_profile),
305 yes_cb=partial(self._disconnect, profile=current_profile), 305 yes_cb=partial(self._disconnect, profile=current_profile),
306 no_cb=G.host.closeUI, 306 no_cb=G.host.close_ui,
307 ) 307 )
308 G.host.showExtraUI(wid) 308 G.host.show_extra_ui(wid)
309 309
310 def on_extra_menu_init(self, extra_menu): 310 def on_extra_menu_init(self, extra_menu):
311 extra_menu.addItem(_('disconnect'), self._on_disconnect) 311 extra_menu.add_item(_('disconnect'), self._on_disconnect)
312 312
313 def updateParamsExtra(self, extra): 313 def update_params_extra(self, extra):
314 # on Android, we handle autoconnection automatically, 314 # on Android, we handle autoconnection automatically,
315 # user must not modify those parameters 315 # user must not modify those parameters
316 extra.update( 316 extra.update(
317 { 317 {
318 "ignore": [ 318 "ignore": [
321 ["Connection", "autodisconnect"], 321 ["Connection", "autodisconnect"],
322 ], 322 ],
323 } 323 }
324 ) 324 )
325 325
326 def getColDataFromUri(self, uri, col_name): 326 def get_col_data_from_uri(self, uri, col_name):
327 cursor = mActivity.getContentResolver().query(uri, None, None, None, None) 327 cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
328 if cursor is None: 328 if cursor is None:
329 return None 329 return None
330 try: 330 try:
331 cursor.moveToFirst() 331 cursor.moveToFirst()
334 return None 334 return None
335 return cursor.getString(col_idx) 335 return cursor.getString(col_idx)
336 finally: 336 finally:
337 cursor.close() 337 cursor.close()
338 338
339 def getFilenameFromUri(self, uri, media_type): 339 def get_filename_from_uri(self, uri, media_type):
340 filename = self.getColDataFromUri(uri, DISPLAY_NAME) 340 filename = self.get_col_data_from_uri(uri, DISPLAY_NAME)
341 if filename is None: 341 if filename is None:
342 uri_p = Path(uri.toString()) 342 uri_p = Path(uri.toString())
343 filename = uri_p.name or "unnamed" 343 filename = uri_p.name or "unnamed"
344 if not uri_p.suffix and media_type: 344 if not uri_p.suffix and media_type:
345 suffix = mimetypes.guess_extension(media_type, strict=False) 345 suffix = mimetypes.guess_extension(media_type, strict=False)
346 if suffix: 346 if suffix:
347 filename = filename + suffix 347 filename = filename + suffix
348 return filename 348 return filename
349 349
350 def getPathFromUri(self, uri): 350 def get_path_from_uri(self, uri):
351 # FIXME: using DATA is not recommended (and DATA is deprecated) 351 # FIXME: using DATA is not recommended (and DATA is deprecated)
352 # we should read directly the file with 352 # we should read directly the file with
353 # ContentResolver#openFileDescriptor(Uri, String) 353 # ContentResolver#openFileDescriptor(Uri, String)
354 path = self.getColDataFromUri(uri, DATA) 354 path = self.get_col_data_from_uri(uri, DATA)
355 return uri.getPath() if path is None else path 355 return uri.getPath() if path is None else path
356 356
357 def on_new_intent(self, intent): 357 def on_new_intent(self, intent):
358 log.debug("on_new_intent") 358 log.debug("on_new_intent")
359 action = intent.getAction(); 359 action = intent.getAction();
376 log.debug("cancelling restoration of previous widget") 376 log.debug("cancelling restoration of previous widget")
377 self.restore_selected_wid = False 377 self.restore_selected_wid = False
378 # and now we open the widget linked to the intent 378 # and now we open the widget linked to the intent
379 current_profile = next(iter(G.host.profiles)) 379 current_profile = next(iter(G.host.profiles))
380 Clock.schedule_once( 380 Clock.schedule_once(
381 lambda *args: G.host.doAction( 381 lambda *args: G.host.do_action(
382 widget, jid.JID(target), [current_profile]), 382 widget, jid.JID(target), [current_profile]),
383 0) 383 0)
384 else: 384 else:
385 log.warning(f"unexpected action: {action}") 385 log.warning(f"unexpected action: {action}")
386 386
398 item = intent.getParcelableExtra(Intent.EXTRA_STREAM) 398 item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
399 if item is not None: 399 if item is not None:
400 uri = cast('android.net.Uri', item) 400 uri = cast('android.net.Uri', item)
401 if uri.getScheme() == 'content': 401 if uri.getScheme() == 'content':
402 # Android content, we'll dump it to a temporary file 402 # Android content, we'll dump it to a temporary file
403 filename = self.getFilenameFromUri(uri, intent_type) 403 filename = self.get_filename_from_uri(uri, intent_type)
404 filepath = self.tmp_dir / filename 404 filepath = self.tmp_dir / filename
405 input_stream = mActivity.getContentResolver().openInputStream(uri) 405 input_stream = mActivity.getContentResolver().openInputStream(uri)
406 buff = bytearray(4096) 406 buff = bytearray(4096)
407 with open(filepath, 'wb') as f: 407 with open(filepath, 'wb') as f:
408 while True: 408 while True:
413 break 413 break
414 input_stream.close() 414 input_stream.close()
415 data['path'] = path = str(filepath) 415 data['path'] = path = str(filepath)
416 else: 416 else:
417 data['uri'] = uri.toString() 417 data['uri'] = uri.toString()
418 path = self.getPathFromUri(uri) 418 path = self.get_path_from_uri(uri)
419 if path is not None and path not in data: 419 if path is not None and path not in data:
420 data['path'] = path 420 data['path'] = path
421 else: 421 else:
422 uri = None 422 uri = None
423 path = None 423 path = None