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.")
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("")
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.")
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()
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))
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))
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.")
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.")
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))
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")
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))
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])
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)
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.")