Ejemplo n.º 1
0
def test_fallback(config_name):

    # create proxy w/o fallback
    with pytest.raises(CommunicationError):
        MaestralProxy(config_name)

    # create proxy w/ fallback
    with MaestralProxy(config_name, fallback=True) as m:
        assert m.config_name == config_name
        assert m._is_fallback
        assert isinstance(m._m, Maestral)
Ejemplo n.º 2
0
    def test_start_stop(self):
        subprocess.run(["maestral", "start", "-c", self.config_name])

        with MaestralProxy(self.config_name) as m:
            self.assertTrue(m.running)
            self.assertTrue(m.syncing)

        subprocess.run(["maestral", "stop", "-c", self.config_name])

        with self.assertRaises(Pyro5.errors.CommunicationError):
            MaestralProxy(self.config_name)
Ejemplo n.º 3
0
def wait_for_idle(m: MaestralProxy, minimum: int = 2):

    while True:
        current_status = m.status
        time.sleep(minimum)
        if current_status in (IDLE, PAUSED, ERROR, ""):
            m.status_change_longpoll(timeout=minimum)
            if m.status == current_status:
                # status did not change, we are done
                return
        else:
            m.status_change_longpoll(timeout=minimum)
Ejemplo n.º 4
0
def analytics(yes: bool, no: bool, config_name: str) -> None:

    if yes or no:
        with MaestralProxy(config_name, fallback=True) as m:
            m.analytics = yes

        enabled_str = "Enabled" if yes else "Disabled"
        click.echo(f"{enabled_str} automatic error reports.")
    else:
        with MaestralProxy(config_name, fallback=True) as m:
            state = m.analytics

        enabled_str = "enabled" if state else "disabled"
        click.echo(f"Automatic error reports are {enabled_str}.")
Ejemplo n.º 5
0
    def run(self):

        try:
            with MaestralProxy(self.config_name) as proxy:

                self.connection = proxy._m._pyroConnection

                func = proxy.__getattr__(self._target)

                res = func(*self._args, **self._kwargs)

                if hasattr(res, "__next__"):
                    while True:
                        try:
                            next_res = next(res)
                            self.emitter.sig_result.emit(next_res)
                        except StopIteration:
                            return
                else:
                    self.emitter.sig_result.emit(res)

        except ConnectionClosedError:
            pass
        except Exception as exc:
            self.emitter.sig_result.emit(exc)
        finally:
            self.connection = None
            self.emitter.sig_done.emit()
Ejemplo n.º 6
0
def excluded_remove(dropbox_path: str, config_name: str):
    """Removes a file or folder from the excluded list and re-syncs."""

    if not dropbox_path.startswith('/'):
        dropbox_path = '/' + dropbox_path

    if dropbox_path == '/':
        click.echo(
            click.style('The root directory is always included.', fg='red'))
        return

    if not pending_link_cli(config_name):

        from maestral.daemon import MaestralProxy

        try:
            with MaestralProxy(config_name) as m:
                if _check_for_fatal_errors(m):
                    return
                try:
                    m.include_item(dropbox_path)
                    click.echo(
                        f'Included \'{dropbox_path}\'. Now downloading...')
                except ConnectionError:
                    raise click.ClickException('Could not connect to Dropbox.')
                except ValueError as e:
                    raise click.ClickException(e.args[0])

        except Pyro5.errors.CommunicationError:
            raise click.ClickException('Maestral daemon must be running '
                                       'to download folders.')
Ejemplo n.º 7
0
def test_notify_level(config_name):

    start_maestral_daemon_process(config_name, timeout=20)
    m = MaestralProxy(config_name)

    runner = CliRunner()
    result = runner.invoke(main, ["notify", "level", "-c", m.config_name])

    level_name = level_number_to_name(m.notification_level)

    assert result.exit_code == 0
    assert level_name in result.output

    level_name = "SYNCISSUE"
    level_number = level_name_to_number(level_name)
    result = runner.invoke(
        main, ["notify", "level", level_name, "-c", m.config_name])

    assert result.exit_code == 0
    assert level_name in result.output
    assert m.notification_level == level_number

    result = runner.invoke(main,
                           ["notify", "level", "INVALID", "-c", m.config_name])

    assert result.exit_code == 2
    assert isinstance(result.exception, SystemExit)
