async def _do_get_organization_stats(core: LoggedCore) -> OrganizationStats: try: return await core.get_organization_stats() except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _do_list_devices(core): try: return await core.get_user_devices_info() except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _create_new_device(self, device_label, file_path, passphrase): try: recovery_device = await load_recovery_device(file_path, passphrase) new_device = await generate_new_device_from_recovery( recovery_device, device_label) return new_device except LocalDeviceError as exc: self.button_validate.setEnabled(True) if "Decryption failed" in str(exc): show_error(self, translate("TEXT_IMPORT_KEY_WRONG_PASSPHRASE"), exception=exc) else: show_error(self, translate("IMPORT_KEY_LOCAL_DEVICE_ERROR"), exception=exc) raise JobResultError("error") from exc except BackendNotAvailable as exc: show_error(self, translate("IMPORT_KEY_BACKEND_OFFLINE"), exception=exc) raise JobResultError("backend-error") from exc except BackendConnectionError as exc: show_error(self, translate("IMPORT_KEY_BACKEND_ERROR"), exception=exc) raise JobResultError("backend-error") from exc except Exception as exc: show_error(self, translate("IMPORT_KEY_ERROR"), exception=exc) raise JobResultError("error") from exc
async def _do_cancel_invitation(core, token): try: await core.delete_invitation(token=token) except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _export_recovery_device(self, config_dir, device, export_path): try: recovery_device = await generate_recovery_device(device) file_name = get_recovery_device_file_name(recovery_device) file_path = export_path / file_name passphrase = await save_recovery_device(file_path, recovery_device) return recovery_device, file_path, passphrase except BackendNotAvailable as exc: show_error(self, translate("EXPORT_KEY_BACKEND_OFFLINE"), exception=exc) raise JobResultError("backend-error") from exc except BackendConnectionError as exc: show_error(self, translate("EXPORT_KEY_BACKEND_ERROR"), exception=exc) raise JobResultError("backend-error") from exc except LocalDeviceAlreadyExistsError as exc: show_error(self, translate("TEXT_RECOVERY_DEVICE_FILE_ALREADY_EXISTS"), exception=exc) raise JobResultError("already-exists") from exc except Exception as exc: show_error(self, translate("EXPORT_KEY_ERROR"), exception=exc) raise JobResultError("error") from exc self.button_validate.setEnabled(True)
async def _do_list_users_and_invitations(core, page, pattern=None, omit_revoked=False, omit_invitation=False): try: if not pattern: users, total = await core.find_humans(page=page, per_page=USERS_PER_PAGE, omit_revoked=omit_revoked) invitations = [] if omit_invitation else await core.list_invitations( ) return total, users, [ inv for inv in invitations if inv["type"] == InvitationType.USER ] else: users, total = await core.find_humans(page=page, per_page=USERS_PER_PAGE, query=pattern, omit_revoked=omit_revoked) return total, users, [] except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _do_revoke_user(core, user_info): try: await core.revoke_user(user_info.user_id) user_info = await core.get_user_info(user_info.user_id) return user_info except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _do_invite_device(core): try: addr, email_sent_status = await core.new_device_invitation( send_email=False) return addr, email_sent_status except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _do_import(self, sources: Iterable[str], dest: FsPath) -> None: # The file list is used in the error handler files = [] try: # Get the list of files to import with the corresponding size files, total_size = await self._get_files_from_sources( sources, dest) # Make the job cancellable with trio.CancelScope() as cancel_scope: # Create loading dialog and connect to the cancel scope wl = LoadingWidget(total_size=total_size + len(files)) loading_dialog = GreyedDialog( wl, _("TEXT_FILE_IMPORT_LOADING_TITLE"), parent=self) wl.cancelled.connect(cancel_scope.cancel) # Control the visibility andl life-cyle of the loading dialog try: loading_dialog.show() # Actually perform the import errors = await self._import_all(files, loading_dialog) finally: loading_dialog.hide() loading_dialog.setParent(None) # Process the errors if errors: text = (_("TEXT_FILE_IMPORT_ONE_PERMISSION_ERROR") if len(files) == 1 else _("TEXT_FILE_IMPORT_MULTIPLE_PERMISSION_ERROR")) show_error(self, text, exception=errors[0]) raise JobResultError("error", exceptions=errors) if cancel_scope.cancel_called: raise JobResultError("cancelled") # Propagate job result errors except JobResultError: raise # Disk full except FSLocalStorageOperationalError as exc: text = _("TEXT_FILE_IMPORT_LOCAL_STORAGE_ERROR") show_error(self, text, exception=exc) raise JobResultError("error") from exc # Show a dialog when an unexpected error occurs except Exception as exc: text = (_("TEXT_FILE_IMPORT_ONE_ERROR") if len(files) == 1 else _("TEXT_FILE_IMPORT_MULTIPLE_ERROR")) show_error(self, text, exception=exc) raise
async def _do_rename(workspace_fs, paths): new_names = {} for (old_path, new_path, entry_id) in paths: try: await workspace_fs.rename(old_path, new_path) new_names[entry_id] = FsPath(new_path).name except FileExistsError as exc: raise JobResultError("already-exists", multi=len(paths) > 1) from exc except OSError as exc: raise JobResultError("not-empty", multi=len(paths) > 1) from exc
async def _do_invite_user(core, email): try: invitation_addr, email_sent_status = await core.new_user_invitation( email=email, send_email=True) return email, invitation_addr, email_sent_status except BackendNotAvailable as exc: raise JobResultError("offline") from exc except BackendInvitationOnExistingMember as exc: raise JobResultError("already_member") from exc except BackendConnectionError as exc: raise JobResultError("error") from exc
async def _do_workspace_rename(core, workspace_id, new_name, button): try: new_name = EntryName(new_name) except ValueError: # This should never occurs given new_name is checked by a validator in the GUI # TODO: improve this logic ? raise JobResultError("invalid-name") try: await core.user_fs.workspace_rename(workspace_id, new_name) return button, new_name except Exception as exc: raise JobResultError("rename-error") from exc
async def claim_device(self, device_label: DeviceLabel) -> LocalDevice: await self.main_oob_send.send(self.Step.ClaimDevice) await self.main_oob_send.send(device_label) r, exc, new_device = await self.job_oob_recv.receive() if not r: raise JobResultError(status="claim-device-failed", origin=exc) return new_device
async def create_new_user(self, human_handle: HumanHandle, device_label: DeviceLabel, profile: UserProfile) -> None: await self.main_mc_send.send(self.Step.CreateNewUser) await self.main_mc_send.send((human_handle, device_label, profile)) r, exc = await self.job_mc_recv.receive() if not r: raise JobResultError(status="create-new-user-failed", origin=exc)
async def claim_user(self, device_label: DeviceLabel, human_handle: HumanHandle): await self.main_oob_send.send(self.Step.ClaimUser) await self.main_oob_send.send((device_label, human_handle)) r, exc, new_device = await self.job_oob_recv.receive() if not r: raise JobResultError(status="claim-user-failed", origin=exc) return new_device
async def _do_workspace_create(core, workspace_name): try: workspace_name = EntryName(workspace_name) except ValueError: # This should never occurs given new_name is checked by a validator in the GUI # TODO: improve this logic ? raise JobResultError("invalid-name") workspace_id = await core.user_fs.workspace_create(workspace_name) return workspace_id
def _handle_fs_errors(): try: yield except FSBackendOfflineError as exc: raise JobResultError(ret=workspace_id, status="offline-backend", origin=exc) except FSWorkspaceNoAccess as exc: raise JobResultError(ret=workspace_id, status="access-error", origin=exc) except FSWorkspaceNotFoundError as exc: raise JobResultError(ret=workspace_id, status="not-found", origin=exc) except FSError as exc: raise JobResultError(ret=workspace_id, status="fs-error", origin=exc)
async def _do_delete(workspace_fs, files, silent=False): for path, file_type in files: try: if file_type == FileType.Folder: await workspace_fs.rmtree(path) else: await workspace_fs.unlink(path) except Exception as exc: if not silent: raise JobResultError("error", multi=len(files) > 1) from exc
async def _do_create_org( config, human_handle: HumanHandle, device_label: DeviceLabel, backend_addr: BackendOrganizationBootstrapAddr, ): try: new_device = await bootstrap_organization(backend_addr, human_handle=human_handle, device_label=device_label) # The organization is brand new, of course there is no existing # remote user manifest, hence our placeholder is non-speculative. await user_storage_non_speculative_init( data_base_dir=config.data_base_dir, device=new_device) return new_device except InviteNotFoundError as exc: raise JobResultError("invite-not-found", exc=exc) except InviteAlreadyUsedError as exc: raise JobResultError("invite-already-used", exc=exc) except BackendConnectionRefused as exc: raise JobResultError("connection-refused", exc=exc) except BackendNotAvailable as exc: raise JobResultError("connection-error", exc=exc) except BackendOutOfBallparkError as exc: raise JobResultError("out-of-ballpark", exc=exc) except InviteError as exc: raise JobResultError("invite-error", exc=exc)
async def run(self, core, token): try: r = await self.main_mc_recv.receive() assert r == self.Step.WaitPeer try: in_progress_ctx = await core.start_greeting_device(token=token) await self.job_mc_send.send((True, None)) except Exception as exc: await self.job_mc_send.send((False, exc)) r = await self.main_mc_recv.receive() assert r == self.Step.GetGreeterSas await self.job_mc_send.send(in_progress_ctx.greeter_sas) r = await self.main_mc_recv.receive() assert r == self.Step.WaitPeerTrust try: in_progress_ctx = await in_progress_ctx.do_wait_peer_trust() await self.job_mc_send.send((True, None)) except Exception as exc: await self.job_mc_send.send((False, exc)) r = await self.main_mc_recv.receive() assert r == self.Step.GetClaimerSas try: choices = in_progress_ctx.generate_claimer_sas_choices(size=4) await self.job_mc_send.send( (True, None, in_progress_ctx.claimer_sas, choices)) except Exception as exc: await self.job_mc_send.send((False, exc, None, None)) r = await self.main_mc_recv.receive() assert r == self.Step.SignifyTrust try: in_progress_ctx = await in_progress_ctx.do_signify_trust() in_progress_ctx = await in_progress_ctx.do_get_claim_requests() await in_progress_ctx.do_create_new_device( author=core.device, device_label=in_progress_ctx.requested_device_label) await self.job_mc_send.send((True, None)) except InviteError as exc: await self.job_mc_send.send((False, exc)) except Exception as exc: await self.job_mc_send.send((False, exc)) except BackendNotAvailable as exc: raise JobResultError(status="backend-not-available", origin=exc)
async def _do_share_workspace(user_fs, workspace_fs, user_info, role): workspace_name = workspace_fs.get_workspace_name() try: await user_fs.workspace_share(workspace_fs.workspace_id, user_info.user_id, role) return user_info, role, workspace_name except ValueError as exc: raise JobResultError( "invalid-user", user_info=user_info, role=role, workspace_name=workspace_name ) from exc except FSBackendOfflineError as exc: raise JobResultError( "offline", user_info=user_info, role=role, workspace_name=workspace_name ) from exc except FSError as exc: raise JobResultError( "fs-error", user_info=user_info, role=role, workspace_name=workspace_name ) from exc except Exception as exc: raise JobResultError( "error", user_info=user_info, role=role, workspace_name=workspace_name ) from exc
async def _do_copy_files(workspace_fs, target_dir, source_files, source_workspace): last_exc = None error_count = 0 for src, src_type in source_files: # In order to be able to rename the file if a file of the same name already exists # we need the name without extensions. name_we, *_ = src.name.str.split(".", 1) count = 2 file_name = src.name while True: try: dst = target_dir / file_name if src_type == FileType.Folder: await workspace_fs.copytree(src, dst, source_workspace) else: await workspace_fs.copyfile(src, dst, source_workspace) break except FileExistsError: # File already exists, we append a counter at the end of its name file_name = EntryName("{} ({}){}".format( name_we, count, "".join(pathlib.Path(src.name.str).suffixes))) count += 1 except FSInvalidArgumentError as exc: # Move a file onto itself # Not a big deal for files, we just do nothing and pretend we # actually did something # For folders we have to warn the user if src_type == FileType.Folder: error_count += 1 last_exc = exc break except Exception as exc: # No idea what happened, we'll just warn the user that we encountered an # unexcepted error and log it error_count += 1 last_exc = exc logger.exception("Unhandled error while cut/copy file", exc_info=exc) break if error_count: raise JobResultError("error", last_exc=last_exc, error_count=error_count)
async def _do_get_users(core, workspace_fs): ret = {} try: participants = await workspace_fs.get_user_roles() updated_participants = {} for user, role in participants.items(): user_info = await core.get_user_info(user) updated_participants[user_info] = role # TODO: handle pagination users, _ = await core.find_humans() for user_info, role in updated_participants.items(): ret[user_info] = role for user_info in users: if user_info not in ret: ret[user_info] = NOT_SHARED_KEY return ret except BackendNotAvailable as exc: raise JobResultError("offline") from exc
async def get_claim_requests(self): await self.main_mc_send.send(self.Step.GetClaimRequests) r, exc, human_handle, device_label = await self.job_mc_recv.receive() if not r: raise JobResultError(status="get-claim-request-failed", origin=exc) return human_handle, device_label
async def signify_trust(self): await self.main_mc_send.send(self.Step.SignifyTrust) r, exc = await self.job_mc_recv.receive() if not r: raise JobResultError(status="signify-trust-failed", origin=exc)
async def get_claimer_sas(self): await self.main_mc_send.send(self.Step.GetClaimerSas) r, exc, claimer_sas, choices = await self.job_mc_recv.receive() if not r: raise JobResultError(status="get-claimer-sas-failed", origin=exc) return claimer_sas, choices
async def wait_peer_trust(self): await self.main_mc_send.send(self.Step.WaitPeerTrust) r, exc = await self.job_mc_recv.receive() if not r: raise JobResultError(status="wait-peer-trust-failed", origin=exc)
async def run(self, addr, config): try: async with backend_invited_cmds_factory( addr=addr, keepalive=config.backend_connection_keepalive) as cmds: # Trigger handshake await cmds.ping() r = await self.main_oob_recv.receive() assert r == self.Step.RetrieveInfo try: initial_ctx = await claimer_retrieve_info(cmds) await self.job_oob_send.send( (True, None, initial_ctx.claimer_email)) except Exception as exc: await self.job_oob_send.send((False, exc, None)) r = await self.main_oob_recv.receive() assert r == self.Step.WaitPeer try: in_progress_ctx = await initial_ctx.do_wait_peer() await self.job_oob_send.send((True, None)) except Exception as exc: await self.job_oob_send.send((False, exc)) r = await self.main_oob_recv.receive() assert r == self.Step.GetGreeterSas try: choices = in_progress_ctx.generate_greeter_sas_choices( size=4) await self.job_oob_send.send( (True, None, in_progress_ctx.greeter_sas, choices)) except Exception as exc: await self.job_oob_send.send((False, exc, None, None)) r = await self.main_oob_recv.receive() assert r == self.Step.SignifyTrust try: in_progress_ctx = await in_progress_ctx.do_signify_trust() await self.job_oob_send.send((True, None)) except Exception as exc: await self.job_oob_send.send((False, exc)) r = await self.main_oob_recv.receive() assert r == self.Step.GetClaimerSas await self.job_oob_send.send(in_progress_ctx.claimer_sas) r = await self.main_oob_recv.receive() assert r == self.Step.WaitPeerTrust try: in_progress_ctx = await in_progress_ctx.do_wait_peer_trust( ) await self.job_oob_send.send((True, None)) except Exception as exc: await self.job_oob_send.send((False, exc)) r = await self.main_oob_recv.receive() assert r == self.Step.ClaimUser try: device_label, human_handle = await self.main_oob_recv.receive( ) new_device = await in_progress_ctx.do_claim_user( requested_device_label=device_label, requested_human_handle=human_handle) # Claiming a user means we are it first device, hence we know there # is no existing user manifest (hence our placeholder is non-speculative) await user_storage_non_speculative_init( data_base_dir=config.data_base_dir, device=new_device) await self.job_oob_send.send((True, None, new_device)) except Exception as exc: await self.job_oob_send.send((False, exc, None)) except BackendNotAvailable as exc: raise JobResultError(status="backend-not-available", origin=exc) except BackendInvitationAlreadyUsed as exc: raise JobResultError(status="invitation-already-used", origin=exc) except BackendConnectionRefused as exc: raise JobResultError(status="invitation-not-found", origin=exc) except BackendOutOfBallparkError as exc: raise JobResultError(status="out-of-ballpark", origin=exc)
async def get_greeter_sas(self): await self.main_oob_send.send(self.Step.GetGreeterSas) r, exc, greeter_sas, choices = await self.job_oob_recv.receive() if not r: raise JobResultError(status="get-greeter-sas-failed", origin=exc) return greeter_sas, choices
async def wait_peer(self): await self.main_oob_send.send(self.Step.WaitPeer) r, exc = await self.job_oob_recv.receive() if not r: raise JobResultError(status="wait-peer-failed", origin=exc)