#!/usr/bin/env python3
#
# Copyright (c) Dropbox, Inc.
#
# dropbox
# Dropbox frontend script
# This file is part of nautilus-dropbox 2026.03.20.
#
# nautilus-dropbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# nautilus-dropbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with nautilus-dropbox.  If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import with_statement

import _thread
import errno
import locale
import optparse
import os
import platform
import shutil
import socket
import subprocess
import sys
import tarfile
import tempfile
import threading
import time
import traceback
import urllib.request

try:
    import gpg

    gpgme = None
except ImportError:
    gpg = None
    # Still support gpgme for now. Remove this once we only support 17.04+.
    try:
        import gpgme
    except ImportError:
        gpgme = None

from contextlib import closing, contextmanager
from io import BytesIO
from operator import methodcaller
from os.path import relpath

INFO = "Dropbox is the easiest way to share and store your files online. Want to learn more? Head to"
LINK = "https://www.dropbox.com/"
WARNING = "In order to use Dropbox, you must download the proprietary daemon."
GPG_WARNING = "Note: python3-gpg (python3-gpgme for Ubuntu 16.10 and lower) is not installed, we will not be able to verify binary signatures."
ERROR_CONNECTING = "Trouble connecting to Dropbox servers. Maybe your internet connection is down, or you need to set your http_proxy environment variable."
ERROR_SIGNATURE = (
    "Downloaded binary does not match Dropbox signature, aborting install."
)
ERROR_INVALID_DROPBOX = "Could not start the Dropbox daemon. Make sure your computer meets the minimum requirements:\nhttps://www.dropbox.com/help/desktop-web/system-requirements#desktop"

DOWNLOAD_LOCATION_FMT = "https://www.dropbox.com/download?plat=%s"
SIGNATURE_LOCATION_FMT = "https://www.dropbox.com/download?plat=%s&signature=1"

DOWNLOADING = "Downloading Dropbox... %d%%"
UNPACKING = "Unpacking Dropbox... %d%%"

PARENT_DIR = os.path.expanduser("~")
DROPBOX_DIST_PATH = "%s/.dropbox-dist" % PARENT_DIR
DROPBOXD_PATH = os.path.join(DROPBOX_DIST_PATH, "dropboxd")
DESKTOP_FILE = "/usr/share/applications/dropbox.desktop"

enc = locale.getpreferredencoding()

# Available from https://linux.dropbox.com/fedora/rpm-public-key.asc
DROPBOX_PUBLIC_KEY = b"""
-----BEGIN PGP PUBLIC KEY BLOCK-----


mQENBEt0ibEBCACv4hZRPqwtpU6z8+BB5YZU1a3yjEvg2W68+a6hEwxtCa2U++4d
zQ+7EqaUq5ybQnwtbDdpFpsOi9x31J+PCpufPUfIG694/0rlEpmzl2GWzY8NqfdB
FGGm/SPSSwvKbeNcFMRLu5neo7W9kwvfMbGjHmvUbzBUVpCVKD0OEEf1q/Ii0Qce
kx9CMoLvWq7ZwNHEbNnij7ecnvwNlE2MxNsOSJj+hwZGK+tM19kuYGSKw4b5mR8I
yThlgiSLIfpSBh1n2KX+TDdk9GR+57TYvlRu6nTPu98P05IlrrCP+KF0hYZYOaMv
Qs9Rmc09tc/eoQlN0kkaBWw9Rv/dvLVc0aUXABEBAAG0MURyb3Bib3ggQXV0b21h
dGljIFNpZ25pbmcgS2V5IDxsaW51eEBkcm9wYm94LmNvbT6JATYEEwECACAFAkt0
ibECGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRD8kYszUESRLi/zB/wMscEa
15rS+0mIpsORknD7kawKwyda+LHdtZc0hD/73QGFINR2P23UTol/R4nyAFEuYNsF
0C4IAD6y4pL49eZ72IktPrr4H27Q9eXhNZfJhD7BvQMBx75L0F5gSQwuC7GdYNlw
SlCD0AAhQbi70VBwzeIgITBkMQcJIhLvllYo/AKD7Gv9huy4RLaIoSeofp+2Q0zU
HNPl/7zymOqu+5Oxe1ltuJT/kd/8hU+N5WNxJTSaOK0sF1/wWFM6rWd6XQUP03Vy
NosAevX5tBo++iD1WY2/lFVUJkvAvge2WFk3c6tAwZT/tKxspFy4M/tNbDKeyvr6
85XKJw9ei6GcOGHDiQGRBBMBCACFBYJpSvoxBAsJCAcJEPyRizNQRJEuRxQAAAAA
AB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZzm02zKJt0bJ2GvF0gBR
jDvHvvvTo2Yb2+/hvhtrIYZGAhUIBBYCAwECF4ACGwMCHgEWIQQcYaJlb7V7fk3g
9MH8kYszUESRLgAAwt4H/3saLWbASC+dJd4YEh7hWZtoMSKxYYBrnvhbfgidxvSV
XiJzYjPZbqw7fRjD6rbk/XnC+U5nFEHB6B7DxbG4R3xtuezwEYOq8Fn4N8uyOkby
k9FXWks0a2Q2uZ3+V4UH6CjrpnYPwnvfmGGPJ26iGrIEn94oAyCeqN0lzk4yIKU2
JolW1NVG0udlVsPEZuBTo1L07r1092A8jfjwNQakdI67uNkFCK6b/gNBkWzP6sz3
FSRXSIhwcJ23AECuUDUOqZJRKxJGo0zybRBhrjMA+ynjfDzwc6/4CCnnTgptMcOD
N35BHMH1ogcdOFNZOJY4HGd9HOONs40Av9f0GzX8DO8=
=kwIr
-----END PGP PUBLIC KEY BLOCK-----
"""


def console_print(st="", f=sys.stdout, linebreak=True):
    f.write(st)
    if linebreak:
        f.write(os.linesep)


def console_flush(f=sys.stdout):
    f.flush()


def yes_no_question(question):
    while True:
        console_print(question, linebreak=False)
        console_print(" [y/n] ", linebreak=False)
        console_flush()
        text = input()
        if text.lower().startswith("y"):
            return True
        elif text.lower().startswith("n"):
            return False
        else:
            console_print("Sorry, I didn't understand that. Please type yes or no.")


