comparison sat/plugins/plugin_comp_file_sharing_management.py @ 3541:888109774673

core: various changes and fixes to work with new storage and D-Bus bridge: - fixes coroutines handling in various places - fixes types which are not serialised by Tx DBus - XEP-0384: call storage methods in main thread in XEP: Python OMEMO's Promise use thread which prevent the use of AsyncIO loop. To work around that, callLater is used to launch storage method in main thread. This is a temporary workaround, as Python OMEMO should get rid of Promise implementation and threads soon.
author Goffi <goffi@goffi.org>
date Thu, 03 Jun 2021 15:21:43 +0200
parents a8259a1f89b2
children 7af29260ecb8
comparison
equal deleted inserted replaced
3540:aa58451b77ba 3541:888109774673
147 form.addField(field) 147 form.addField(field)
148 148
149 payload = form.toElement() 149 payload = form.toElement()
150 return payload, status, None, None 150 return payload, status, None, None
151 151
152 @defer.inlineCallbacks 152 async def _getFileData(self, client, session_data, command_form):
153 def _getFileData(self, client, session_data, command_form):
154 """Retrieve field requested in root form 153 """Retrieve field requested in root form
155 154
156 "found_file" will also be set in session_data 155 "found_file" will also be set in session_data
157 @param command_form(data_form.Form): response to root form 156 @param command_form(data_form.Form): response to root form
158 @return (D(dict)): found file data 157 @return (D(dict)): found file data
175 174
176 # TODO: if parent_path and basename are empty, we ask for root directory 175 # TODO: if parent_path and basename are empty, we ask for root directory
177 # this must be managed 176 # this must be managed
178 177
179 try: 178 try:
180 found_files = yield self.host.memory.getFiles( 179 found_files = await self.host.memory.getFiles(
181 client, requestor_bare, path=parent_path, name=basename, 180 client, requestor_bare, path=parent_path, name=basename,
182 namespace=namespace) 181 namespace=namespace)
183 found_file = found_files[0] 182 found_file = found_files[0]
184 except (exceptions.NotFound, IndexError): 183 except (exceptions.NotFound, IndexError):
185 raise WorkflowError(self._err(_("file not found"))) 184 raise WorkflowError(self._err(_("file not found")))
191 log.warning(_("Only owner can manage files")) 190 log.warning(_("Only owner can manage files"))
192 raise WorkflowError(self._err(_("forbidden"))) 191 raise WorkflowError(self._err(_("forbidden")))
193 192
194 session_data['found_file'] = found_file 193 session_data['found_file'] = found_file
195 session_data['namespace'] = namespace 194 session_data['namespace'] = namespace
196 defer.returnValue(found_file) 195 return found_file
197 196
198 def _updateReadPermission(self, access, allowed_jids): 197 def _updateReadPermission(self, access, allowed_jids):
199 if not allowed_jids: 198 if not allowed_jids:
200 if C.ACCESS_PERM_READ in access: 199 if C.ACCESS_PERM_READ in access:
201 del access[C.ACCESS_PERM_READ] 200 del access[C.ACCESS_PERM_READ]
207 access[C.ACCESS_PERM_READ] = { 206 access[C.ACCESS_PERM_READ] = {
208 "type": C.ACCESS_TYPE_WHITELIST, 207 "type": C.ACCESS_TYPE_WHITELIST,
209 "jids": [j.full() for j in allowed_jids] 208 "jids": [j.full() for j in allowed_jids]
210 } 209 }
211 210
212 @defer.inlineCallbacks 211 async def _updateDir(self, client, requestor, namespace, file_data, allowed_jids):
213 def _updateDir(self, client, requestor, namespace, file_data, allowed_jids):
214 """Recursively update permission of a directory and all subdirectories 212 """Recursively update permission of a directory and all subdirectories
215 213
216 @param file_data(dict): metadata of the file 214 @param file_data(dict): metadata of the file
217 @param allowed_jids(list[jid.JID]): list of entities allowed to read the file 215 @param allowed_jids(list[jid.JID]): list of entities allowed to read the file
218 """ 216 """
219 assert file_data['type'] == C.FILE_TYPE_DIRECTORY 217 assert file_data['type'] == C.FILE_TYPE_DIRECTORY
220 files_data = yield self.host.memory.getFiles( 218 files_data = await self.host.memory.getFiles(
221 client, requestor, parent=file_data['id'], namespace=namespace) 219 client, requestor, parent=file_data['id'], namespace=namespace)
222 220
223 for file_data in files_data: 221 for file_data in files_data:
224 if not file_data['access'].get(C.ACCESS_PERM_READ, {}): 222 if not file_data['access'].get(C.ACCESS_PERM_READ, {}):
225 log.debug("setting {perm} read permission for {name}".format( 223 log.debug("setting {perm} read permission for {name}".format(
226 perm=allowed_jids, name=file_data['name'])) 224 perm=allowed_jids, name=file_data['name']))
227 yield self.host.memory.fileUpdate( 225 await self.host.memory.fileUpdate(
228 file_data['id'], 'access', 226 file_data['id'], 'access',
229 partial(self._updateReadPermission, allowed_jids=allowed_jids)) 227 partial(self._updateReadPermission, allowed_jids=allowed_jids))
230 if file_data['type'] == C.FILE_TYPE_DIRECTORY: 228 if file_data['type'] == C.FILE_TYPE_DIRECTORY:
231 yield self._updateDir(client, requestor, namespace, file_data, 'PUBLIC') 229 await self._updateDir(client, requestor, namespace, file_data, 'PUBLIC')
232 230
233 @defer.inlineCallbacks 231 async def _onChangeFile(self, client, command_elt, session_data, action, node):
234 def _onChangeFile(self, client, command_elt, session_data, action, node):
235 try: 232 try:
236 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) 233 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
237 command_form = data_form.Form.fromElement(x_elt) 234 command_form = data_form.Form.fromElement(x_elt)
238 except StopIteration: 235 except StopIteration:
239 command_form = None 236 command_form = None
242 requestor = session_data['requestor'] 239 requestor = session_data['requestor']
243 requestor_bare = requestor.userhostJID() 240 requestor_bare = requestor.userhostJID()
244 241
245 if command_form is None or len(command_form.fields) == 0: 242 if command_form is None or len(command_form.fields) == 0:
246 # root request 243 # root request
247 defer.returnValue(self._getRootArgs()) 244 return self._getRootArgs()
248 245
249 elif found_file is None: 246 elif found_file is None:
250 # file selected, we retrieve it and ask for permissions 247 # file selected, we retrieve it and ask for permissions
251 try: 248 try:
252 found_file = yield self._getFileData(client, session_data, command_form) 249 found_file = await self._getFileData(client, session_data, command_form)
253 except WorkflowError as e: 250 except WorkflowError as e:
254 defer.returnValue(e.err_args) 251 return e.err_args
255 252
256 # management request 253 # management request
257 if found_file['type'] == C.FILE_TYPE_DIRECTORY: 254 if found_file['type'] == C.FILE_TYPE_DIRECTORY:
258 instructions = D_("Please select permissions for this directory") 255 instructions = D_("Please select permissions for this directory")
259 else: 256 else:
282 ) 279 )
283 form.addField(field) 280 form.addField(field)
284 281
285 status = self._c.STATUS.EXECUTING 282 status = self._c.STATUS.EXECUTING
286 payload = form.toElement() 283 payload = form.toElement()
287 defer.returnValue((payload, status, None, None)) 284 return (payload, status, None, None)
288 285
289 else: 286 else:
290 # final phase, we'll do permission change here 287 # final phase, we'll do permission change here
291 try: 288 try:
292 read_allowed = command_form.fields['read_allowed'] 289 read_allowed = command_form.fields['read_allowed']
305 log.warning(_("Can't use read_allowed values: {reason}").format( 302 log.warning(_("Can't use read_allowed values: {reason}").format(
306 reason=e)) 303 reason=e))
307 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD) 304 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD)
308 305
309 if found_file['type'] == C.FILE_TYPE_FILE: 306 if found_file['type'] == C.FILE_TYPE_FILE:
310 yield self.host.memory.fileUpdate( 307 await self.host.memory.fileUpdate(
311 found_file['id'], 'access', 308 found_file['id'], 'access',
312 partial(self._updateReadPermission, allowed_jids=allowed_jids)) 309 partial(self._updateReadPermission, allowed_jids=allowed_jids))
313 else: 310 else:
314 try: 311 try:
315 recursive = command_form.fields['recursive'] 312 recursive = command_form.fields['recursive']
316 except KeyError: 313 except KeyError:
317 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD) 314 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD)
318 yield self.host.memory.fileUpdate( 315 await self.host.memory.fileUpdate(
319 found_file['id'], 'access', 316 found_file['id'], 'access',
320 partial(self._updateReadPermission, allowed_jids=allowed_jids)) 317 partial(self._updateReadPermission, allowed_jids=allowed_jids))
321 if recursive: 318 if recursive:
322 # we set all file under the directory as public (if they haven't 319 # we set all file under the directory as public (if they haven't
323 # already a permission set), so allowed entities of root directory 320 # already a permission set), so allowed entities of root directory
324 # can read them. 321 # can read them.
325 namespace = session_data['namespace'] 322 namespace = session_data['namespace']
326 yield self._updateDir( 323 await self._updateDir(
327 client, requestor_bare, namespace, found_file, 'PUBLIC') 324 client, requestor_bare, namespace, found_file, 'PUBLIC')
328 325
329 # job done, we can end the session 326 # job done, we can end the session
330 status = self._c.STATUS.COMPLETED 327 status = self._c.STATUS.COMPLETED
331 payload = None 328 payload = None
332 note = (self._c.NOTE.INFO, _("management session done")) 329 note = (self._c.NOTE.INFO, _("management session done"))
333 defer.returnValue((payload, status, None, note)) 330 return (payload, status, None, note)
334 331
335 @defer.inlineCallbacks 332 async def _onDeleteFile(self, client, command_elt, session_data, action, node):
336 def _onDeleteFile(self, client, command_elt, session_data, action, node):
337 try: 333 try:
338 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) 334 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
339 command_form = data_form.Form.fromElement(x_elt) 335 command_form = data_form.Form.fromElement(x_elt)
340 except StopIteration: 336 except StopIteration:
341 command_form = None 337 command_form = None
344 requestor = session_data['requestor'] 340 requestor = session_data['requestor']
345 requestor_bare = requestor.userhostJID() 341 requestor_bare = requestor.userhostJID()
346 342
347 if command_form is None or len(command_form.fields) == 0: 343 if command_form is None or len(command_form.fields) == 0:
348 # root request 344 # root request
349 defer.returnValue(self._getRootArgs()) 345 return self._getRootArgs()
350 346
351 elif found_file is None: 347 elif found_file is None:
352 # file selected, we need confirmation before actually deleting 348 # file selected, we need confirmation before actually deleting
353 try: 349 try:
354 found_file = yield self._getFileData(client, session_data, command_form) 350 found_file = await self._getFileData(client, session_data, command_form)
355 except WorkflowError as e: 351 except WorkflowError as e:
356 defer.returnValue(e.err_args) 352 return e.err_args
357 if found_file['type'] == C.FILE_TYPE_DIRECTORY: 353 if found_file['type'] == C.FILE_TYPE_DIRECTORY:
358 msg = D_("Are you sure to delete directory {name} and all files and " 354 msg = D_("Are you sure to delete directory {name} and all files and "
359 "directories under it?").format(name=found_file['name']) 355 "directories under it?").format(name=found_file['name'])
360 else: 356 else:
361 msg = D_("Are you sure to delete file {name}?" 357 msg = D_("Are you sure to delete file {name}?"
368 desc="check this box to confirm" 364 desc="check this box to confirm"
369 ) 365 )
370 form.addField(field) 366 form.addField(field)
371 status = self._c.STATUS.EXECUTING 367 status = self._c.STATUS.EXECUTING
372 payload = form.toElement() 368 payload = form.toElement()
373 defer.returnValue((payload, status, None, None)) 369 return (payload, status, None, None)
374 370
375 else: 371 else:
376 # final phase, we'll do deletion here 372 # final phase, we'll do deletion here
377 try: 373 try:
378 confirmed = C.bool(command_form.fields['confirm'].value) 374 confirmed = C.bool(command_form.fields['confirm'].value)
380 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD) 376 self._c.adHocError(self._c.ERROR.BAD_PAYLOAD)
381 if not confirmed: 377 if not confirmed:
382 note = None 378 note = None
383 else: 379 else:
384 recursive = found_file['type'] == C.FILE_TYPE_DIRECTORY 380 recursive = found_file['type'] == C.FILE_TYPE_DIRECTORY
385 yield self.host.memory.fileDelete( 381 await self.host.memory.fileDelete(
386 client, requestor_bare, found_file['id'], recursive) 382 client, requestor_bare, found_file['id'], recursive)
387 note = (self._c.NOTE.INFO, _("file deleted")) 383 note = (self._c.NOTE.INFO, _("file deleted"))
388 status = self._c.STATUS.COMPLETED 384 status = self._c.STATUS.COMPLETED
389 payload = None 385 payload = None
390 defer.returnValue((payload, status, None, note)) 386 return (payload, status, None, note)
391 387
392 def _updateThumbs(self, extra, thumbnails): 388 def _updateThumbs(self, extra, thumbnails):
393 extra[C.KEY_THUMBNAILS] = thumbnails 389 extra[C.KEY_THUMBNAILS] = thumbnails
394 390
395 @defer.inlineCallbacks 391 async def _genThumbs(self, client, requestor, namespace, file_data):
396 def _genThumbs(self, client, requestor, namespace, file_data):
397 """Recursively generate thumbnails 392 """Recursively generate thumbnails
398 393
399 @param file_data(dict): metadata of the file 394 @param file_data(dict): metadata of the file
400 """ 395 """
401 if file_data['type'] == C.FILE_TYPE_DIRECTORY: 396 if file_data['type'] == C.FILE_TYPE_DIRECTORY:
402 sub_files_data = yield self.host.memory.getFiles( 397 sub_files_data = await self.host.memory.getFiles(
403 client, requestor, parent=file_data['id'], namespace=namespace) 398 client, requestor, parent=file_data['id'], namespace=namespace)
404 for sub_file_data in sub_files_data: 399 for sub_file_data in sub_files_data:
405 yield self._genThumbs(client, requestor, namespace, sub_file_data) 400 await self._genThumbs(client, requestor, namespace, sub_file_data)
406 401
407 elif file_data['type'] == C.FILE_TYPE_FILE: 402 elif file_data['type'] == C.FILE_TYPE_FILE:
408 media_type = file_data['media_type'] 403 media_type = file_data['media_type']
409 file_path = os.path.join(self.files_path, file_data['file_hash']) 404 file_path = os.path.join(self.files_path, file_data['file_hash'])
410 if media_type == 'image': 405 if media_type == 'image':
411 thumbnails = [] 406 thumbnails = []
412 407
413 for max_thumb_size in self._t.SIZES: 408 for max_thumb_size in self._t.SIZES:
414 try: 409 try:
415 thumb_size, thumb_id = yield self._t.generateThumbnail( 410 thumb_size, thumb_id = await self._t.generateThumbnail(
416 file_path, 411 file_path,
417 max_thumb_size, 412 max_thumb_size,
418 #  we keep thumbnails for 6 months 413 #  we keep thumbnails for 6 months
419 60 * 60 * 24 * 31 * 6, 414 60 * 60 * 24 * 31 * 6,
420 ) 415 )
422 log.warning(_("Can't create thumbnail: {reason}") 417 log.warning(_("Can't create thumbnail: {reason}")
423 .format(reason=e)) 418 .format(reason=e))
424 break 419 break
425 thumbnails.append({"id": thumb_id, "size": thumb_size}) 420 thumbnails.append({"id": thumb_id, "size": thumb_size})
426 421
427 yield self.host.memory.fileUpdate( 422 await self.host.memory.fileUpdate(
428 file_data['id'], 'extra', 423 file_data['id'], 'extra',
429 partial(self._updateThumbs, thumbnails=thumbnails)) 424 partial(self._updateThumbs, thumbnails=thumbnails))
430 425
431 log.info("thumbnails for [{file_name}] generated" 426 log.info("thumbnails for [{file_name}] generated"
432 .format(file_name=file_data['name'])) 427 .format(file_name=file_data['name']))
433 428
434 else: 429 else:
435 log.warning("unmanaged file type: {type_}".format(type_=file_data['type'])) 430 log.warning("unmanaged file type: {type_}".format(type_=file_data['type']))
436 431
437 @defer.inlineCallbacks 432 async def _onGenThumbnails(self, client, command_elt, session_data, action, node):
438 def _onGenThumbnails(self, client, command_elt, session_data, action, node):
439 try: 433 try:
440 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) 434 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
441 command_form = data_form.Form.fromElement(x_elt) 435 command_form = data_form.Form.fromElement(x_elt)
442 except StopIteration: 436 except StopIteration:
443 command_form = None 437 command_form = None
445 found_file = session_data.get('found_file') 439 found_file = session_data.get('found_file')
446 requestor = session_data['requestor'] 440 requestor = session_data['requestor']
447 441
448 if command_form is None or len(command_form.fields) == 0: 442 if command_form is None or len(command_form.fields) == 0:
449 # root request 443 # root request
450 defer.returnValue(self._getRootArgs()) 444 return self._getRootArgs()
451 445
452 elif found_file is None: 446 elif found_file is None:
453 # file selected, we retrieve it and ask for permissions 447 # file selected, we retrieve it and ask for permissions
454 try: 448 try:
455 found_file = yield self._getFileData(client, session_data, command_form) 449 found_file = await self._getFileData(client, session_data, command_form)
456 except WorkflowError as e: 450 except WorkflowError as e:
457 defer.returnValue(e.err_args) 451 return e.err_args
458 452
459 log.info("Generating thumbnails as requested") 453 log.info("Generating thumbnails as requested")
460 yield self._genThumbs(client, requestor, found_file['namespace'], found_file) 454 await self._genThumbs(client, requestor, found_file['namespace'], found_file)
461 455
462 # job done, we can end the session 456 # job done, we can end the session
463 status = self._c.STATUS.COMPLETED 457 status = self._c.STATUS.COMPLETED
464 payload = None 458 payload = None
465 note = (self._c.NOTE.INFO, _("thumbnails generated")) 459 note = (self._c.NOTE.INFO, _("thumbnails generated"))
466 defer.returnValue((payload, status, None, note)) 460 return (payload, status, None, note)
467 461
468 async def _onQuota(self, client, command_elt, session_data, action, node): 462 async def _onQuota(self, client, command_elt, session_data, action, node):
469 requestor = session_data['requestor'] 463 requestor = session_data['requestor']
470 quota = self.host.plugins["file_sharing"].getQuota(client, requestor) 464 quota = self.host.plugins["file_sharing"].getQuota(client, requestor)
471 try: 465 try: