def show_modal( cls, jobs_ctx, workspace_fs, path, reload_timestamped_signal, update_version_list, close_version_list, client, parent, on_finished, ): w = cls( jobs_ctx=jobs_ctx, workspace_fs=workspace_fs, path=path, reload_timestamped_signal=reload_timestamped_signal, update_version_list=update_version_list, close_version_list=close_version_list, client=client, ) d = GreyedDialog( w, title=_("TEXT_FILE_HISTORY_TITLE_name").format(name=path.name), parent=parent) w.dialog = d if on_finished: d.finished.connect(on_finished) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show() return w
def show_modal(cls, jobs_ctx, parent, on_finished): w = cls(jobs_ctx) d = GreyedDialog(w, _("TEXT_ORG_WIZARD_TITLE"), parent=parent, width=1000) d.closing.connect(w.on_close) w.accepted.connect(d.accept) w.finished.connect(on_finished) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show()
def show_modal(cls, client, parent): w = cls(client=client) d = GreyedDialog(w, title=_("TEXT_CHANGE_PASSWORD_TITLE"), parent=parent) w.accepted.connect(d.accept) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show()
def show_modal(cls, user_fs, workspace_fs, client, jobs_ctx, parent, on_finished): w = cls(user_fs=user_fs, workspace_fs=workspace_fs, client=client, jobs_ctx=jobs_ctx) d = GreyedDialog(w, title=_("TEXT_WORKSPACE_SHARING_TITLE"), parent=parent, width=1000) d.closing.connect(w.on_close) w.closing.connect(on_finished) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show() return w
def show_modal(cls, jobs_ctx, config, addr, parent, on_finished): w = cls(jobs_ctx=jobs_ctx, config=config, addr=addr) d = GreyedDialog(w, _("TEXT_CLAIM_DEVICE_TITLE"), parent=parent, width=800) d.closing.connect(w.on_close) w.finished.connect(on_finished) w.accepted.connect(d.accept) w.rejected.connect(d.reject) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show()
def show_modal(cls, client, jobs_ctx, token, parent, on_finished): w = cls(client=client, jobs_ctx=jobs_ctx, token=token) d = GreyedDialog(w, _("TEXT_GREET_USER_TITLE"), parent=parent, width=950) d.closing.connect(w.on_close) w.finished.connect(on_finished) w.accepted.connect(d.accept) w.rejected.connect(d.reject) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show() return w
def show_modal(cls, workspace_fs, jobs_ctx, parent, on_finished): w = cls(workspace_fs=workspace_fs, jobs_ctx=jobs_ctx) d = GreyedDialog(center_widget=w, title=_("TEXT_WORKSPACE_TIMESTAMPED_TITLE"), parent=parent, width=600) w.dialog = d def _on_finished(result): if result == QDialog.Rejected: return on_finished(None, None) return on_finished(w.date, w.time) d.finished.connect(_on_finished) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show() return w
def show_modal(cls, jobs_ctx, config, addr, parent, on_finished): w = cls(jobs_ctx=jobs_ctx, config=config, addr=addr) d = GreyedDialog(w, _("TEXT_BOOTSTRAP_ORG_TITLE"), parent=parent, width=1000) w.dialog = d w.line_edit_login.setFocus() def _on_finished(result): if result == QDialog.Accepted: return on_finished(w.status) return on_finished(None) d.finished.connect(_on_finished) # Unlike exec_, show is asynchronous and works within the main Qt loop d.show() return w
class FilesWidget(QWidget, Ui_FilesWidget): fs_updated_qt = pyqtSignal(ClientEvent, UUID) fs_synced_qt = pyqtSignal(ClientEvent, UUID) entry_downsynced_qt = pyqtSignal(UUID, UUID) global_clipboard_updated_qt = pyqtSignal(object) sharing_updated_qt = pyqtSignal(WorkspaceEntry, object) back_clicked = pyqtSignal() rename_success = pyqtSignal(QtToTrioJob) rename_error = pyqtSignal(QtToTrioJob) delete_success = pyqtSignal(QtToTrioJob) delete_error = pyqtSignal(QtToTrioJob) folder_stat_success = pyqtSignal(QtToTrioJob) folder_stat_error = pyqtSignal(QtToTrioJob) folder_create_success = pyqtSignal(QtToTrioJob) folder_create_error = pyqtSignal(QtToTrioJob) import_success = pyqtSignal(QtToTrioJob) import_error = pyqtSignal(QtToTrioJob) import_progress = pyqtSignal(str, int) reload_timestamped_requested = pyqtSignal(DateTime, FsPath, FileType, bool, bool, bool) reload_timestamped_success = pyqtSignal(QtToTrioJob) reload_timestamped_error = pyqtSignal(QtToTrioJob) update_version_list = pyqtSignal(WorkspaceFS, FsPath) close_version_list = pyqtSignal() folder_changed = pyqtSignal(str, str) def __init__(self, client, jobs_ctx, event_bus, *args, **kwargs): super().__init__(*args, **kwargs) self.setupUi(self) self.spinner = CenteredSpinnerWidget(parent=self.table_files) self.table_files_layout.addWidget(self.spinner, 0, 0) self.spinner.hide() self.client = client self.jobs_ctx = jobs_ctx self.event_bus = event_bus self.workspace_fs = None self.import_job = None self.clipboard = None self.button_back.clicked.connect(self.back_clicked) self.button_back.apply_style() self.button_import_folder.clicked.connect(self.import_folder_clicked) self.button_import_folder.apply_style() self.button_import_files.clicked.connect(self.import_files_clicked) self.button_import_files.apply_style() self.button_create_folder.clicked.connect(self.create_folder_clicked) self.button_create_folder.apply_style() self.line_edit_search.textChanged.connect(self.filter_files) self.line_edit_search.hide() self.current_directory = FsPath("/") self.current_directory_uuid = None self.fs_updated_qt.connect(self._on_fs_updated_qt) self.fs_synced_qt.connect(self._on_fs_synced_qt) self.entry_downsynced_qt.connect(self._on_entry_downsynced_qt) self.update_timer = QTimer() self.update_timer.setInterval(1000) self.update_timer.setSingleShot(True) self.update_timer.timeout.connect(self.reload) self.default_import_path = str(pathlib.Path.home()) self.table_files.config = self.client.config self.table_files.file_moved.connect(self.on_file_moved) self.table_files.item_activated.connect(self.item_activated) self.table_files.rename_clicked.connect(self.rename_files) self.table_files.delete_clicked.connect(self.delete_files) self.table_files.open_clicked.connect(self.open_files) self.table_files.files_dropped.connect(self.on_files_dropped) self.table_files.show_history_clicked.connect(self.show_history) self.table_files.paste_clicked.connect(self.on_paste_clicked) self.table_files.copy_clicked.connect(self.on_copy_clicked) self.table_files.cut_clicked.connect(self.on_cut_clicked) self.table_files.file_path_clicked.connect( self.on_get_file_path_clicked) self.table_files.open_current_dir_clicked.connect( self.on_open_current_dir_clicked) self.sharing_updated_qt.connect(self._on_sharing_updated_qt) self.rename_success.connect(self._on_rename_success) self.rename_error.connect(self._on_rename_error) self.delete_success.connect(self._on_delete_success) self.delete_error.connect(self._on_delete_error) self.folder_stat_success.connect(self._on_folder_stat_success) self.folder_stat_error.connect(self._on_folder_stat_error) self.folder_create_success.connect(self._on_folder_create_success) self.folder_create_error.connect(self._on_folder_create_error) self.import_success.connect(self._on_import_success) self.import_error.connect(self._on_import_error) self.reload_timestamped_requested.connect( self._on_reload_timestamped_requested) self.reload_timestamped_success.connect( self._on_reload_timestamped_success) self.reload_timestamped_error.connect( self._on_reload_timestamped_error) self.loading_dialog = None self.import_progress.connect(self._on_import_progress) self.event_bus.connect(ClientEvent.FS_ENTRY_UPDATED, self._on_fs_entry_updated_trio) self.event_bus.connect(ClientEvent.FS_ENTRY_SYNCED, self._on_fs_entry_synced_trio) self.event_bus.connect(ClientEvent.SHARING_UPDATED, self._on_sharing_updated_trio) self.event_bus.connect(ClientEvent.FS_ENTRY_DOWNSYNCED, self._on_entry_downsynced_trio) def disconnect_all(self): pass def set_workspace_fs(self, wk_fs, current_directory=FsPath("/"), default_selection=None, clipboard=None): self.current_directory = current_directory self.workspace_fs = wk_fs ws_entry = self.jobs_ctx.run_sync( self.workspace_fs.get_workspace_entry) self.current_user_role = ws_entry.role self.label_role.setText(get_role_translation(self.current_user_role)) self.table_files.current_user_role = self.current_user_role if self.current_user_role == WorkspaceRole.READER: self.button_import_folder.hide() self.button_import_files.hide() self.button_create_folder.hide() else: self.button_import_folder.show() self.button_import_files.show() self.button_create_folder.show() self.clipboard = clipboard if not self.clipboard: self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Disabled) else: if self.clipboard.source_workspace == self.workspace_fs: self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Enabled) else: # Sending the source_workspace name for paste text self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Enabled, source_workspace=str( self.jobs_ctx.run_sync(self.clipboard.source_workspace. get_workspace_name)), ) self.reset(default_selection) def reset(self, default_selection=None): workspace_name = self.jobs_ctx.run_sync( self.workspace_fs.get_workspace_name) self.load(self.current_directory, default_selection) self.table_files.sortItems(0) self.folder_changed.emit(str(workspace_name), str(self.current_directory)) def on_get_file_path_clicked(self): files = self.table_files.selected_files() if len(files) != 1: return url = BackendOrganizationFileLinkAddr.build( self.client.device.organization_addr, self.workspace_fs.workspace_id, self.current_directory / files[0].name, ) desktop.copy_to_clipboard(str(url)) show_info(self, _("TEXT_FILE_LINK_COPIED_TO_CLIPBOARD")) def on_copy_clicked(self): files = self.table_files.selected_files() files_to_copy = [] for f in files: if f.type != FileType.Folder and f.type != FileType.File: continue files_to_copy.append((self.current_directory / f.name, f.type)) self.clipboard = Clipboard(files=files_to_copy, status=Clipboard.Status.Copied, source_workspace=self.workspace_fs) self.global_clipboard_updated_qt.emit(self.clipboard) self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Enabled) def on_cut_clicked(self): files = self.table_files.selected_files() files_to_cut = [] rows = [] for f in files: if f.type != FileType.Folder and f.type != FileType.File: continue rows.append(f.row) files_to_cut.append((self.current_directory / f.name, f.type)) self.table_files.set_rows_cut(rows) self.clipboard = Clipboard(files=files_to_cut, status=Clipboard.Status.Cut, source_workspace=self.workspace_fs) self.global_clipboard_updated_qt.emit(self.clipboard) self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Enabled) def on_paste_clicked(self): if not self.clipboard: return error_count = 0 last_exc = None for f in self.clipboard.files: src = f[0] src_type = f[1] file_name = src.name base_name = pathlib.Path(src.name) # In order to be able to rename the file if a file of the same name already exists # we need the name without extensions. # .stem only removes the first extension, so we loop over it. while str(base_name) != base_name.stem: base_name = pathlib.Path(base_name.stem) count = 2 base_name = str(base_name) while True: try: dst = self.current_directory / file_name if self.clipboard.status == Clipboard.Status.Cut: self.jobs_ctx.run(self.workspace_fs.move, src, dst, self.clipboard.source_workspace) else: if src_type == FileType.Folder: self.jobs_ctx.run( self.workspace_fs.copytree, src, dst, self.clipboard.source_workspace, ) else: self.jobs_ctx.run( self.workspace_fs.copyfile, src, dst, self.clipboard.source_workspace, ) break except FileExistsError: # File already exists, we append a counter at the end of its name file_name = "{} ({}){}".format( base_name, count, "".join(pathlib.Path(src.name).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 self.clipboard.status == Clipboard.Status.Cut: self.clipboard = None self.global_clipboard_updated_qt.emit(None) self.table_files.paste_status = PasteStatus( status=PasteStatus.Status.Disabled) if last_exc: # Multiple errors, we'll just display a generic error message if error_count > 1: show_error(self, _("TEXT_FILE_PASTE_ERROR")) else: # Folder moved into itself if type(last_exc) == FSInvalidArgumentError: show_error(self, _("TEXT_FILE_FOLDER_MOVED_INTO_ITSELF_ERROR")) else: show_error(self, _("TEXT_FILE_PASTE_ERROR")) self.reset() def show_history(self): files = self.table_files.selected_files() if len(files) == 0: return if len(files) > 1: show_error(self, _("TEXT_FILE_HISTORY_MULTIPLE_FILES_SELECTED_ERROR")) return selected_path = self.current_directory / files[0].name FileHistoryWidget.show_modal( jobs_ctx=self.jobs_ctx, workspace_fs=self.workspace_fs, path=selected_path, reload_timestamped_signal=self.reload_timestamped_requested, update_version_list=self.update_version_list, close_version_list=self.close_version_list, client=self.client, parent=self, on_finished=None, ) def rename_files(self): files = self.table_files.selected_files() if len(files) == 1: def _on_rename_one_file_finished(return_code, new_name): if return_code and new_name: self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "rename_success", QtToTrioJob), ThreadSafeQtSignal(self, "rename_error", QtToTrioJob), _do_rename, workspace_fs=self.workspace_fs, paths=[( self.current_directory / files[0].name, self.current_directory / new_name, files[0].uuid, )], ) get_text_input( self, _("TEXT_FILE_RENAME_TITLE"), _("TEXT_FILE_RENAME_INSTRUCTIONS"), placeholder=_("TEXT_FILE_RENAME_PLACEHOLDER"), default_text=files[0].name, button_text=_("ACTION_FILE_RENAME"), on_finished=_on_rename_one_file_finished, ) else: def _on_rename_multiple_files_finished(return_code, new_name): if return_code and new_name: self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "rename_success", QtToTrioJob), ThreadSafeQtSignal(self, "rename_error", QtToTrioJob), _do_rename, workspace_fs=self.workspace_fs, paths=[( self.current_directory / f.name, self.current_directory / "{}_{}{}".format( new_name, i, ".".join( pathlib.Path(f.name).suffixes)), f.uuid, ) for i, f in enumerate(files, 1)], ) get_text_input( self, _("TEXT_FILE_RENAME_MULTIPLE_TITLE_count").format( count=len(files)), _("TEXT_FILE_RENAME_MULTIPLE_INSTRUCTIONS_count").format( count=len(files)), placeholder=_("TEXT_FILE_RENAME_MULTIPLE_PLACEHOLDER"), button_text=_("ACTION_FILE_RENAME_MULTIPLE"), on_finished=_on_rename_multiple_files_finished, ) def delete_files(self): files = self.table_files.selected_files() def _on_delete_file_question_finished(return_code, answer): if return_code and (answer == _("ACTION_FILE_DELETE") or answer == _("ACTION_FILE_DELETE_MULTIPLE")): self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "delete_success", QtToTrioJob), ThreadSafeQtSignal(self, "delete_error", QtToTrioJob), _do_delete, workspace_fs=self.workspace_fs, files=[(self.current_directory / f.name, f.type) for f in files], ) if len(files) == 1: ask_question( self, _("TEXT_FILE_DELETE_TITLE"), _("TEXT_FILE_DELETE_INSTRUCTIONS_name").format( name=files[0].name), [_("ACTION_FILE_DELETE"), _("ACTION_CANCEL")], on_finished=_on_delete_file_question_finished, ) else: ask_question( self, _("TEXT_FILE_DELETE_MULTIPLE_TITLE_count").format( count=len(files)), _("TEXT_FILE_DELETE_MULTIPLE_INSTRUCTIONS_count").format( count=len(files)), [_("ACTION_FILE_DELETE_MULTIPLE"), _("ACTION_CANCEL")], on_finished=_on_delete_file_question_finished, ) def on_open_current_dir_clicked(self): self.open_file(None) def open_files(self): files = self.table_files.selected_files() if len(files) == 1: if not self.open_file(files[0][2]): show_error( self, _("TEXT_FILE_OPEN_ERROR_file").format(file=files[0][2])) else: def _on_open_file_question_finished(return_code, answer): if return_code and answer == _("ACTION_FILE_OPEN_MULTIPLE"): success = True for f in files: success &= self.open_file(f[2]) if not success: show_error(self, _("TEXT_FILE_OPEN_MULTIPLE_ERROR")) ask_question( self, _("TEXT_FILE_OPEN_MULTIPLE_TITLE_count").format( count=len(files)), _("TEXT_FILE_OPEN_MULTIPLE_INSTRUCTIONS_count").format( count=len(files)), [_("ACTION_FILE_OPEN_MULTIPLE"), _("ACTION_CANCEL")], on_finished=_on_open_file_question_finished, ) def open_file(self, file_name): # The Qt thread should never hit the client directly. # Synchronous calls can run directly in the job system # as they won't block the Qt loop for long path = self.jobs_ctx.run_sync( self.client.mountpoint_manager.get_path_in_mountpoint, self.workspace_fs.workspace_id, self.current_directory / file_name if file_name else self.current_directory, self.workspace_fs.timestamp if isinstance(self.workspace_fs, WorkspaceFSTimestamped) else None, ) return desktop.open_file(str(path)) def item_activated(self, file_type, file_name): if file_type == FileType.ParentFolder: self.load(self.current_directory.parent) elif file_type == FileType.ParentWorkspace: self.back_clicked.emit() elif file_type == FileType.File: if not self.open_file(file_name): show_error( self, _("TEXT_FILE_OPEN_ERROR_file").format(file=file_name)) elif file_type == FileType.Folder: self.load(self.current_directory / file_name) def reload(self): self.load(self.current_directory) def load(self, directory, default_selection=None): self.table_files.clear() self.table_files.setStyleSheet("background-color: #EFEFEF;") self.spinner.spinner_movie.start() self.spinner.show() self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "folder_stat_success", QtToTrioJob), ThreadSafeQtSignal(self, "folder_stat_error", QtToTrioJob), _do_folder_stat, workspace_fs=self.workspace_fs, path=directory, default_selection=default_selection, ) def import_all(self, files, total_size): assert not self.import_job wl = LoadingWidget(total_size=total_size + len(files)) self.loading_dialog = GreyedDialog(wl, _("TEXT_FILE_IMPORT_LOADING_TITLE"), parent=self) wl.cancelled.connect(self.cancel_import) self.loading_dialog.show() self.import_job = self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "import_success", QtToTrioJob), ThreadSafeQtSignal(self, "import_error", QtToTrioJob), _do_import, workspace_fs=self.workspace_fs, files=files, total_size=total_size, progress_signal=ThreadSafeQtSignal(self, "import_progress", str, int), ) def cancel_import(self): assert self.import_job assert self.loading_dialog self.import_job.cancel_and_join() def _on_import_progress(self, file_name, progress): if not self.loading_dialog: return self.loading_dialog.center_widget.set_progress(progress) self.loading_dialog.center_widget.set_current_file(file_name) def get_files(self, paths, dst_dir=None): files = [] total_size = 0 for path in paths: p = pathlib.Path(path) if dst_dir is not None: dst = dst_dir / p.name else: dst = self.current_directory / p.name files.append((p, dst)) total_size += p.stat().st_size return files, total_size def get_folder(self, src, dst_dir=None): files = [] total_size = 0 if dst_dir is None: dst = self.current_directory / src.name else: dst = dst_dir / src.name for f in src.iterdir(): if f.is_dir(): new_files, new_size = self.get_folder(f, dst_dir=dst) files.extend(new_files) total_size += new_size elif f.is_file(): new_dst = dst / f.name files.append((f, new_dst)) total_size += f.stat().st_size return files, total_size def import_files_clicked(self): paths, x = QFileDialog.getOpenFileNames(self, _("TEXT_FILE_IMPORT_FILES"), self.default_import_path) if not paths: return files, total_size = self.get_files(paths) f = files[0][0] self.default_import_path = str(f.parent) self.import_all(files, total_size) def import_folder_clicked(self): path = QFileDialog.getExistingDirectory(self, _("TEXT_FILE_IMPORT_FOLDER"), self.default_import_path) if not path: return p = pathlib.Path(path) files, total_size = self.get_folder(p) self.default_import_path = str(p) self.import_all(files, total_size) def on_files_dropped(self, srcs, dst): files = [] total_size = 0 if dst == "..": dst_dir = self.current_directory.parent elif dst == ".": dst_dir = self.current_directory else: dst_dir = self.current_directory / dst for src in srcs: if src.is_dir(): tmp_files, tmp_total_size = self.get_folder(src, dst_dir=dst_dir) files.extend(tmp_files) total_size += tmp_total_size elif src.is_file(): tmp_files, tmp_total_size = self.get_files([src], dst_dir=dst_dir) files.extend(tmp_files) total_size += tmp_total_size self.import_all(files, total_size) def on_file_moved(self, src, dst): src_path = self.current_directory / src dst_path = "" if dst == "..": dst_path = self.current_directory.parent / src else: dst_path = self.current_directory / dst / src self.jobs_ctx.run(self.workspace_fs.move, src_path, dst_path) def filter_files(self, pattern): pattern = pattern.lower() for i in range(self.table_files.rowCount()): file_type = self.table_files.item(i, 0).data(TYPE_DATA_INDEX) name_item = self.table_files.item(i, 1) if file_type != FileType.ParentFolder and file_type != FileType.ParentWorkspace: if pattern not in name_item.text().lower(): self.table_files.setRowHidden(i, True) else: self.table_files.setRowHidden(i, False) def create_folder_clicked(self): def _on_folder_name_finished(return_code, folder_name): if return_code and folder_name: self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "folder_create_success", QtToTrioJob), ThreadSafeQtSignal(self, "folder_create_error", QtToTrioJob), _do_folder_create, workspace_fs=self.workspace_fs, path=self.current_directory / folder_name, ) get_text_input( self, _("TEXT_FILE_CREATE_FOLDER_TITLE"), _("TEXT_FILE_CREATE_FOLDER_INSTRUCTIONS"), placeholder=_("TEXT_FILE_CREATE_FOLDER_PLACEHOLDER"), button_text=_("ACTION_FILE_CREATE_FOLDER"), on_finished=_on_folder_name_finished, ) def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: self.delete_files() def _on_rename_success(self, job): self.reload() def _on_rename_error(self, job): if job.exc.params.get("multi"): show_error(self, _("TEXT_FILE_RENAME_MULTIPLE_ERROR"), exception=job.exc) else: show_error(self, _("TEXT_FILE_RENAME_ERROR"), exception=job.exc) def _on_delete_success(self, job): self.reload() def _on_delete_error(self, job): if not getattr(job.exc, "params", None): return if job.exc.params.get("multi"): show_error(self, _("TEXT_FILE_DELETE_MULTIPLE_ERROR"), exception=job.exc) else: show_error(self, _("TEXT_FILE_DELETE_ERROR"), exception=job.exc) def _on_folder_stat_success(self, job): ( self.current_directory, self.current_directory_uuid, files_stats, default_selection, ) = job.ret self.table_files.clear() self.table_files.setStyleSheet("background-color: #FFFFFF;") self.spinner.spinner_movie.stop() self.spinner.hide() old_sort = self.table_files.horizontalHeader().sortIndicatorSection() old_order = self.table_files.horizontalHeader().sortIndicatorOrder() self.table_files.setSortingEnabled(False) if self.current_directory == FsPath("/"): self.table_files.add_parent_workspace() else: self.table_files.add_parent_folder() file_found = False for path, stats in files_stats.items(): selected = False if default_selection and str(path) == default_selection: selected = True file_found = True if stats["type"] == "inconsistency": self.table_files.add_inconsistency(str(path), stats["id"]) elif stats["type"] == "folder": self.table_files.add_folder(str(path), stats["id"], not stats["need_sync"], stats["confined"], selected) else: self.table_files.add_file( str(path), stats["id"], stats["size"], stats["created"], stats["updated"], not stats["need_sync"], stats["confined"], selected, ) self.table_files.sortItems(old_sort, old_order) self.table_files.setSortingEnabled(True) if self.line_edit_search.text(): self.filter_files(self.line_edit_search.text()) if default_selection and not file_found: show_error(self, _("TEXT_FILE_GOTO_LINK_NOT_FOUND")) workspace_name = self.jobs_ctx.run_sync( self.workspace_fs.get_workspace_name) self.folder_changed.emit(str(workspace_name), str(self.current_directory)) def _on_folder_stat_error(self, job): self.table_files.clear() self.table_files.setStyleSheet("background-color: #FFFFFF;") self.spinner.spinner_movie.stop() self.spinner.hide() if isinstance(job.exc, FSFileNotFoundError): show_error(self, _("TEXT_FILE_FOLDER_NOT_FOUND")) self.table_files.add_parent_workspace() return if self.current_directory == FsPath("/"): self.table_files.add_parent_workspace() else: self.table_files.add_parent_folder() def _on_folder_create_success(self, job): pass def _on_folder_create_error(self, job): if job.status == "already-exists": show_error(self, _("TEXT_FILE_FOLDER_CREATE_ERROR_ALREADY_EXISTS")) else: show_error(self, _("TEXT_FILE_FOLDER_CREATE_ERROR_UNKNOWN")) def _on_import_success(self): assert self.loading_dialog self.loading_dialog.hide() self.loading_dialog.setParent(None) self.loading_dialog = None self.import_job = None def _on_import_error(self): assert self.loading_dialog if hasattr(self.import_job.exc, "status") and self.import_job.exc.status == "cancelled": self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "delete_success", QtToTrioJob), ThreadSafeQtSignal(self, "delete_error", QtToTrioJob), _do_delete, workspace_fs=self.workspace_fs, files=[(self.import_job.exc.params["last_file"], FileType.File) ], silent=True, ) else: show_error(self, _("TEXT_FILE_IMPORT_ERROR"), exception=self.import_job.exc) self.loading_dialog.hide() self.loading_dialog.setParent(None) self.loading_dialog = None self.import_job = None def _on_fs_entry_synced_trio(self, event, id, workspace_id=None): self.fs_synced_qt.emit(event, id) def _on_fs_entry_updated_trio(self, event, workspace_id=None, id=None): assert id is not None if workspace_id is None or (self.workspace_fs is not None and workspace_id == self.workspace_fs.workspace_id): self.fs_updated_qt.emit(event, id) def _on_entry_downsynced_trio(self, event, workspace_id=None, id=None): self.entry_downsynced_qt.emit(workspace_id, id) def _on_entry_downsynced_qt(self, workspace_id, id): if not self.workspace_fs: return ws_id = self.workspace_fs.workspace_id if ws_id != workspace_id: return if id == self.current_directory_uuid: if not self.update_timer.isActive(): self.update_timer.start() self.reload() def _on_fs_synced_qt(self, event, uuid): if not self.workspace_fs: return if self.current_directory_uuid == uuid: return for i in range(1, self.table_files.rowCount()): item = self.table_files.item(i, 0) if item and item.data(UUID_DATA_INDEX) == uuid: if (item.data(TYPE_DATA_INDEX) == FileType.File or item.data(TYPE_DATA_INDEX) == FileType.Folder): item.confined = False item.is_synced = True def _on_fs_updated_qt(self, event, uuid): if not self.workspace_fs: return if self.current_directory_uuid == uuid or self.table_files.has_file( uuid): if not self.update_timer.isActive(): self.update_timer.start() self.reload() def _on_sharing_updated_trio(self, event, new_entry, previous_entry): self.sharing_updated_qt.emit(new_entry, previous_entry) def _on_sharing_updated_qt(self, new_entry, previous_entry): if new_entry is None or new_entry.role is None: # Sharing revoked show_error(self, _("TEXT_FILE_SHARING_REVOKED")) self.back_clicked.emit() elif previous_entry is not None and previous_entry.role is not None: self.current_user_role = new_entry.role self.label_role.setText( get_role_translation(self.current_user_role)) if (previous_entry.role != WorkspaceRole.READER and new_entry.role == WorkspaceRole.READER): show_error(self, _("TEXT_FILE_SHARING_DEMOTED_TO_READER")) def _on_reload_timestamped_requested(self, timestamp, path, file_type, open_after_load, close_after_remount, reload_after_remount): self.jobs_ctx.submit_job( ThreadSafeQtSignal(self, "reload_timestamped_success", QtToTrioJob), ThreadSafeQtSignal(self, "reload_timestamped_error", QtToTrioJob), _do_remount_timestamped, mountpoint_manager=self.client.mountpoint_manager, workspace_fs=self.workspace_fs, timestamp=timestamp, path=path if path is not None else self.current_directory, file_type=file_type, open_after_load=open_after_load, close_after_load=close_after_remount, reload_after_remount=reload_after_remount, ) def _on_reload_timestamped_success(self, job): ( workspace_fs, path, file_type, open_after_load, close_after_load, reload_after_remount, ) = job.ret self.set_workspace_fs( workspace_fs, path.parent if file_type == FileType.File else path) # TODO : Select element if possible? if close_after_load: self.close_version_list.emit() if reload_after_remount: self.update_version_list.emit(self.workspace_fs, path) if open_after_load: self.open_file(path.name) def _on_reload_timestamped_error(self, job): raise job.exc