async def test_workspace_button_rename_clicked(qtbot, workspace_fs,
                                               core_config, alice_user_info):
    switch_language(core_config, "en")

    roles = {alice_user_info.user_id: (WorkspaceRole.OWNER, alice_user_info)}
    w = WorkspaceButton(
        workspace_name="Workspace",
        workspace_fs=workspace_fs,
        users_roles=roles,
        is_mounted=True,
        files=[],
    )
    qtbot.addWidget(w)
    with qtbot.waitSignal(w.rename_clicked, timeout=500) as blocker:
        qtbot.mouseClick(w.button_rename, QtCore.Qt.LeftButton)
    assert blocker.args == [w]
示例#2
0
    async def _gui_factory(
        event_bus=None,
        core_config=core_config,
        start_arg=None,
        skip_dialogs=True,
        throttle_job_no_wait=True,
    ):
        # Wait for the backend to run if necessary
        await running_backend_ready()

        # First start popup blocks the test
        # Check version and mountpoint are useless for most tests
        core_config = core_config.evolve(
            gui_check_version_at_startup=False,
            gui_first_launch=False,
            gui_last_version=parsec_version,
            mountpoint_enabled=True,
            gui_language="en",
            gui_show_confined=False,
        )
        event_bus = event_bus or event_bus_factory()
        ParsecApp.connected_devices = set()

        # Language config rely on global var, must reset it for each test !
        switch_language(core_config, "en")

        # Pass minimize_on_close to avoid having test blocked by the closing confirmation prompt
        main_w = testing_main_window_cls(job_scheduler,
                                         job_scheduler.close,
                                         event_bus,
                                         core_config,
                                         minimize_on_close=True)
        aqtbot.add_widget(main_w)
        main_w.show_window(skip_dialogs=skip_dialogs)
        main_w.show_top()
        windows.append(main_w)
        main_w.add_instance(start_arg)

        def right_main_window():
            assert ParsecApp.get_main_window() is main_w

        # For some reasons, the main window from the previous test might
        # still be around. Simply wait for things to settle down until
        # our freshly created window is detected as the app main window.
        await aqtbot.wait_until(right_main_window)

        return main_w
示例#3
0
def test_organization_validator(qtbot, core_config):
    switch_language(core_config, "en")

    le = ValidatedLineEdit()
    le.set_validator(validators.OrganizationIDValidator())
    qtbot.add_widget(le)
    le.show()

    qtbot.keyClicks(le, "Reynholm")
    qtbot.wait_until(lambda: le.text() == "Reynholm")
    assert le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Acceptable

    qtbot.keyClicks(le, " Industries")
    qtbot.wait_until(lambda: le.text() == "Reynholm Industries")
    assert not le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Invalid
示例#4
0
    async def _gui_factory(event_bus=None,
                           core_config=core_config,
                           start_arg=None):
        # First start popup blocks the test
        # Check version and mountpoint are useless for most tests
        core_config = core_config.evolve(
            gui_check_version_at_startup=False,
            gui_first_launch=False,
            gui_last_version=parsec_version,
            mountpoint_enabled=True,
            gui_language="en",
        )
        event_bus = event_bus or EventBus()
        # Language config rely on global var, must reset it for each test !
        switch_language(core_config)

        def _create_main_window():
            # Pass minimize_on_close to avoid having test blocked by the
            # closing confirmation prompt

            switch_language(core_config, "en")
            monkeypatch.setattr(
                "parsec.core.gui.main_window.list_available_devices",
                lambda *args, **kwargs: (["a"]),
            )
            main_w = MainWindow(qt_thread_gateway._job_scheduler,
                                event_bus,
                                core_config,
                                minimize_on_close=True)
            qtbot.add_widget(main_w)
            main_w.showMaximized()
            main_w.show_top()
            windows.append(main_w)
            main_w.add_instance(start_arg)

            def right_main_window():
                assert ParsecApp.get_main_window() is main_w

            # For some reasons, the main window from the previous test might
            # still be around. Simply wait for things to settle down until
            # our freshly created window is detected as the app main window.
            qtbot.wait_until(right_main_window)

            return main_w

        return await qt_thread_gateway.send_action(_create_main_window)
示例#5
0
        def _create_main_window():
            # Pass minimize_on_close to avoid having test blocked by the
            # closing confirmation prompt

            switch_language(core_config, "en")
            monkeypatch.setattr(
                "parsec.core.gui.main_window.list_available_devices",
                lambda *args, **kwargs: (["a"]),
            )
            main_w = MainWindow(qt_thread_gateway._job_scheduler,
                                event_bus,
                                core_config,
                                minimize_on_close=True)
            qtbot.add_widget(main_w)
            main_w.showMaximized()
            main_w.show_top()
            windows.append(main_w)
            main_w.add_instance(start_arg)
            return main_w
示例#6
0
def test_email_validator(qtbot, core_config):
    switch_language(core_config, "en")

    le = ValidatedLineEdit()
    le.set_validator(validators.EmailValidator())
    qtbot.addWidget(le)
    le.show()

    qtbot.keyClicks(le, "maurice")
    assert not le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Intermediate

    qtbot.keyClicks(le, "*****@*****.**")
    assert le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Acceptable

    qtbot.keyClicks(le, "#")
    assert not le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Invalid
示例#7
0
async def test_workspace_button_shared_by(qtbot, workspace_fs, core_config):
    switch_language(core_config, "en")

    w = WorkspaceButton(
        workspace_name="Workspace",
        workspace_fs=workspace_fs,
        is_shared=True,
        is_creator=False,
        files=[],
    )

    qtbot.addWidget(w)
    w.show()
    assert w.widget_empty.isVisible() is True
    assert w.widget_files.isVisible() is False
    assert w.label_owner.isVisible() is False
    assert w.label_shared.isVisible() is True
    assert w.name == "Workspace"
    assert w.label_title.text() == "Workspace"
示例#8
0
async def test_workspace_button(qtbot, workspace_fs, core_config):
    switch_language(core_config, "en")

    roles = {workspace_fs.device.user_id: WorkspaceRole.OWNER}
    w = WorkspaceButton(
        workspace_name="Workspace",
        workspace_fs=workspace_fs,
        users_roles=roles,
        is_mounted=True,
        files=[],
    )
    qtbot.addWidget(w)
    w.show()

    assert w.widget_empty.isVisible() is True
    assert w.widget_files.isVisible() is False
    assert w.label_owner.isVisible() is True
    assert w.label_shared.isVisible() is False
    assert w.name == "Workspace"
    assert w.label_title.text().startswith("Workspace")
    assert w.label_title.toolTip() == "Workspace (private)"
示例#9
0
def test_backend_action_addr_validator(qtbot, core_config, action_addr):
    switch_language(core_config, "en")

    le = ValidatedLineEdit()
    le.set_validator(validators.BackendActionAddrValidator())
    qtbot.add_widget(le)
    le.show()

    qtbot.keyClicks(le, "http://host:1337")
    qtbot.wait_until(lambda: le.text() == "http://host:1337")
    assert not le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Intermediate

    le.setText("")
    qtbot.wait_until(lambda: le.text() == "")
    assert not le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Intermediate

    qtbot.keyClicks(le, action_addr)
    qtbot.wait_until(lambda: le.text() == action_addr)
    assert le.is_input_valid()
    assert le.property("validity") == QtGui.QValidator.Acceptable
示例#10
0
async def test_workspace_button(qtbot, workspace_fs, core_config,
                                alice_user_info):
    switch_language(core_config, "en")

    roles = {alice_user_info.user_id: (WorkspaceRole.OWNER, alice_user_info)}
    w = WorkspaceButton.create(
        workspace_name=EntryName("Workspace"),
        workspace_fs=workspace_fs,
        users_roles=roles,
        is_mounted=True,
        files=[],
    )
    qtbot.add_widget(w)
    w.show()

    assert w.widget_empty.isVisible() is True
    assert w.widget_files.isVisible() is False
    assert w.label_owner.isVisible() is True
    assert w.label_shared.isVisible() is False
    assert w.name == EntryName("Workspace")
    assert w.label_title.text().startswith("Workspace")
    assert w.label_title.toolTip() == "Workspace (private)"
    assert w.label_role.text() == _("TEXT_WORKSPACE_ROLE_OWNER")
示例#11
0
async def test_workspace_button_files(qtbot, workspace_fs, core_config):
    switch_language(core_config, "en")

    roles = {workspace_fs.device.user_id: WorkspaceRole.OWNER}
    w = WorkspaceButton(
        workspace_name="Workspace",
        workspace_fs=workspace_fs,
        users_roles=roles,
        is_mounted=True,
        files=["File1.txt", "File2.txt", "Dir1"],
    )

    qtbot.addWidget(w)
    w.show()
    assert w.widget_empty.isVisible() is False
    assert w.widget_files.isVisible() is True
    assert w.label_owner.isVisible() is True
    assert w.label_shared.isVisible() is False
    assert w.name == "Workspace"
    assert w.file1_name.text() == "File1.txt"
    assert w.file2_name.text() == "File2.txt"
    assert w.file3_name.text() == "Dir1"
    assert w.file4_name.text() == ""
示例#12
0
async def test_workspace_button_files(qtbot, workspace_fs, core_config):
    switch_language(core_config, "en")

    w = WorkspaceButton(
        workspace_name="Workspace",
        workspace_fs=workspace_fs,
        is_shared=True,
        is_creator=True,
        files=["File1.txt", "File2.txt", "Dir1"],
    )

    qtbot.addWidget(w)
    w.show()
    assert w.widget_empty.isVisible() is False
    assert w.widget_files.isVisible() is True
    assert w.label_owner.isVisible() is True
    assert w.label_shared.isVisible() is True
    assert w.name == "Workspace"
    assert w.label_title.text() == "Workspace (shared wi..."
    assert w.label_title.toolTip()
    assert w.file1_name.text() == "File1.txt"
    assert w.file2_name.text() == "File2.txt"
    assert w.file3_name.text() == "Dir1"
    assert w.file4_name.text() == ""
示例#13
0
def run_gui(config: CoreConfig, start_arg: str = None, diagnose: bool = False):
    logger.info("Starting UI")

    # Needed for High DPI usage of QIcons, otherwise only QImages are well scaled
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)

    app = QApplication(["-stylesheet"])
    app.setOrganizationName("Scille")
    app.setOrganizationDomain("parsec.cloud")
    app.setApplicationName("Parsec")

    QFontDatabase.addApplicationFont(":/fonts/fonts/Roboto-Regular.ttf")
    f = QFont("Roboto")
    app.setFont(f)

    rc = QFile(":/styles/styles/main.css")
    rc.open(QFile.ReadOnly)
    content = rc.readAll().data()
    app.setStyleSheet(str(content, "utf-8"))

    lang_key = lang.switch_language(config)

    event_bus = EventBus()
    with run_trio_thread() as jobs_ctx:
        win = MainWindow(
            jobs_ctx=jobs_ctx,
            event_bus=event_bus,
            config=config,
            minimize_on_close=config.gui_tray_enabled and systray_available(),
        )

        result_queue = Queue(maxsize=1)

        class ThreadSafeNoQtSignal(ThreadSafeQtSignal):
            def __init__(self):
                self.qobj = None
                self.signal_name = ""
                self.args_types = ()

            def emit(self, *args):
                pass

        jobs_ctx.submit_job(
            ThreadSafeNoQtSignal(),
            ThreadSafeNoQtSignal(),
            _start_ipc_server,
            config,
            win,
            start_arg,
            result_queue,
        )
        if result_queue.get() == "already_running":
            # Another instance of Parsec already started, nothing more to do
            return

        if systray_available():
            systray = Systray(parent=win)
            win.systray_notification.connect(systray.on_systray_notification)
            systray.on_close.connect(win.close_app)
            systray.on_show.connect(win.show_top)
            app.aboutToQuit.connect(before_quit(systray))
            if config.gui_tray_enabled:
                app.setQuitOnLastWindowClosed(False)

        if config.gui_check_version_at_startup and not diagnose:
            CheckNewVersion(jobs_ctx=jobs_ctx,
                            event_bus=event_bus,
                            config=config,
                            parent=win)

        win.showMaximized(skip_dialogs=diagnose)
        win.show_top()
        win.new_instance_needed.emit(start_arg)

        def kill_window(*args):
            win.close_app(force=True)
            QApplication.quit()

        signal.signal(signal.SIGINT, kill_window)

        # QTimer wakes up the event loop periodically which allows us to close
        # the window even when it is in background.
        timer = QTimer()
        timer.start(1000 if diagnose else 400)
        timer.timeout.connect(kill_window if diagnose else lambda: None)

        if diagnose:
            diagnose_timer = QTimer()
            diagnose_timer.start(1000)
            diagnose_timer.timeout.connect(kill_window)

        if lang_key:
            event_bus.send("gui.config.changed", gui_language=lang_key)

        if diagnose:
            with fail_on_first_exception(kill_window):
                return app.exec_()
        else:
            return app.exec_()
