예제 #1
0
def excluded_remove(dropbox_path: str, config_name: str):
    """Removes a 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 _is_maestral_linked(config_name):

        from maestral.sync.daemon import MaestralProxy

        try:
            with MaestralProxy(config_name) as m:
                if _check_for_fatal_errors(m):
                    return
                try:
                    m.include_folder(dropbox_path)
                    click.echo("Included directory '{}'. Now downloading...".format(dropbox_path))
                except ConnectionError:
                    click.echo("Could not connect to Dropbox.")
                except ValueError as e:
                    click.echo("Error: " + e.args[0])

        except Pyro5.errors.CommunicationError:
            click.echo("Maestral daemon must be running to download folders.")
예제 #2
0
def ls(dropbox_path: str, config_name: str, list_all: bool):
    """Lists contents of a Dropbox directory."""

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

    if _is_maestral_linked(config_name):
        from maestral.sync.daemon import MaestralProxy
        from maestral.sync.errors import PathError

        with MaestralProxy(config_name, fallback=True) as m:
            try:
                entries = m.list_folder(dropbox_path, recursive=False)
            except PathError:
                click.echo("Error: No such directory on Dropbox: '{}'".format(dropbox_path))
                return

            if not entries:
                click.echo("Could not connect to Dropbox")
                return

            types = list("file" if e["type"] == "FileMetadata" else "folder" for e in entries)
            shared_status = list("shared" if "sharing_info" in e else "private" for e in entries)
            names = list(e["name"] for e in entries)
            excluded_status = list(m.excluded_status(e["path_lower"]) for e in entries)

            click.echo("")
            click.echo(format_table([types, shared_status, names, excluded_status]))
            click.echo("")
예제 #3
0
def status(config_name: str):
    """Returns the current status of the Maestral daemon."""
    from maestral.sync.daemon import MaestralProxy

    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_conf("account", "email")))
            click.echo("Usage:         {}".format(m.get_conf("account", "usage")))
            click.echo("Status:        {}".format(m.status))
            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:
                header = ("PATH", "ERROR")
                col0 = list("'{}'".format(err["dbx_path"]) for err in sync_err_list)
                col1 = list("{}. {}".format(err["title"], err["message"]) for err in sync_err_list)

                click.echo(format_table([col0, col1], header, spacing=4))
                click.echo("")

    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")
예제 #4
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()
예제 #5
0
def analytics(config_name: str, yes: bool):
    """Enables or disables sharing crash reports."""
    # This is safe to call, even if the GUI or daemon are running.
    from maestral.sync.daemon import MaestralProxy

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

    enabled_str = "Enabled" if yes else "Disabled"
    click.echo("{} automatic crash reports.".format(enabled_str))
예제 #6
0
def notifications(config_name: str, yes: bool):
    """Enables or disables system notifications."""
    # This is safe to call, even if the GUI or daemon are running.
    from maestral.sync.daemon import MaestralProxy

    with MaestralProxy(config_name, fallback=True) as m:
        m.set_conf("app", "notifications", yes)

    enabled_str = "Enabled" if yes else "Disabled"
    click.echo("{} system notifications.".format(enabled_str))
예제 #7
0
def pause(config_name: str):
    """Pauses syncing."""
    from maestral.sync.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.")
예제 #8
0
def resume(config_name: str):
    """Resumes syncing."""
    from maestral.sync.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.")
예제 #9
0
def set_dir(config_name: str, new_path: str):
    """Change the location of your Dropbox folder."""

    if _is_maestral_linked(config_name):
        from maestral.sync.main import Maestral
        from maestral.sync.daemon import MaestralProxy
        with MaestralProxy(config_name, fallback=True) as m:
            if not new_path:
                # don't use the remote instance because we need console interaction
                new_path = Maestral._ask_for_path(config_name)
            m.move_dropbox_directory(new_path)

        click.echo("Dropbox folder moved to {}.".format(new_path))
예제 #10
0
def file_status(config_name: str, local_path: str):
    """Returns the current sync status of a given file or folder."""
    from maestral.sync.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")
예제 #11
0
def level(config_name: str, level_name: str):
    """Gets or sets the log level. Changes will take effect after restart."""
    if level_name:
        from maestral.sync.daemon import MaestralProxy

        level_num = logging._nameToLevel[level_name]
        with MaestralProxy(config_name, fallback=True) as m:
            m.set_log_level(level_num)
        click.echo("Log level set to {}.".format(level_name))
    else:
        os.environ["MAESTRAL_CONFIG"] = config_name
        from maestral.config.main import MaestralConfig

        conf = MaestralConfig(config_name)

        level_num = conf.get("app", "log_level")
        level_name = logging.getLevelName(level_num)
        click.echo("Log level:  {}".format(level_name))
예제 #12
0
def excluded_add(dropbox_path: str, config_name: str):
    """Adds a folder to the excluded list and re-syncs."""

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

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

    if _is_maestral_linked(config_name):

        from maestral.sync.daemon import MaestralProxy

        with MaestralProxy(config_name, fallback=True) as m:
            if _check_for_fatal_errors(m):
                return
            try:
                m.exclude_folder(dropbox_path)
                click.echo("Excluded directory '{}'.".format(dropbox_path))
            except ConnectionError:
                click.echo("Could not connect to Dropbox.")
            except ValueError as e:
                click.echo("Error: " + e.args[0])
예제 #13
0
 def start(self):
     with MaestralProxy(self.config_name) as m:
         func = m.__getattr__(self._target)
         res = func(*self._args, **self._kwargs)
     self.sig_done.emit(res)
예제 #14
0
def activity(config_name: str):
    """Live view of all items being synced."""
    from maestral.sync.daemon import MaestralProxy

    try:
        with MaestralProxy(config_name) as m:

            if _check_for_fatal_errors(m):
                return

            import curses
            import time

            def curses_loop(screen):

                curses.use_default_colors()  # don't change terminal background
                screen.nodelay(1)  # set `scree.getch()` to non-blocking

                while True:

                    # get info from daemon
                    res = m.get_activity()
                    up = res["uploading"]
                    down = res["downloading"]
                    sync_status = m.status
                    n_errors = len(m.sync_errors)

                    # create header
                    lines = [
                        "Status: {}, Sync errors: {}".format(sync_status, n_errors),
                        "Uploading: {}, Downloading: {}".format(len(up), len(down)),
                        "",
                    ]

                    # create table
                    up.insert(0, ("UPLOADING", "STATUS"))  # column titles
                    up.append(("", ""))  # append spacer
                    down.insert(0, ("DOWNLOADING", "STATUS"))  # column titles

                    file_names = tuple(os.path.basename(item[0]) for item in up + down)
                    states = tuple(item[1] for item in up + down)
                    col_len = max(len(fn) for fn in file_names) + 2

                    for fn, s in zip(file_names, states):  # create rows
                        lines.append(fn.ljust(col_len) + s)

                    # print to console screen
                    screen.clear()
                    try:
                        screen.addstr("\n".join(lines))
                    except curses.error:
                        pass
                    screen.refresh()

                    # abort when user presses "q", refresh otherwise
                    key = screen.getch()
                    if key == ord("q"):
                        break
                    elif key < 0:
                        time.sleep(1)

            # enter curses event loop
            curses.wrapper(curses_loop)

    except Pyro5.errors.CommunicationError:
        click.echo("Maestral daemon is not running.")