def plat():
    if sys.platform.lower().startswith("linux"):
        arch = platform.machine()
        if arch == "x86_64":
            plat = arch
        else:
            FatalVisibleError("Platform not supported")
        return "lnx.%s" % plat
    else:
        FatalVisibleError("Platform not supported")


def is_dropbox_running():
    pidfile = os.path.expanduser("~/.dropbox/dropbox.pid")

    try:
        with open(pidfile, "r") as f:
            pid = int(f.read())
        with open("/proc/%d/cmdline" % pid, "r") as f:
            cmdline = f.read().lower()
    except:
        cmdline = ""

    return "dropbox" in cmdline


@contextmanager
def gpg_context(keys):
    gpg_conf_contents = b""
    _gpghome = tempfile.mkdtemp(prefix="tmp.gpghome")

    try:
        os.environ["GNUPGHOME"] = _gpghome
        fp = open(os.path.join(_gpghome, "gpg.conf"), "wb")
        fp.write(gpg_conf_contents)
        fp.close()
        if gpg:
            ctx = gpg.Context()
        else:
            ctx = gpgme.Context()

        loaded = []
        for key_file in keys:
            if gpg:
                ctx.op_import(key_file.read())
                result = ctx.op_import_result()
                key = ctx.get_key(result.imports[0].fpr)
            else:
                result = ctx.import_(key_file)
                key = ctx.get_key(result.imports[0][0])
            loaded.append(key)

        ctx.signers = loaded

        yield ctx
    finally:
        del os.environ["GNUPGHOME"]
        shutil.rmtree(_gpghome, ignore_errors=True)


class SignatureVerifyError(Exception):
    pass


def verify_signature(key_file, sig_file, plain_file):
    with gpg_context([key_file]) as ctx:
        if gpg:
            ctx.op_verify(sig_file.read(), plain_file.read(), None)
            result = ctx.op_verify_result()
            return result.signatures[0].status == 0
        # gpgme exists
        sigs = ctx.verify(sig_file, plain_file, None)
        return sigs[0].status == None


def download_file_chunk(url, buf):
    opener = urllib.request.build_opener()
    opener.addheaders = [("User-Agent", "DropboxLinuxDownloader/2026.03.20")]

    with closing(opener.open(url)) as f:
        size = int(f.info()["content-length"])
        bufsize = int(max(size / 200, 4096))
        progress = 0
        yield (0, True)
        while True:
            try:
                chunk = f.read(bufsize)
                progress += len(chunk)
                buf.write(chunk)
                yield (float(progress) / size, True)
                if progress == size:
                    break
            except OSError as e:
                if hasattr(e, "errno") and e.errno == errno.EAGAIN:
                    # nothing left to read
                    yield (float(progress) / size, False)
                else:
                    raise


class DownloadState(object):
    def __init__(self):
        self.local_file = BytesIO()

    def copy_data(self):
        return download_file_chunk(DOWNLOAD_LOCATION_FMT % plat(), self.local_file)

    def unpack(self):
        # download signature
        signature = BytesIO()
        for _ in download_file_chunk(SIGNATURE_LOCATION_FMT % plat(), signature):
            pass
        signature.seek(0)
        self.local_file.seek(0)

        if gpg or gpgme:
            if not verify_signature(
                BytesIO(DROPBOX_PUBLIC_KEY), signature, self.local_file
            ):
                raise SignatureVerifyError()

        self.local_file.seek(0)
        archive = tarfile.open(fileobj=self.local_file, mode="r:gz")
        total_members = len(archive.getmembers())
        for i, member in enumerate(archive.getmembers()):
            filename = os.path.join(PARENT_DIR, member.name)
            if os.path.exists(filename) and not os.path.isdir(filename):
                os.unlink(filename)
            archive.extract(member, PARENT_DIR)
            yield member.name, i, total_members
        archive.close()

    def cancel(self):
        if not self.local_file.closed:
            self.local_file.close()

    def is_dropbox_valid(self):
        """
        Validate that Dropbox runs, so we can show an error
        message to the user if it doesn't work.

        Returns True if Dropbox can run, false otherwise.
        """
        f = open("/dev/null", "w")
        try:
            a = subprocess.Popen(
                [DROPBOXD_PATH, "/testrun", "0"],
                preexec_fn=os.setsid,
                cwd=os.path.expanduser("~"),
                stderr=sys.stderr,
                stdout=f,
                close_fds=True,
            )
        except Exception as e:
            print(e)
            return False

        # in seconds
        interval = 0.5
        wait_for = 30
        for _ in range(int(wait_for / interval)):
            ret_val = a.poll()
            if ret_val is None:
                time.sleep(interval)
                continue
            return ret_val == 0

        return False


def load_serialized_images():
    global box_logo_pixbuf, window_icon
    import gi

    gi.require_version("GdkPixbuf", "2.0")
    import base64

    from gi.repository import GdkPixbuf

    # Decode base64 string to bytes
    image64_str = eval(
        "' '"
    )  # Use eval() to safely unescape a `repr()`d string
    image16_str = eval(
        "' '"
    )  # Use eval() to safely unescape a `repr()`d string
    image64_bytes = base64.b64decode(image64_str)
    image16_bytes = base64.b64decode(image16_str)

    # Create GdkPixbuf from bytes
    loader64 = GdkPixbuf.PixbufLoader()
    loader64.write(image64_bytes)
    loader64.close()
    box_logo_pixbuf = loader64.get_pixbuf()

    loader16 = GdkPixbuf.PixbufLoader()
    loader16.write(image16_bytes)
    loader16.close()
    window_icon = loader16.get_pixbuf()


GUI_AVAILABLE = os.environ.get("DISPLAY", "")