示例#14
0
def run_gui(config: CoreConfig, start_arg: str = None, diagnose: bool = False):
    logger.info("Starting UI")

    # Needed for High DPI usage of QIcons, otherwise only QImages are well scaled
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QApplication.setHighDpiScaleFactorRoundingPolicy(
        Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)

    app = ParsecApp()

    app.load_stylesheet()
    app.load_font()

    lang_key = lang.switch_language(config)

    event_bus = EventBus()
    with run_trio_thread() as jobs_ctx:
        win = MainWindow(
            jobs_ctx=jobs_ctx,
            event_bus=event_bus,
            config=config,
            minimize_on_close=config.gui_tray_enabled and systray_available(),
        )

        result_queue = Queue(maxsize=1)

        class ThreadSafeNoQtSignal(ThreadSafeQtSignal):
            def __init__(self):
                self.qobj = None
                self.signal_name = ""
                self.args_types = ()

            def emit(self, *args):
                pass

        jobs_ctx.submit_job(
            ThreadSafeNoQtSignal(),
            ThreadSafeNoQtSignal(),
            _start_ipc_server,
            config,
            win,
            start_arg,
            result_queue,
        )
        if result_queue.get() == "already_running":
            # Another instance of Parsec already started, nothing more to do
            return

        if systray_available():
            systray = Systray(parent=win)
            win.systray_notification.connect(systray.on_systray_notification)
            systray.on_close.connect(win.close_app)
            systray.on_show.connect(win.show_top)
            app.aboutToQuit.connect(before_quit(systray))
            if config.gui_tray_enabled:
                app.setQuitOnLastWindowClosed(False)

        if config.gui_check_version_at_startup and not diagnose:
            CheckNewVersion(jobs_ctx=jobs_ctx,
                            event_bus=event_bus,
                            config=config,
                            parent=win)

        win.show_window(skip_dialogs=diagnose, invitation_link=start_arg)
        win.show_top()
        win.new_instance_needed.emit(start_arg)

        def kill_window(*args):
            win.close_app(force=True)
            QApplication.quit()

        signal.signal(signal.SIGINT, kill_window)

        # QTimer wakes up the event loop periodically which allows us to close
        # the window even when it is in background.
        timer = QTimer()
        timer.start(1000 if diagnose else 400)
        timer.timeout.connect(kill_window if diagnose else lambda: None)

        if diagnose:
            diagnose_timer = QTimer()
            diagnose_timer.start(1000)
            diagnose_timer.timeout.connect(kill_window)

        if lang_key:
            event_bus.send(CoreEvent.GUI_CONFIG_CHANGED, gui_language=lang_key)

        if diagnose:
            with fail_on_first_exception(kill_window):
                return app.exec_()
        else:
            with log_pyqt_exceptions():
                return app.exec_()
