Exemplo n.º 1
0
 def show_modal(
     cls,
     jobs_ctx,
     workspace_fs,
     path,
     reload_timestamped_signal,
     update_version_list,
     close_version_list,
     core,
     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,
         core=core,
     )
     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
Exemplo n.º 2
0
 def exec_modal(cls, jobs_ctx, parent):
     w = cls(jobs_ctx)
     d = GreyedDialog(w, _("TEXT_ORG_WIZARD_TITLE"), parent=parent)
     w.dialog = d
     if d.exec_() == QDialog.Accepted:
         return w.status
     return None
Exemplo n.º 3
0
 def _show_settings(self):
     w = SettingsWidget(self.config, self.jobs_ctx, self.event_bus)
     d = GreyedDialog(w,
                      title=_("TEXT_SETTINGS_TITLE"),
                      parent=self,
                      width=1000)
     d.exec_()
Exemplo n.º 4
0
 def _show_license(self):
     w = LicenseWidget()
     d = GreyedDialog(w,
                      title=_("TEXT_LICENSE_TITLE"),
                      parent=self,
                      width=1000)
     d.exec_()
Exemplo n.º 5
0
 def _show_changelog(self):
     w = ChangelogWidget()
     d = GreyedDialog(w,
                      title=_("TEXT_CHANGELOG_TITLE"),
                      parent=self,
                      width=1000)
     d.exec_()
Exemplo n.º 6
0
 def _on_manage_keys(self):
     w = KeysWidget(config=self.config, parent=self)
     w.key_imported.connect(self.reload_login_devices)
     d = GreyedDialog(w,
                      title=_("TEXT_KEYS_DIALOG"),
                      parent=self,
                      width=800)
     d.exec()
 def exec_modal(cls, jobs_ctx, config, addr, parent):
     w = cls(jobs_ctx=jobs_ctx, config=config, addr=addr)
     d = GreyedDialog(w, _("TEXT_BOOTSTRAP_ORG_TITLE"), parent=parent)
     w.dialog = d
     w.line_edit_login.setFocus()
     if d.exec_() == QDialog.Accepted and w.status:
         return w.status
     return None
 def exec_modal(cls, user_fs, workspace_fs, core, jobs_ctx, parent):
     w = cls(user_fs=user_fs,
             workspace_fs=workspace_fs,
             core=core,
             jobs_ctx=jobs_ctx)
     d = GreyedDialog(w,
                      title=_("TEXT_WORKSPACE_SHARING_TITLE"),
                      parent=parent)
     return d.exec_()
Exemplo n.º 9
0
    def show_modal(cls, core, jobs_ctx, token, parent, on_finished):
        w = cls(core=core, jobs_ctx=jobs_ctx, token=token)
        d = GreyedDialog(w, _("TEXT_GREET_USER_TITLE"), parent=parent, width=1000)
        w.dialog = d

        d.finished.connect(on_finished)
        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
Exemplo n.º 10
0
def _show_large_qrcode(parent, image):
    w = LargeQRCodeWidget(image)
    d = GreyedDialog(w,
                     title=None,
                     parent=parent,
                     hide_close=True,
                     close_on_click=True)
    w.dialog = d
    return d.open()
Exemplo n.º 11
0
    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_USER_TITLE"), parent=parent, width=800)
        w.dialog = d

        d.finished.connect(on_finished)
        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
 def exec_modal(cls, workspace_fs, jobs_ctx, parent):
     w = cls(workspace_fs=workspace_fs, jobs_ctx=jobs_ctx)
     d = GreyedDialog(
         center_widget=w, title=_("TEXT_WORKSPACE_TIMESTAMPED_TITLE"), parent=parent
     )
     w.dialog = d
     r = d.exec_()
     if r == QDialog.Rejected:
         return None, None
     return w.date, w.time
    def show_modal(cls, config, jobs_ctx, devices, parent):
        w = cls(config=config, jobs_ctx=jobs_ctx, devices=devices)
        d = GreyedDialog(w,
                         translate("TEXT_DEVICE_RECOVERY_EXPORT_WIZARD_TITLE"),
                         parent=parent,
                         width=800)
        w.dialog = d

        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