if GUI_AVAILABLE:

    def download():
        import gi

        gi.require_version("Gdk", "3.0")
        gi.require_version("Gtk", "3.0")
        import webbrowser

        from gi.repository import Gdk, GLib, Gtk, Pango

        load_serialized_images()

        global FatalVisibleError

        def FatalVisibleError(s):
            error = Gtk.MessageDialog(
                parent=None,
                flags=Gtk.DialogFlags.MODAL,
                type=Gtk.MessageType.ERROR,
                buttons=Gtk.ButtonsType.OK,
                message_format=s,
            )
            error.set_title("Error")
            error.run()
            Gtk.main_quit()
            sys.exit(-1)

        class GeneratorTask(object):
            def __init__(
                self, generator, loop_callback, on_done=None, on_exception=None
            ):
                self.generator = generator
                self.loop_callback = loop_callback
                self.on_done = on_done
                self.on_exception = on_exception

            def _run(self, *args, **kwargs):
                self._stopped = False
                try:
                    for ret in self.generator(*args, **kwargs):
                        if ret is None:
                            ret = ()
                        if not isinstance(ret, tuple):
                            ret = (ret,)
                        GLib.idle_add(self.loop_callback, *ret)

                        if self._stopped:
                            _thread.exit()
                except Exception as e:
                    print(e)
                    if self.on_exception is not None:
                        GLib.idle_add(self.on_exception, e)
                else:
                    if self.on_done is not None:
                        GLib.idle_add(self.on_done)

            def start(self, *args, **kwargs):
                t = threading.Thread(
                    target=self._run, args=args, kwargs=kwargs, daemon=True
                )
                t.start()

            def stop(self):
                self._stopped = True

        class DownloadDialog(Gtk.Dialog):
            def handle_delete_event(self, wid, ev, data=None):
                self.handle_cancel(wid)

            def handle_dont_show_toggle(self, button, data=None):
                reroll_autostart(not button.get_active())

            def handle_cancel(self, button):
                if self.task:
                    self.task.stop()
                if self.download:
                    self.download.cancel()
                Gtk.main_quit()
                self.user_cancelled = True

            def handle_ok(self, button):
                # begin download
                self.ok.hide()
                self.download = DownloadState()

                self.label.hide()
                if self.dont_show_again_align is not None:
                    self.dont_show_again_align.hide()
                self.progress.show()

                def download_progress(progress, status):
                    if not status:
                        self.task.stop()
                    self.update_progress(DOWNLOADING, progress)

                def finished():
                    self.update_progress(DOWNLOADING, 1.0)
                    self.unpack_dropbox()

                def error(ex):
                    FatalVisibleError(ERROR_CONNECTING)

                self.update_progress(DOWNLOADING, 0)
                self.task = GeneratorTask(
                    self.download.copy_data, download_progress, finished, error
                ).start()

            def update_progress(self, text, fraction):
                self.progress.set_text(text % int(fraction * 100))
                self.progress.set_fraction(fraction)

            def unpack_dropbox(self):
                def unpack_progress(name, i, total):
                    self.update_progress(UNPACKING, float(i) / total)

                def finished():
                    self.update_progress(UNPACKING, 1.0)
                    if not self.download.is_dropbox_valid():
                        FatalVisibleError(ERROR_INVALID_DROPBOX)
                    Gtk.main_quit()

                def error(ex):
                    if isinstance(ex, SignatureVerifyError):
                        FatalVisibleError(ERROR_SIGNATURE)
                    else:
                        FatalVisibleError(ERROR_CONNECTING)

                self.task = GeneratorTask(
                    self.download.unpack, unpack_progress, finished, error
                ).start()

            def mouse_down(self, widget, event):
                if self.hovering:
                    self.clicked_link = True

            def mouse_up(self, widget, event):
                if self.clicked_link:
                    webbrowser.open(LINK)
                    self.clicked_link = False

            def label_motion(self, widget, event):
                offx, offy = self.label.get_layout_offsets()
                layout = self.label.get_layout()
                index = layout.xy_to_index(
                    int((offx + event.x) * Pango.SCALE),
                    int((offy + event.y) * Pango.SCALE),
                )[1]
                link_index = layout.get_text().find(LINK)
                if index >= link_index and index < link_index + len(LINK):
                    self.hovering = True
                    self.label_box.get_window().set_cursor(
                        Gdk.Cursor(Gdk.CursorType.HAND2)
                    )
                else:
                    self.hovering = False
                    self.label_box.get_window().set_cursor(
                        Gdk.Cursor(Gdk.CursorType.ARROW)
                    )

            def __init__(self):
                super(DownloadDialog, self).__init__(
                    parent=None, title="Dropbox Installation"
                )

                self.download = None
                self.hovering = False
                self.clicked_link = False
                self.user_cancelled = False
                self.task = None

                self.ok = ok = Gtk.Button.new_with_mnemonic("_OK")
                ok.connect("clicked", self.handle_ok)
                self.action_area.add(ok)
                ok.show()

                cancel = Gtk.Button.new_with_mnemonic("_Cancel")
                cancel.connect("clicked", self.handle_cancel)
                self.action_area.add(cancel)
                cancel.show()

                self.connect("delete_event", self.handle_delete_event)

                self.box_logo = Gtk.Image.new_from_pixbuf(box_logo_pixbuf)
                self.box_logo.show()

                self.set_icon(window_icon)

                self.progress = Gtk.ProgressBar()
                self.progress.set_property("width-request", 300)
                self.progress.set_property("show-text", True)

                self.label = Gtk.Label()
                GPG_WARNING_MSG = (
                    ("\n\n" + GPG_WARNING) if not gpg and not gpgme else ""
                )
                self.label.set_markup(
                    '%s <span foreground="#000099" underline="single" weight="bold">%s</span>\n\n%s%s'
                    % (INFO, LINK, WARNING, GPG_WARNING_MSG)
                )
                self.label.set_line_wrap(True)
                self.label.set_property("width-request", 300)
                self.label.show()

                self.label_box = Gtk.EventBox()
                self.label_box.add(self.label)
                self.label_box.connect("button-release-event", self.mouse_up)
                self.label_box.connect("button-press-event", self.mouse_down)
                self.label_box.connect("motion-notify-event", self.label_motion)

                self.label_box.show()

                def on_realize(widget):
                    self.label_box.add_events(Gdk.EventMask.POINTER_MOTION_MASK)

                self.label_box.connect("realize", on_realize)

                self.hbox = Gtk.HBox(spacing=10)
                self.hbox.set_property("border-width", 10)
                self.hbox.pack_start(self.box_logo, False, False, 0)
                self.hbox.pack_start(self.label_box, False, False, 0)
                self.hbox.pack_start(self.progress, False, False, 0)
                self.hbox.show()

                self.vbox.add(self.hbox)

                self.dont_show_again_align = None

                try:
                    if can_reroll_autostart():
                        dont_show_again = Gtk.CheckButton.new_with_mnemonic(
                            "_Don't show this again"
                        )
                        dont_show_again.connect("toggled", self.handle_dont_show_toggle)
                        dont_show_again.show()

                        self.dont_show_again_align = Gtk.Alignment(
                            xalign=1.0, yalign=0.0, xscale=0.0, yscale=0.0
                        )
                        self.dont_show_again_align.add(dont_show_again)
                        self.dont_show_again_align.show()

                        hbox = Gtk.HBox()
                        hbox.set_property("border-width", 10)
                        hbox.pack_start(self.dont_show_again_align, True, True, 0)
                        hbox.show()

                        self.vbox.add(hbox)

                    self.set_resizable(False)
                except:
                    traceback.print_exc()

                self.ok.grab_focus()

        dialog = DownloadDialog()
        dialog.show()
        Gtk.main()
        if dialog.user_cancelled:
            raise Exception("user cancelled download!!!")