示例#15
0
def test_file_table_sort(qtbot, core_config):
    switch_language(core_config, "en")

    w = FileTable(parent=None)
    qtbot.add_widget(w)
    w.add_parent_workspace()
    w.add_folder(EntryName("Dir1"), EntryID.new(), True, False)
    w.add_file(
        EntryName("File1.txt"),
        EntryID.new(),
        100,
        pendulum.datetime(2000, 1, 15),
        pendulum.datetime(2000, 1, 20),
        True,
        False,
    )
    w.add_file(
        EntryName("AnotherFile.txt"),
        EntryID.new(),
        80,
        pendulum.datetime(2000, 1, 10),
        pendulum.datetime(2000, 1, 25),
        True,
        False,
    )
    assert w.rowCount() == 4
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "Dir1"
    assert w.item(2, 1).text() == "File1.txt"
    assert w.item(3, 1).text() == "AnotherFile.txt"

    # Name
    w.sortByColumn(1, QtCore.Qt.AscendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "AnotherFile.txt"
    assert w.item(2, 1).text() == "Dir1"
    assert w.item(3, 1).text() == "File1.txt"

    w.sortByColumn(1, QtCore.Qt.DescendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "File1.txt"
    assert w.item(2, 1).text() == "Dir1"
    assert w.item(3, 1).text() == "AnotherFile.txt"

    # Created
    w.sortByColumn(2, QtCore.Qt.AscendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "Dir1"
    assert w.item(2, 1).text() == "AnotherFile.txt"
    assert w.item(3, 1).text() == "File1.txt"

    w.sortByColumn(2, QtCore.Qt.DescendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "File1.txt"
    assert w.item(2, 1).text() == "AnotherFile.txt"
    assert w.item(3, 1).text() == "Dir1"

    # Updated
    w.sortByColumn(3, QtCore.Qt.AscendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "Dir1"
    assert w.item(2, 1).text() == "File1.txt"
    assert w.item(3, 1).text() == "AnotherFile.txt"

    w.sortByColumn(3, QtCore.Qt.DescendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "AnotherFile.txt"
    assert w.item(2, 1).text() == "File1.txt"
    assert w.item(3, 1).text() == "Dir1"

    # Size
    w.sortByColumn(4, QtCore.Qt.AscendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "Dir1"
    assert w.item(2, 1).text() == "AnotherFile.txt"
    assert w.item(3, 1).text() == "File1.txt"

    w.sortByColumn(4, QtCore.Qt.DescendingOrder)
    assert w.item(0, 1).text() == "Workspaces list"
    assert w.item(1, 1).text() == "File1.txt"
    assert w.item(2, 1).text() == "AnotherFile.txt"
    assert w.item(3, 1).text() == "Dir1"
示例#16
0
async def _run_gui(app: ParsecApp,
                   config: CoreConfig,
                   start_arg: str = None,
                   diagnose: bool = False):
    app.load_stylesheet()
    app.load_font()

    lang_key = lang.switch_language(config)

    event_bus = EventBus()
    async with run_trio_job_scheduler() as jobs_ctx:
        win = MainWindow(
            jobs_ctx=jobs_ctx,
            quit_callback=jobs_ctx.close,
            event_bus=event_bus,
            config=config,
            minimize_on_close=config.gui_tray_enabled and systray_available(),
        )

        # Attempt to run an IPC server if Parsec is not already started
        try:
            await jobs_ctx.nursery.start(_run_ipc_server, config, win,
                                         start_arg)
        # Another instance of Parsec already started, nothing more to do
        except IPCServerAlreadyRunning:
            return

        # If we are here, it's either the IPC server has successfully started
        # or it has crashed without being able to communicate with an existing
        # IPC server. Such case is of course not supposed to happen but if it
        # does we nevertheless keep the application running as a kind of
        # failsafe mode (and the crash reason is logged and sent to telemetry).

        # Systray is not displayed on MacOS, having natively a menu with similar functions.
        if systray_available() and sys.platform != "darwin":
            systray = Systray(parent=win)
            win.systray_notification.connect(systray.on_systray_notification)
            systray.on_close.connect(win.close_app)
            systray.on_show.connect(win.show_top)
            app.aboutToQuit.connect(before_quit(systray))
            if config.gui_tray_enabled:
                app.setQuitOnLastWindowClosed(False)

        if config.gui_check_version_at_startup and not diagnose:
            CheckNewVersion(jobs_ctx=jobs_ctx,
                            event_bus=event_bus,
                            config=config,
                            parent=win)

        win.show_window(skip_dialogs=diagnose)
        win.show_top()
        win.new_instance_needed.emit(start_arg)

        if sys.platform == "darwin":
            # macFUSE is not bundled with Parsec and must be manually installed by the user
            # so we detect early such need to provide a help dialogue ;-)
            # TODO: provide a similar mechanism on Windows&Linux to handle mountpoint runner not available
            from parsec.core.gui.instance_widget import ensure_macfuse_available_or_show_dialogue

            ensure_macfuse_available_or_show_dialogue(win)

        def kill_window(*args):
            win.close_app(force=True)

        signal.signal(signal.SIGINT, kill_window)

        # QTimer wakes up the event loop periodically which allows us to close
        # the window even when it is in background.
        timer = QTimer()
        timer.start(400)
        timer.timeout.connect(lambda: None)

        if diagnose:
            diagnose_timer = QTimer()
            diagnose_timer.start(1000)
            diagnose_timer.timeout.connect(kill_window)

        if lang_key:
            event_bus.send(CoreEvent.GUI_CONFIG_CHANGED, gui_language=lang_key)

        with QDialogInProcess.manage_pools():
            if diagnose:
                with fail_on_first_exception(kill_window):
                    await trio.sleep_forever()
            else:
                with log_pyqt_exceptions():
                    await trio.sleep_forever()