Exemplo n.º 14
0
    def show_modal(cls, core, parent, on_finished):
        w = cls(core=core)
        d = GreyedDialog(w,
                         title=_("TEXT_CHANGE_PASSWORD_TITLE"),
                         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
Exemplo n.º 15
0
 def run_dialog():
     w = InviteUserWidget(jobs_ctx=u_w.jobs_ctx, core=u_w.core)
     d = GreyedDialog(w, title="Title", parent=u_w)
     d.show()
     assert not w.line_edit_token.text()
     assert not w.line_edit_url.text()
     aqtbot.qtbot.keyClicks(w.line_edit_username, "new_user")
     aqtbot.qtbot.mouseClick(w.button_register, QtCore.Qt.LeftButton)
     assert w.line_edit_url.text()
     assert w.line_edit_username.text() == "new_user"
     assert w.line_edit_token.text()
     with aqtbot.qtbot.waitSignal(w.registration_error):
         aqtbot.qtbot.mouseClick(d.button_close, QtCore.Qt.LeftButton)
     assert not w.widget_registration.isVisible()
     assert not d.isVisible()
Exemplo n.º 16
0
    def show_modal(cls, user_fs, workspace_fs, core, jobs_ctx, parent, on_finished):
        workspace_name = workspace_fs.get_workspace_name()

        w = cls(user_fs=user_fs, workspace_fs=workspace_fs, core=core, jobs_ctx=jobs_ctx)
        d = GreyedDialog(
            w,
            title=_("TEXT_WORKSPACE_SHARING_TITLE_workspace").format(workspace=workspace_name),
            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, config, jobs_ctx, parent, on_finished):
        w = cls(config=config, jobs_ctx=jobs_ctx)
        d = GreyedDialog(w,
                         translate("TEXT_DEVICE_RECOVERY_IMPORT_WIZARD_TITLE"),
                         parent=parent,
                         width=800)
        w.dialog = d

        def _on_finished(result):
            return on_finished()

        d.finished.connect(_on_finished)
        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
Exemplo n.º 18
0
    def show_modal(cls, jobs_ctx, config, addr, parent, on_finished):
        w = cls(jobs_ctx=jobs_ctx, config=config, addr=addr)
        d = GreyedDialog(w,
                         translate("TEXT_ENROLLMENT_QUERY_TITLE"),
                         parent=parent,
                         width=800)
        w.dialog = d

        def _on_finished(result):
            return 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, 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=1000
        )
        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
Exemplo n.º 20
0
    def show_modal(cls,
                   jobs_ctx,
                   config,
                   parent,
                   on_finished,
                   start_addr=None):
        w = cls(jobs_ctx, config, start_addr)
        d = GreyedDialog(w,
                         _("TEXT_ORG_WIZARD_TITLE"),
                         parent=parent,
                         width=1000)
        w.dialog = d

        d.finished.connect(on_finished)
        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
Exemplo n.º 21
0
    def show_modal(cls, enrollment_info, parent, on_finished,
                   user_profile_outsider_allowed):
        w = cls(enrollment_info, user_profile_outsider_allowed)
        d = GreyedDialog(w,
                         translate("TEXT_ENROLLMENT_ACCEPT_CHECK_INFO_TITLE"),
                         parent=parent,
                         width=800)
        w.dialog = d

        def _on_finished(result):
            return on_finished(result, w)

        d.finished.connect(_on_finished)

        # Unlike exec_, show is asynchronous and works within the main Qt loop
        d.show()
        return w
Exemplo n.º 22
0
    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
Exemplo n.º 23
0
 def show_modal(cls, jobs_ctx, workspace_fs, path, core, parent,
                on_finished):
     w = cls(jobs_ctx=jobs_ctx,
             workspace_fs=workspace_fs,
             path=path,
             core=core)
     d = GreyedDialog(
         w,
         title=_("TEXT_FILE_STATUS_TITLE_name").format(name=path.name),
         parent=parent,
         width=1000,
     )
     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
Exemplo n.º 24
0
    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),
        )
Exemplo n.º 25
0
    async def show_modal(cls, core, jobs_ctx, parent, on_finished=None):
        available_device = get_available_device(core.config.config_dir,
                                                core.device)
        loaded_device = None

        try:
            if available_device.type == DeviceFileType.PASSWORD:
                password = get_text_input(
                    parent,
                    _("TEXT_DEVICE_UNLOCK_TITLE"),
                    _("TEXT_DEVICE_UNLOCK_FOR_AUTH_CHANGE_LABEL"),
                    placeholder="",
                    default_text="",
                    completion=None,
                    button_text=None,
                    validator=None,
                    hidden=True,
                )
                if not password:
                    return
                loaded_device = load_device_with_password(
                    available_device.key_file_path, password)
            else:
                loaded_device = await load_device_with_smartcard(
                    available_device.key_file_path)
        except LocalDeviceError:
            show_error(parent, _("TEXT_LOGIN_ERROR_AUTHENTICATION_FAILED"))
            return

        w = cls(core=core, jobs_ctx=jobs_ctx, loaded_device=loaded_device)
        d = GreyedDialog(w,
                         title=_("TEXT_CHANGE_AUTHENTICATION_TITLE"),
                         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
Exemplo n.º 26
0
 def exec_modal(
     cls,
     jobs_ctx,
     workspace_fs,
     path,
     reload_timestamped_signal,
     update_version_list,
     close_version_list,
     parent,
 ):
     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,
     )
     d = GreyedDialog(
         w,
         title=_("TEXT_FILE_HISTORY_TITLE_name").format(name=path.name),
         parent=parent)
     return d.exec_()
Exemplo n.º 27
0
 def exec_modal(cls, core, parent):
     w = cls(core=core)
     d = GreyedDialog(w, title=_("TEXT_CHANGE_PASSWORD_TITLE"), parent=parent)
     w.dialog = d
     return d.exec_()
Exemplo n.º 28
0
class FilesWidget(QWidget, Ui_FilesWidget):
    fs_updated_qt = pyqtSignal(CoreEvent, UUID)
    fs_synced_qt = pyqtSignal(CoreEvent, UUID)
    entry_downsynced_qt = pyqtSignal(UUID, UUID)

    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, core, jobs_ctx, event_bus, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)

        # Create spinner and stack it on the table_files widget
        self.spinner = CenteredSpinnerWidget(parent=self.table_files)
        self.table_files_layout.addWidget(self.spinner, 0, 0)
        self.spinner.hide()

        self.core = core
        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.core.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(CoreEvent.FS_ENTRY_UPDATED,
                               self._on_fs_entry_updated_trio)
        self.event_bus.connect(CoreEvent.FS_ENTRY_SYNCED,
                               self._on_fs_entry_synced_trio)
        self.event_bus.connect(CoreEvent.SHARING_UPDATED,
                               self._on_sharing_updated_trio)
        self.event_bus.connect(CoreEvent.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):
        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 = None
        self.table_files.paste_disabled = True
        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.core.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_to_copy, Clipboard.Status.Copied)
        self.table_files.paste_disabled = False

    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.table_files.paste_disabled = False
        self.clipboard = Clipboard(files_to_cut, Clipboard.Status.Cut)

    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)
                    else:
                        if src_type == FileType.Folder:
                            self.jobs_ctx.run(self.workspace_fs.copytree, src,
                                              dst)
                        else:
                            self.jobs_ctx.run(self.workspace_fs.copyfile, src,
                                              dst)
                    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.table_files.paste_disabled = True

        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) > 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,
            core=self.core,
            parent=self,
            on_finished=None,
        )

    def rename_files(self):
        files = self.table_files.selected_files()
        if len(files) == 1:
            new_name = 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"),
            )
            if not new_name:
                return
            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,
                )],
            )
        else:
            new_name = 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"),
            )
            if not new_name:
                return

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

    def delete_files(self):
        files = self.table_files.selected_files()
        if len(files) == 1:
            result = ask_question(
                self,
                _("TEXT_FILE_DELETE_TITLE"),
                _("TEXT_FILE_DELETE_INSTRUCTIONS_name").format(
                    name=files[0].name),
                [_("ACTION_FILE_DELETE"),
                 _("ACTION_CANCEL")],
            )
        else:
            result = 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")],
            )
        if result != _("ACTION_FILE_DELETE_MULTIPLE") and result != _(
                "ACTION_FILE_DELETE"):
            return
        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],
        )

    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:
            result = 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")],
            )
            if result != _("ACTION_FILE_OPEN_MULTIPLE"):
                return
            success = True
            for f in files:
                success &= self.open_file(f[2])
            if not success:
                show_error(self, _("TEXT_FILE_OPEN_MULTIPLE_ERROR"))

    def open_file(self, file_name):
        # The Qt thread should never hit the core 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.core.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.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):
        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"),
        )
        if not folder_name:
            return

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

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete:
            self.delete_files()

    def _on_rename_success(self, job):
        self.reset()

    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.reset()

    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.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
            confined = bool(stats["confinement_point"])
            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"], confined,
                                            selected)
            else:
                self.table_files.add_file(
                    str(path),
                    stats["id"],
                    stats["size"],
                    stats["created"],
                    stats["updated"],
                    not stats["need_sync"],
                    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.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):
        def _display_import_error(file_count, exceptions=None):
            if exceptions and all(
                    isinstance(exc, PermissionError) for exc in exceptions):
                if file_count and file_count == 1:
                    show_error(self,
                               _("TEXT_FILE_IMPORT_ONE_PERMISSION_ERROR"),
                               exception=exceptions[0])
                else:
                    show_error(
                        self,
                        _("TEXT_FILE_IMPORT_MULTIPLE_PERMISSION_ERROR"),
                        exception=exceptions[0],
                    )
            else:
                if file_count and file_count == 1:
                    show_error(
                        self,
                        _("TEXT_FILE_IMPORT_ONE_ERROR"),
                        exception=exceptions[0] if exceptions else None,
                    )
                else:
                    show_error(
                        self,
                        _("TEXT_FILE_IMPORT_MULTIPLE_ERROR"),
                        exception=exceptions[0] if exceptions else None,
                    )

        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:
            _display_import_error(
                file_count=self.import_job.exc.params.get("file_count", 0),
                exceptions=self.import_job.exc.params.get("exceptions", None),
            )
        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_workspace").format(
                    workspace=previous_entry.name))
            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.core.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
Exemplo n.º 29
0
 def exec_modal(cls, core, jobs_ctx, parent):
     w = cls(core=core, jobs_ctx=jobs_ctx)
     d = GreyedDialog(w, title=_("TEXT_INVITE_USER_TITLE"), parent=parent)
     w.dialog = d
     return d.exec_()
Exemplo n.º 30
0
 def _show_about(self):
     w = AboutWidget()
     d = GreyedDialog(w, title="", parent=self, width=1000)
     d.exec_()