else:

    def download():
        global FatalVisibleError

        def FatalVisibleError(s):
            console_print("\nError: %s" % s, f=sys.stderr)
            sys.exit(-1)

        ESC = "\x1b"
        save = ESC + "7"
        unsave = ESC + "8"
        erase_to_start = ESC + "[1K"
        write = sys.stdout.write
        flush = sys.stdout.flush

        last_progress = [None, None]

        def setprogress(text, frac):
            if last_progress == [text, frac]:
                return
            if sys.stdout.isatty():
                write(erase_to_start)
                write(unsave)
            console_print(text % int(100 * frac), linebreak=not sys.stdout.isatty())
            if sys.stdout.isatty():
                flush()
            last_progress[0], last_progress[1] = text, frac

        console_print()
        if sys.stdout.isatty():
            write(save)
            flush()
        console_print("%s %s\n" % (INFO, LINK))
        GPG_WARNING_MSG = ("\n%s" % GPG_WARNING) if not gpg and not gpgme else ""

        if not yes_no_question("%s%s" % (WARNING, GPG_WARNING_MSG)):
            return

        download = DownloadState()

        try:
            for progress, status in download.copy_data():
                if not status:
                    break
                setprogress(DOWNLOADING, progress)
        except Exception:
            traceback.print_exc()
            FatalVisibleError(ERROR_CONNECTING)
        else:
            setprogress(DOWNLOADING, 1.0)
            console_print()
            write(save)

        try:
            for _, i, total in download.unpack():
                setprogress(UNPACKING, float(i) / total)
        except SignatureVerifyError:
            traceback.print_exc()
            FatalVisibleError(ERROR_SIGNATURE)
        except Exception:
            traceback.print_exc()
            FatalVisibleError(ERROR_CONNECTING)
        else:
            setprogress(UNPACKING, 1.0)

        if not download.is_dropbox_valid():
            FatalVisibleError(ERROR_INVALID_DROPBOX)

        console_print()