Ejemplo n.º 8
0
    def test_fallback(self):

        config_name = "daemon-lifecycle-test"

        # create proxy w/o fallback
        with self.assertRaises(CommunicationError):
            MaestralProxy(config_name)

        # create proxy w/ fallback
        with MaestralProxy(config_name, fallback=True) as m:
            self.assertEqual(m.config_name, config_name)
            self.assertTrue(m._is_fallback)
            self.assertIsInstance(m._m, Maestral)

        # clean up config
        remove_configuration(config_name)
Ejemplo n.º 9
0
def rebuild_index(config_name: str):
    """Rebuilds Maestral's index. May take several minutes."""

    try:
        import textwrap
        from maestral.daemon import MaestralProxy

        with MaestralProxy(config_name) as m:

            width, height = click.get_terminal_size()

            msg = textwrap.fill(
                'Rebuilding the index may take several minutes, depending on the size of '
                'your Dropbox. Any changes to local files will be synced once rebuilding '
                'has completed. If you stop the daemon during the process, rebuilding '
                'will start again on the next launch.',
                width=width)

            click.echo(msg + '\n')
            click.confirm('Do you want to continue?', abort=True)

            m.rebuild_index()

    except Pyro5.errors.CommunicationError:
        click.echo('Maestral daemon is not running.')
Ejemplo n.º 10
0
def history(config_name: str) -> None:

    from datetime import datetime

    with MaestralProxy(config_name, fallback=True) as m:

        history = m.get_history()
        paths = []
        change_times = []
        change_types = []

        for event in history:

            dbx_path = cast(str, event["dbx_path"])
            change_type = cast(str, event["change_type"])
            change_time_or_sync_time = cast(float, event["change_time_or_sync_time"])
            dt = datetime.fromtimestamp(change_time_or_sync_time)

            paths.append(dbx_path)
            change_times.append(dt.strftime("%d %b %Y %H:%M"))
            change_types.append(change_type)

        click.echo(
            format_table(columns=[paths, change_types, change_times], wrap=False)
        )
Ejemplo n.º 11
0
def revs(dropbox_path: str, config_name: str) -> None:

    from datetime import datetime

    if not dropbox_path.startswith("/"):
        dropbox_path = "/" + dropbox_path

    with MaestralProxy(config_name, fallback=True) as m:

        entries = m.list_revisions(dropbox_path)

    revs = []
    last_modified = []

    for e in entries:

        rev = cast(str, e["rev"])
        cm = cast(str, e["client_modified"])

        revs.append(rev)

        dt = datetime.strptime(cm, "%Y-%m-%dT%H:%M:%S%z").astimezone()
        last_modified.append(dt.strftime("%d %b %Y %H:%M"))

    click.echo(format_table(columns=[revs, last_modified]))
Ejemplo n.º 12
0
def rebuild_index(config_name: str) -> None:

    import textwrap

    with MaestralProxy(config_name, fallback=True) as m:

        width, height = click.get_terminal_size()

        msg = textwrap.fill(
            "Rebuilding the index may take several minutes, depending on the size of "
            "your Dropbox. Any changes to local files will be synced once rebuilding "
            "has completed. If you stop the daemon during the process, rebuilding will "
            "start again on the next launch.\nIf the daemon is not currently running, "
            "a rebuild will be schedules for the next startup.",
            width=width,
        )

        click.echo(msg + "\n")
        click.confirm("Do you want to continue?", abort=True)

        m.rebuild_index()

        if isinstance(m, MaestralProxy):
            click.echo('Rebuilding now. Run "maestral status" to view progress.')
        else:
            click.echo("Daemon is not running. Rebuilding scheduled for next startup.")
Ejemplo n.º 13
0
def status(config_name: str) -> None:

    try:
        with MaestralProxy(config_name) as m:

            n_errors = len(m.sync_errors)
            color = "red" if n_errors > 0 else "green"
            n_errors_str = click.style(str(n_errors), fg=color)
            click.echo("")
            click.echo("Account:      {}".format(m.get_state("account", "email")))
            click.echo("Usage:        {}".format(m.get_state("account", "usage")))
            click.echo("Status:       {}".format(m.status))
            click.echo("Sync threads: {}".format("Running" if m.running else "Stopped"))
            click.echo("Sync errors:  {}".format(n_errors_str))
            click.echo("")

            check_for_fatal_errors(m)

            sync_err_list = m.sync_errors

            if len(sync_err_list) > 0:
                headers = ["PATH", "ERROR"]
                col0 = ["'{}'".format(err["dbx_path"]) for err in sync_err_list]
                col1 = ["{title}. {message}".format(**err) for err in sync_err_list]

                click.echo(format_table(columns=[col0, col1], headers=headers))
                click.echo("")

    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")
Ejemplo n.º 14
0
 def rebuild_in_thread():
     if isinstance(m0, Pyro5.client.Proxy):
         # rebuild index from separate proxy
         with MaestralProxy(config_name) as m1:
             m1.rebuild_index()
     else:
         # rebuild index with main instance
         m0.rebuild_index()
Ejemplo n.º 15
0
def move_dir(new_path: str, config_name: str) -> None:

    new_path = new_path or select_dbx_path_dialog(config_name)

    with MaestralProxy(config_name, fallback=True) as m:
        m.move_dropbox_directory(new_path)

    click.echo(f"Dropbox folder moved to {new_path}.")
Ejemplo n.º 16
0
def pause(config_name: str) -> None:

    try:
        with MaestralProxy(config_name) as m:
            m.pause_sync()
        click.echo("Syncing paused.")
    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")
Ejemplo n.º 17
0
def resume(config_name: str) -> None:

    try:
        with MaestralProxy(config_name) as m:
            if not check_for_fatal_errors(m):
                m.resume_sync()
                click.echo("Syncing resumed.")

    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")
Ejemplo n.º 18
0
def pause(config_name: str):
    """Pauses syncing."""
    from maestral.daemon import MaestralProxy

    try:
        with MaestralProxy(config_name) as m:
            m.pause_sync()
        click.echo('Syncing paused.')
    except Pyro5.errors.CommunicationError:
        click.echo('Maestral daemon is not running.')
Ejemplo n.º 19
0
def link(relink: bool, config_name: str) -> None:

    with MaestralProxy(config_name, fallback=True) as m:

        if m.pending_link or relink:
            link_dialog(m)
        else:
            click.echo(
                "Maestral is already linked. Use the option "
                "'-r' to relink to the same account."
            )
Ejemplo n.º 20
0
def notify_level(level_name: str, config_name: str) -> None:

    from maestral.utils.notify import MaestralDesktopNotifier as MDN

    with MaestralProxy(config_name, fallback=True) as m:
        if level_name:
            m.notification_level = MDN.level_name_to_number(level_name)
            click.echo(f"Notification level set to {level_name}.")
        else:
            level_name = MDN.level_number_to_name(m.notification_level)
            click.echo(f"Notification level: {level_name}.")
Ejemplo n.º 21
0
def log_level(level_name: str, config_name: str) -> None:

    import logging

    with MaestralProxy(config_name, fallback=True) as m:
        if level_name:
            m.log_level = cast(int, getattr(logging, level_name))
            click.echo(f"Log level set to {level_name}.")
        else:
            level_name = logging.getLevelName(m.log_level)
            click.echo(f"Log level: {level_name}")
Ejemplo n.º 22
0
def resume(config_name: str):
    """Resumes syncing."""
    from maestral.daemon import MaestralProxy

    try:
        with MaestralProxy(config_name) as m:
            if not _check_for_fatal_errors(m):
                m.resume_sync()
                click.echo('Syncing resumed.')

    except Pyro5.errors.CommunicationError:
        click.echo('Maestral daemon is not running.')
Ejemplo n.º 23
0
def test_remote_exceptions(config_name):

    # start daemon process
    start_maestral_daemon_process(config_name, timeout=20)

    # create proxy and call a remote method which raises an error
    with MaestralProxy(config_name) as m:
        with pytest.raises(NotLinkedError):
            m.get_account_info()

    # stop daemon
    stop_maestral_daemon_process(config_name)
Ejemplo n.º 24
0
def excluded_list(config_name: str) -> None:

    with MaestralProxy(config_name, fallback=True) as m:

        excluded_items = m.excluded_items
        excluded_items.sort()

        if len(excluded_items) == 0:
            click.echo("No excluded files or folders.")
        else:
            for item in excluded_items:
                click.echo(item)
Ejemplo n.º 25
0
def analytics(config_name: str, yes: bool, no: bool):
    """Enables or disables sharing error reports."""
    # This is safe to call regardless if the GUI or daemon are running.
    from maestral.daemon import MaestralProxy

    if yes or no:
        try:
            with MaestralProxy(config_name) as m:
                m.analytics = yes
        except Pyro5.errors.CommunicationError:
            MaestralConfig(config_name).set('app', 'analytics', yes)

        enabled_str = 'Enabled' if yes else 'Disabled'
        click.echo(f'{enabled_str} automatic error reports.')
    else:
        try:
            with MaestralProxy(config_name) as m:
                state = m.analytics
        except Pyro5.errors.CommunicationError:
            state = MaestralConfig(config_name).get('app', 'analytics')
        enabled_str = 'enabled' if state else 'disabled'
        click.echo(f'Automatic error reports are {enabled_str}.')
Ejemplo n.º 26
0
def file_status(local_path: str, config_name: str) -> None:

    try:
        with MaestralProxy(config_name) as m:

            if check_for_fatal_errors(m):
                return

            stat = m.get_file_status(local_path)
            click.echo(stat)

    except Pyro5.errors.CommunicationError:
        click.echo("unwatched")
Ejemplo n.º 27
0
def move_dir(config_name: str, new_path: str):
    """Change the location of your Dropbox folder."""

    if not pending_link_cli(config_name):
        from maestral.main import select_dbx_path_dialog
        from maestral.daemon import MaestralProxy

        new_path = new_path or select_dbx_path_dialog(config_name,
                                                      allow_merge=False)

        with MaestralProxy(config_name, fallback=True) as m:
            m.move_dropbox_directory(new_path)

        click.echo(f'Dropbox folder moved to {new_path}.')
Ejemplo n.º 28
0
def notify_snooze(minutes: int, config_name: str) -> None:

    try:
        with MaestralProxy(config_name) as m:
            m.notification_snooze = minutes
    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")
    else:
        if minutes > 0:
            click.echo(
                f"Notifications snoozed for {minutes} min. " "Set snooze to 0 to reset."
            )
        else:
            click.echo("Notifications enabled.")
Ejemplo n.º 29
0
def file_status(config_name: str, local_path: str):
    """Returns the current sync status of a given file or folder."""
    from maestral.daemon import MaestralProxy

    try:
        with MaestralProxy(config_name) as m:

            if _check_for_fatal_errors(m):
                return

            stat = m.get_file_status(local_path)
            click.echo(stat)

    except Pyro5.errors.CommunicationError:
        click.echo('unwatched')
Ejemplo n.º 30
0
def excluded_add(dropbox_path: str, config_name: str) -> None:

    if not dropbox_path.startswith("/"):
        dropbox_path = "/" + dropbox_path

    if dropbox_path == "/":
        click.echo(click.style("Cannot exclude the root directory.", fg="red"))
        return

    with MaestralProxy(config_name, fallback=True) as m:
        if check_for_fatal_errors(m):
            return

        m.exclude_item(dropbox_path)
        click.echo(f"Excluded '{dropbox_path}'.")