class CommandTicker(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()

    def stop(self):
        self.stop_event.set()

    def run(self):
        ticks = ["[.  ]", "[.. ]", "[...]", "[ ..]", "[  .]", "[   ]"]
        i = 0
        first = True
        while True:
            self.stop_event.wait(0.25)
            if self.stop_event.is_set():
                break
            if i == len(ticks):
                first = False
                i = 0
            if not first:
                sys.stderr.write("\r%s\r" % ticks[i])
                sys.stderr.flush()
            i += 1
        sys.stderr.flush()


class DropboxCommand(object):
    class CouldntConnectError(Exception):
        pass

    class BadConnectionError(Exception):
        pass

    class EOFError(Exception):
        pass

    class CommandError(Exception):
        pass

    def __init__(self, timeout=5):
        self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.s.settimeout(timeout)
        try:
            self.s.connect(os.path.expanduser("~/.dropbox/command_socket"))
        except socket.error:
            raise DropboxCommand.CouldntConnectError()
        self.f = self.s.makefile("rw", 4096)

    def close(self):
        self.f.close()
        self.s.close()

    def __readline(self):
        try:
            toret = self.f.readline().rstrip("\n")
        except socket.error:
            raise DropboxCommand.BadConnectionError()
        if toret == "":
            raise DropboxCommand.EOFError()
        else:
            return toret

    # atttribute doesn't exist, i know what you want
    def send_command(self, name, args):
        self.f.write(name)
        self.f.write("\n")
        self.f.writelines(
            ("\t".join([k] + ([v] if isinstance(v, str) else list(v))) + "\n")
            for k, v in args.items()
        )
        self.f.write("done\n")

        self.f.flush()

        ticker_thread = None
        if sys.stderr.isatty():
            # Start a ticker
            ticker_thread = CommandTicker()
            ticker_thread.start()

        # This is the potentially long-running call.
        try:
            ok = self.__readline() == "ok"
        except KeyboardInterrupt:
            raise DropboxCommand.BadConnectionError("Keyboard interruption detected")
        finally:
            if ticker_thread is not None:
                # Tell the ticker to stop.
                ticker_thread.stop()
                ticker_thread.join()

        if ok:
            toret = {}
            for i in range(21):
                if i == 20:
                    raise Exception("close this connection!")

                line = self.__readline()
                if line == "done":
                    break

                argval = line.split("\t")
                toret[argval[0]] = argval[1:]

            return toret
        else:
            problems = []
            for i in range(21):
                if i == 20:
                    raise Exception("close this connection!")

                line = self.__readline()
                if line == "done":
                    break

                problems.append(line)

            raise DropboxCommand.CommandError("\n".join(problems))

    # this is the hotness, auto marshalling
    def __getattr__(self, name):
        try:
            return super(DropboxCommand, self).__getattr__(name)
        except:

            def __spec_command(**kw):
                return self.send_command(str(name), kw)

            self.__setattr__(name, __spec_command)
            return __spec_command


commands = {}
aliases = {}


def command(meth):
    global commands, aliases
    assert meth.__doc__, (
        "All commands need properly formatted docstrings (even %r!!)" % meth
    )
    if hasattr(meth, "im_func"):  # bound method, if we ever have one
        meth = meth.im_func
    commands[meth.__name__] = meth
    meth_aliases = [
        str(alias)
        for alias in aliases.keys()
        if aliases[alias].__name__ == meth.__name__
    ]
    if meth_aliases:
        meth.__doc__ += "\nAliases: %s" % ",".join(meth_aliases)
    return meth


def alias(name):
    def decorator(meth):
        global commands, aliases
        assert name not in commands, "This alias is the name of a command."
        aliases[name] = meth
        return meth

    return decorator


def requires_dropbox_running(meth):
    def newmeth(*n, **kw):
        if is_dropbox_running():
            return meth(*n, **kw)
        else:
            console_print("Dropbox isn't running!")

    newmeth.__name__ = meth.__name__
    newmeth.__doc__ = meth.__doc__
    return newmeth


def start_dropbox():
    if os.access(DROPBOXD_PATH, os.X_OK):
        f = open("/dev/null", "w")

        # Fix indicator icon and menu on Unity environments. (LP: #1559249)
        # Fix indicator icon and menu in Budgie environment. (LP: #1683051)
        new_env = os.environ.copy()
        current_env = os.environ.get("XDG_CURRENT_DESKTOP", "").split(":")
        to_check = ["Unity", "Budgie"]
        if any(word in to_check for word in current_env):
            new_env["XDG_CURRENT_DESKTOP"] = "Unity"

        # we don't reap the child because we're gonna die anyway, let init do it
        subprocess.Popen(
            [DROPBOXD_PATH],
            preexec_fn=os.setsid,
            cwd=os.path.expanduser("~"),
            stderr=sys.stderr,
            stdout=f,
            close_fds=True,
            env=new_env,
        )

        # in seconds
        interval = 0.5
        wait_for = 60
        for _ in range(int(wait_for / interval)):
            if is_dropbox_running():
                return True
            # back off from connect for a while
            time.sleep(interval)

        return False
    else:
        return False


# Extracted and modified from os.cmd.Cmd
def columnize(list, display_list=None, display_width=None):
    if not list:
        console_print("<empty>")
        return

    non_str = [i for i in range(len(list)) if not (isinstance(list[i], str))]
    if non_str:
        raise TypeError(
            "list[i] not a string for i in %s" % ", ".join(map(str, non_str))
        )

    if not display_width:
        d = os.popen("stty size", "r").read().split()
        if d:
            display_width = int(d[1])
        else:
            for item in list:
                console_print(item)
            return

    if not display_list:
        display_list = list

    size = len(list)
    if size == 1:
        console_print(display_list[0])
        return

    for nrows in range(1, len(list)):
        ncols = (size + nrows - 1) // nrows
        colwidths = []
        totwidth = -2
        for col in range(ncols):
            colwidth = 0
            for row in range(nrows):
                i = row + nrows * col
                if i >= size:
                    break
                x = list[i]
                colwidth = max(colwidth, len(x))
            colwidths.append(colwidth)
            totwidth += colwidth + 2
            if totwidth > display_width:
                break
        if totwidth <= display_width:
            break
    else:
        nrows = len(list)
        ncols = 1
        colwidths = [0]
    lines = []
    for row in range(nrows):
        texts = []
        display_texts = []
        for col in range(ncols):
            i = row + nrows * col
            if i >= size:
                x = ""
                y = ""
            else:
                x = list[i]
                y = display_list[i]
            texts.append(x)
            display_texts.append(y)
        while texts and not texts[-1]:
            del texts[-1]
        original_texts = texts[:]
        for col in range(len(texts)):
            texts[col] = texts[col].ljust(colwidths[col])
            texts[col] = texts[col].replace(original_texts[col], display_texts[col])
        line = "  ".join(texts)
        lines.append(line)
    for line in lines:
        console_print(line)


@command
def update(args):
    """download latest version of Dropbox
    dropbox update

    Downloads the latest version of Dropbox. This should not be required
    normally, as Dropbox automatically updates itself.
    """
    download()


@command
@requires_dropbox_running
@alias("stat")
def filestatus(args):
    """get current sync status of one or more files
    dropbox filestatus [-l] [-a] [FILE]...

    Prints the current status of each FILE.

    options:
      -l --list  Prints out information in a format similar to ls. Works best when your console supports color :)
      -a --all   Do not ignore entries starting with "."
    """
    global enc

    oparser = optparse.OptionParser()
    oparser.add_option("-l", "--list", action="store_true", dest="list")
    oparser.add_option("-a", "--all", action="store_true", dest="all")
    (options, args) = oparser.parse_args(args)

    try:
        with closing(DropboxCommand()) as dc:
            if options.list:
                # Listing.

                # Separate directories from files.
                if len(args) == 0:
                    dirs, nondirs = ["."], []
                else:
                    dirs, nondirs = [], []

                    for a in args:
                        try:
                            (dirs if os.path.isdir(a) else nondirs).append(a)
                        except UnicodeDecodeError:
                            continue

                    if len(dirs) == 0 and len(nondirs) == 0:
                        # TODO: why?
                        exit(1)

                dirs.sort(key=methodcaller("lower"))
                nondirs.sort(key=methodcaller("lower"))

                # Gets a string representation for a path.
                def path_to_string(file_path):
                    if not os.path.exists(file_path):
                        path = "%s (File doesn't exist!)" % os.path.basename(file_path)
                        return (path, path)
                    try:
                        status = dc.icon_overlay_file_status(path=file_path).get(
                            "status", [None]
                        )[0]
                    except DropboxCommand.CommandError as e:
                        path = "%s (%s)" % (os.path.basename(file_path), e)
                        return (path, path)

                    env_term = os.environ.get("TERM", "")
                    supports_color = sys.stderr.isatty() and (
                        env_term.startswith("vt")
                        or env_term.startswith("linux")
                        or "xterm" in env_term
                        or "color" in env_term
                    )

                    # TODO: Test when you don't support color.
                    if not supports_color:
                        path = os.path.basename(file_path)
                        return (path, path)

                    if status == "up to date":
                        init, cleanup = "\x1b[32;1m", "\x1b[0m"
                    elif status == "syncing":
                        init, cleanup = "\x1b[36;1m", "\x1b[0m"
                    elif status == "unsyncable":
                        init, cleanup = "\x1b[41;1m", "\x1b[0m"
                    elif status in ("selective sync", "ignored"):
                        # https://help.dropbox.com/files-folders/restore-delete/ignored-files
                        # gray-bar on local ignored files
                        # formerly ignored files were local files under selsync paths
                        # Accept "selective sync", in addition to "ignored" - for compatibility to < v94 of dropbox
                        init, cleanup = "\x1b[37;1m", "\x1b[0m"
                    else:
                        init, cleanup = "", ""

                    path = os.path.basename(file_path)
                    return (path, "%s%s%s" % (init, path, cleanup))

                # Prints a directory.
                def print_directory(name):
                    clean_paths = []
                    formatted_paths = []
                    for subname in sorted(os.listdir(name), key=methodcaller("lower")):
                        if type(subname) != str:
                            continue

                        if not options.all and subname[0] == ".":
                            continue

                        try:
                            clean, formatted = path_to_string(
                                os.path.abspath(os.path.join(name, subname))
                            )
                            clean_paths.append(clean)
                            formatted_paths.append(formatted)
                        except (UnicodeEncodeError, UnicodeDecodeError):
                            continue

                    columnize(clean_paths, formatted_paths)

                try:
                    if len(dirs) == 1 and len(nondirs) == 0:
                        print_directory(dirs[0])
                    else:
                        nondir_formatted_paths = []
                        nondir_clean_paths = []
                        for name in nondirs:
                            try:
                                clean, formatted = path_to_string(os.path.abspath(name))
                                nondir_clean_paths.append(clean)
                                nondir_formatted_paths.append(formatted)
                            except (UnicodeEncodeError, UnicodeDecodeError):
                                continue

                        if nondir_clean_paths:
                            columnize(nondir_clean_paths, nondir_formatted_paths)

                        if len(nondirs) == 0:
                            console_print(dirs[0] + ":")
                            print_directory(dirs[0])
                            dirs = dirs[1:]

                        for name in dirs:
                            console_print()
                            console_print(name + ":")
                            print_directory(name)

                except DropboxCommand.EOFError:
                    console_print("Dropbox daemon stopped.")
                except DropboxCommand.BadConnectionError:
                    console_print("Dropbox isn't responding!")
            else:
                if len(args) == 0:
                    args = [
                        name
                        for name in sorted(os.listdir("."), key=methodcaller("lower"))
                        if type(name) == str
                    ]
                if len(args) == 0:
                    # Bail early if there's nothing to list to avoid crashing on indent below
                    console_print("<empty>")
                    return
                indent = max(len(st) + 1 for st in args)
                for file in args:
                    try:
                        if type(file) is not str:
                            file = file.decode(enc)
                        fp = os.path.abspath(file)
                    except (UnicodeEncodeError, UnicodeDecodeError):
                        continue
                    if not os.path.exists(fp):
                        console_print(
                            "%-*s %s" % (indent, file + ":", "File doesn't exist")
                        )
                        continue

                    try:
                        status = dc.icon_overlay_file_status(path=fp).get(
                            "status", ["unknown"]
                        )[0]
                        console_print("%-*s %s" % (indent, file + ":", status))
                    except DropboxCommand.BadConnectionError:
                        console_print("Dropbox isn't responding!")
                    except DropboxCommand.CommandError as e:
                        console_print("%-*s %s" % (indent, file + ":", e))
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
@requires_dropbox_running
def ls(args):
    """list directory contents with current sync status
    dropbox ls [FILE]...

    This is an alias for filestatus -l
    """
    return filestatus(["-l"] + args)


@command
@requires_dropbox_running
def sharelink(args):
    """get a shared link for a file in your Dropbox
    dropbox sharelink FILE

    Prints out a shared link for FILE.
    """
    if len(args) != 1:
        console_print(sharelink.__doc__, linebreak=False)
        return

    try:
        with closing(DropboxCommand()) as dc:
            try:
                path = os.path.abspath(args[0])
                link = dc.get_shared_link(path=path).get("link", ["No link"])[0]
                console_print(link)
            except DropboxCommand.CommandError as e:
                console_print("Couldn't get shared link: " + str(e))
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
@requires_dropbox_running
def proxy(args):
    """set proxy settings for Dropbox
    dropbox proxy MODE [TYPE] [HOST] [PORT] [USERNAME] [PASSWORD]

    Set proxy settings for Dropbox.

    MODE - one of "none", "auto", "manual"
    TYPE - one of "http", "socks4", "socks5" (only valid with "manual" mode)
    HOST - proxy hostname (only valid with "manual" mode)
    PORT - proxy port (only valid with "manual" mode)
    USERNAME - (optional) proxy username (only valid with "manual" mode)
    PASSWORD - (optional) proxy password (only valid with "manual" mode)
    """
    mode = None
    type_ = None
    if len(args) >= 1:
        mode = args[0].lower()
    if len(args) >= 2:
        type_ = args[1].lower()

    if (
        len(args) == 0
        or mode not in ["none", "auto", "manual"]
        or (mode == "manual" and len(args) not in (4, 6))
        or (mode != "manual" and len(args) != 1)
        or (mode == "manual" and type_ not in ["http", "socks4", "socks5"])
    ):
        # Print help
        console_print(proxy.__doc__, linebreak=False)
        return

    ARGS = ["mode", "type", "host", "port", "username", "password"]

    # Load the args into a dictionary
    kwargs = dict(zip(ARGS, args))

    # Re-set these two because they were coerced to lower case
    kwargs["mode"] = mode
    if type_:
        kwargs["type"] = type_

    try:
        with closing(DropboxCommand()) as dc:
            try:
                dc.set_proxy_settings(**kwargs)
                console_print("set")
            except DropboxCommand.CommandError as e:
                console_print("Couldn't set proxy: " + str(e))
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
@requires_dropbox_running
def throttle(args):
    """set bandwidth limits for Dropbox
    dropbox throttle DOWNLOAD UPLOAD

    Set bandwidth limits for file sync.

    DOWNLOAD - either "unlimited" or a manual limit in KB/s
    UPLOAD - one of "unlimited", "auto", or a manual limit in KB/s
    """
    if len(args) != 2:
        console_print(throttle.__doc__, linebreak=False)
        return

    downlimit = args[0].lower()
    uplimit = args[1].lower()

    download_limit = None
    download_mode = None
    if downlimit == "unlimited":
        download_mode = downlimit
    else:
        try:
            download_limit = int(downlimit)
            download_mode = "manual"
        except ValueError:
            console_print(throttle.__doc__, linebreak=False)
            return

    upload_limit = None
    upload_mode = None
    if uplimit in ["unlimited", "auto"]:
        upload_mode = uplimit
    else:
        try:
            upload_limit = int(uplimit)
            upload_mode = "manual"
        except ValueError:
            console_print(throttle.__doc__, linebreak=False)
            return

    kwargs = {
        "download_mode": download_mode,
        "upload_mode": upload_mode,
    }
    if download_limit:
        kwargs["download_limit"] = str(download_limit)
    if upload_limit:
        kwargs["upload_limit"] = str(upload_limit)

    try:
        with closing(DropboxCommand()) as dc:
            try:
                dc.set_bandwidth_limits(**kwargs)
                console_print("set")
            except DropboxCommand.CommandError as e:
                console_print("Couldn't set bandwidth limits: " + str(e))
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
@requires_dropbox_running
def status(args):
    """get current status of the dropboxd
    dropbox status

    Prints out the current status of the Dropbox daemon.
    """
    if len(args) != 0:
        console_print(status.__doc__, linebreak=False)
        return

    try:
        with closing(DropboxCommand()) as dc:
            try:
                lines = dc.get_dropbox_status()["status"]
                if len(lines) == 0:
                    console_print("Idle")
                else:
                    for line in lines:
                        console_print(line)
                grab_link_url_if_necessary()
            except KeyError:
                console_print("Couldn't get status: daemon isn't responding")
            except DropboxCommand.CommandError as e:
                console_print("Couldn't get status: " + str(e))
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
def running(argv):
    """return whether Dropbox is running
    dropbox running

    Returns 1 if running, and 0 if not running.
    """
    return int(is_dropbox_running())


@command
@requires_dropbox_running
def stop(args):
    """stop dropboxd
    dropbox stop

    Stops the Dropbox daemon.
    """
    try:
        with closing(DropboxCommand()) as dc:
            try:
                dc.tray_action_hard_exit()
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


# returns true if link is necessary
def grab_link_url_if_necessary():
    try:
        with closing(DropboxCommand()) as dc:
            try:
                link_url = dc.needs_link().get("link_url", None)
                if link_url is not None:
                    console_print(
                        "To link this computer to a Dropbox account, visit the following url:\n%s"
                        % link_url[0]
                    )
                    return True
                else:
                    return False
            except DropboxCommand.CommandError:
                pass
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")
            except DropboxCommand.EOFError:
                console_print("Dropbox daemon stopped.")
    except DropboxCommand.CouldntConnectError:
        console_print("Dropbox isn't running!")


@command
@requires_dropbox_running
def lansync(argv):
    """enables or disables LAN sync
    dropbox lansync [y/n]

    options:
      y  Dropbox will use LAN sync (default)
      n  Dropbox will not use LAN sync
    """
    if len(argv) != 1:
        console_print(lansync.__doc__, linebreak=False)
        return

    s = argv[0].lower()
    if s.startswith("y") or s.startswith("-y"):
        should_lansync = True
    elif s.startswith("n") or s.startswith("-n"):
        should_lansync = False
    else:
        should_lansync = None

    if should_lansync is None:
        console_print(lansync.__doc__, linebreak=False)
    else:
        with closing(DropboxCommand()) as dc:
            try:
                dc.set_lan_sync(lansync="enabled" if should_lansync else "disabled")
            except DropboxCommand.BadConnectionError:
                console_print("Dropbox isn't responding!")


@command
@requires_dropbox_running
def exclude(args):
    """ignores/excludes a directory from syncing
    dropbox exclude [list]
    dropbox exclude add [DIRECTORY] [DIRECTORY] ...
    dropbox exclude remove [DIRECTORY] [DIRECTORY] ...

    "list" prints a list of directories currently excluded from syncing.
    "add" adds one or more directories to the exclusion list, then
    resynchronizes Dropbox.
    "remove" removes one or more directories from the exclusion list, then
    resynchronizes Dropbox.

    With no arguments, executes "list".

    Any specified path must be within Dropbox.
    """
    if len(args) == 0:
        try:
            with closing(DropboxCommand()) as dc:
                try:
                    lines = [
                        relpath(path) for path in dc.get_ignore_set()["ignore_set"]
                    ]
                    lines.sort()
                    if len(lines) == 0:
                        console_print("No directories are being ignored.")
                    else:
                        console_print("Excluded: ")
                        for line in lines:
                            console_print(str(line))
                except KeyError:
                    console_print("Couldn't get ignore set: daemon isn't responding")
                except DropboxCommand.CommandError as e:
                    if e.args[0].startswith("No command exists by that name"):
                        console_print(
                            "This version of the client does not support this command."
                        )
                    else:
                        console_print("Couldn't get ignore set: " + str(e))
                except DropboxCommand.BadConnectionError:
                    console_print("Dropbox isn't responding!")
                except DropboxCommand.EOFError:
                    console_print("Dropbox daemon stopped.")
        except DropboxCommand.CouldntConnectError:
            console_print("Dropbox isn't running!")
    elif len(args) == 1 and args[0] == "list":
        exclude([])
    elif len(args) >= 2:
        sub_command = args[0]
        paths = args[1:]
        absolute_paths = [os.path.abspath(path) for path in paths]
        if sub_command == "add":
            try:
                with closing(DropboxCommand(timeout=None)) as dc:
                    try:
                        result = dc.ignore_set_add(paths=absolute_paths)
                        if result["ignored"]:
                            console_print("Excluded: ")
                            lines = [relpath(path) for path in result["ignored"]]
                            for line in lines:
                                console_print(str(line))
                    except KeyError:
                        console_print(
                            "Couldn't add ignore path: daemon isn't responding"
                        )
                    except DropboxCommand.CommandError as e:
                        if e.args[0].startswith("No command exists by that name"):
                            console_print(
                                "This version of the client does not support this command."
                            )
                        else:
                            console_print("Couldn't get ignore set: " + str(e))
                    except DropboxCommand.BadConnectionError as e:
                        console_print("Dropbox isn't responding! [%s]" % e)
                    except DropboxCommand.EOFError:
                        console_print("Dropbox daemon stopped.")
            except DropboxCommand.CouldntConnectError:
                console_print("Dropbox isn't running!")
        elif sub_command == "remove":
            try:
                with closing(DropboxCommand(timeout=None)) as dc:
                    try:
                        result = dc.ignore_set_remove(paths=absolute_paths)
                        if result["removed"]:
                            console_print("No longer excluded: ")
                            lines = [relpath(path) for path in result["removed"]]
                            for line in lines:
                                console_print(str(line))
                    except KeyError:
                        console_print(
                            "Couldn't remove ignore path: daemon isn't responding"
                        )
                    except DropboxCommand.CommandError as e:
                        if e.args[0].startswith("No command exists by that name"):
                            console_print(
                                "This version of the client does not support this command."
                            )
                        else:
                            console_print("Couldn't get ignore set: " + str(e))
                    except DropboxCommand.BadConnectionError as e:
                        console_print("Dropbox isn't responding! [%s]" % e)
                    except DropboxCommand.EOFError:
                        console_print("Dropbox daemon stopped.")
            except DropboxCommand.CouldntConnectError:
                console_print("Dropbox isn't running!")
        else:
            console_print(exclude.__doc__, linebreak=False)
            return
    else:
        console_print(exclude.__doc__, linebreak=False)
        return


@command
def start(argv):
    """start dropboxd
    dropbox start [-i]

    Starts the Dropbox daemon, dropboxd. If dropboxd is already running,
    this will do nothing.

    options:
      -i --install  auto install dropboxd if not available on the system
    """

    should_install = "-i" in argv or "--install" in argv

    # first check if dropbox is already running
    if is_dropbox_running():
        if not grab_link_url_if_necessary():
            console_print("Dropbox is already running!")
        return

    console_print("Starting Dropbox...", linebreak=False)
    console_flush()
    if not start_dropbox():
        if not should_install:
            console_print()
            console_print("The Dropbox daemon is not installed!")
            console_print('Run "dropbox start -i" to install the daemon')
            return

        # install dropbox!!!
        try:
            download()
        except:
            traceback.print_exc()
        else:
            if GUI_AVAILABLE:
                start_dropbox()
                console_print("Done!")
            else:
                if start_dropbox():
                    if not grab_link_url_if_necessary():
                        console_print("Done!")
    else:
        if not grab_link_url_if_necessary():
            console_print("Done!")


def can_reroll_autostart():
    return ".config" in os.listdir(os.path.expanduser("~"))


def reroll_autostart(should_autostart):
    home_dir = os.path.expanduser("~")
    contents = os.listdir(home_dir)

    # UBUNTU
    if ".config" in contents:
        autostart_dir = os.path.join(home_dir, ".config", "autostart")
        autostart_link = os.path.join(autostart_dir, "dropbox.desktop")
        if should_autostart:
            if os.path.exists(DESKTOP_FILE):
                if not os.path.exists(autostart_dir):
                    os.makedirs(autostart_dir)
                shutil.copyfile(DESKTOP_FILE, autostart_link)
        elif os.path.exists(autostart_link):
            os.remove(autostart_link)


@command
def autostart(argv):
    """automatically start Dropbox at login
    dropbox autostart [y/n]

    options:
      n  Dropbox will not start automatically at login
      y  Dropbox will start automatically at login (default)

    Note: May only work on current Ubuntu distributions.
    """
    if len(argv) != 1:
        console_print("".join(autostart.__doc__.split("\n", 1)[1:]))
        return

    s = argv[0].lower()
    if s.startswith("y") or s.startswith("-y"):
        should_autostart = True
    elif s.startswith("n") or s.startswith("-n"):
        should_autostart = False
    else:
        should_autostart = None

    if should_autostart is None:
        console_print(autostart.__doc__, linebreak=False)
    else:
        reroll_autostart(should_autostart)


@command
def version(argv):
    """print version information for Dropbox
    dropbox version

    Prints the version information for the Dropbox proprietary daemon, if
    it's installed, and the Dropbox command-line interface.
    """
    dropbox_daemon_version = "Not installed"
    try:
        with open(os.path.join(DROPBOX_DIST_PATH, "VERSION")) as f:
            dropbox_daemon_version = f.read().strip()
    except OSError:
        pass

    console_print("Dropbox daemon version: %s" % dropbox_daemon_version)
    console_print("Dropbox command-line interface version: 2026.03.20")


@command
def help(argv):
    """provide help
    dropbox help [COMMAND]

    With no arguments, print a list of commands and a short description of
    each. With a command, print descriptive help on how to use the
    command.
    """
    if not argv:
        return usage()
    for command in commands:
        if command == argv[0]:
            console_print(commands[command].__doc__.split("\n", 1)[1].strip())
            return
    for alias in aliases:
        if alias == argv[0]:
            console_print(aliases[alias].__doc__.split("\n", 1)[1].strip())
            return
    console_print("unknown command '%s'" % argv[0], f=sys.stderr)


def usage():
    console_print("Dropbox command-line interface\n")
    console_print("commands:\n")
    console_print(
        "Note: use dropbox help <command> to view usage for a specific command.\n"
    )
    out = []
    for command in commands:
        out.append((command, commands[command].__doc__.splitlines()[0]))
    out.sort(key=lambda x: x[0])
    spacing = max(len(o[0]) + 3 for o in out)
    for o in out:
        console_print(" %-*s%s" % (spacing, o[0], o[1]))


def main(argv):
    global commands

    # now we need to find out if one of the commands are in the
    # argv list, and if so split the list at the point to
    # separate the argv list at that point
    cut = None
    for i in range(len(argv)):
        if argv[i] in commands or argv[i] in aliases:
            cut = i
            break

    if cut == None:
        usage()
        os._exit(0)
        return

    # lol no options for now
    globaloptionparser = optparse.OptionParser()
    globaloptionparser.parse_args(argv[0:i])

    # now dispatch and run
    result = None
    if argv[i] in commands:
        result = commands[argv[i]](argv[i + 1 :])
    elif argv[i] in aliases:
        result = aliases[argv[i]](argv[i + 1 :])

    # flush, in case output is rerouted to a file.
    console_flush()

    # done
    return result


if __name__ == "__main__":
    ret = main(sys.argv)
    if ret is not None:
        sys.exit(ret)
