summaryrefslogtreecommitdiff
path: root/dropbox.in
diff options
context:
space:
mode:
authorBenjamin Valentin <[email protected]>2012-03-27 03:03:55 +0200
committerBenjamin Valentin <[email protected]>2012-03-27 03:03:55 +0200
commite214b332395adbc77ad596218895a4834cad434d (patch)
tree52df3b5c109d78f18e2d48f1b34336561e939c93 /dropbox.in
parentd367ab28270220c1378d2a7ca9bda4a9012c76cb (diff)
downloadcaja-dropbox-e214b332395adbc77ad596218895a4834cad434d.tar.bz2
caja-dropbox-e214b332395adbc77ad596218895a4834cad434d.tar.xz
import nautilus dropbox 0.7.1
Diffstat (limited to 'dropbox.in')
-rwxr-xr-xdropbox.in1362
1 files changed, 1362 insertions, 0 deletions
diff --git a/dropbox.in b/dropbox.in
new file mode 100755
index 0000000..ee357ec
--- /dev/null
+++ b/dropbox.in
@@ -0,0 +1,1362 @@
+#!/usr/bin/python
+#
+# Copyright 2008 Evenflow, Inc.
+#
+# dropbox
+# Dropbox frontend script
+# This file is part of nautilus-dropbox @PACKAGE_VERSION@.
+#
+# 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 errno
+import fcntl
+import locale
+import optparse
+import os
+import platform
+import shutil
+import socket
+import StringIO
+import subprocess
+import sys
+import tarfile
+import tempfile
+import threading
+import time
+import urllib
+
+try:
+ import gpgme
+except ImportError:
+ gpgme = None
+
+from contextlib import closing, contextmanager
+from posixpath import curdir, sep, pardir, join, abspath, commonprefix
+
+INFO = u"Dropbox is the easiest way to share and store your files online. Want to learn more? Head to"
+LINK = u"http://www.dropbox.com/"
+WARNING = u"In order to use Dropbox, you must download the proprietary daemon."
+GPG_WARNING = u"Note: python-gpgme is not installed, we will not be able to verify binary signatures."
+
+DOWNLOADING = u"Downloading Dropbox... %d%%"
+UNPACKING = u"Unpacking Dropbox... %d%%"
+
+PARENT_DIR = os.path.expanduser("~")
+DROPBOXD_PATH = "%s/.dropbox-dist/dropboxd" % PARENT_DIR
+DESKTOP_FILE = u"@DESKTOP_FILE_DIR@/dropbox.desktop"
+
+enc = locale.getpreferredencoding()
+
+# Available from http://linux.dropbox.com/fedora/rpm-public-key.asc
+DROPBOX_PUBLIC_KEY = """
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: SKS 1.1.0
+
+mQENBEt0ibEBCACv4hZRPqwtpU6z8+BB5YZU1a3yjEvg2W68+a6hEwxtCa2U++4dzQ+7EqaU
+q5ybQnwtbDdpFpsOi9x31J+PCpufPUfIG694/0rlEpmzl2GWzY8NqfdBFGGm/SPSSwvKbeNc
+FMRLu5neo7W9kwvfMbGjHmvUbzBUVpCVKD0OEEf1q/Ii0Qcekx9CMoLvWq7ZwNHEbNnij7ec
+nvwNlE2MxNsOSJj+hwZGK+tM19kuYGSKw4b5mR8IyThlgiSLIfpSBh1n2KX+TDdk9GR+57TY
+vlRu6nTPu98P05IlrrCP+KF0hYZYOaMvQs9Rmc09tc/eoQlN0kkaBWw9Rv/dvLVc0aUXABEB
+AAG0MURyb3Bib3ggQXV0b21hdGljIFNpZ25pbmcgS2V5IDxsaW51eEBkcm9wYm94LmNvbT6J
+ATYEEwECACAFAkt0ibECGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRD8kYszUESRLi/z
+B/wMscEa15rS+0mIpsORknD7kawKwyda+LHdtZc0hD/73QGFINR2P23UTol/R4nyAFEuYNsF
+0C4IAD6y4pL49eZ72IktPrr4H27Q9eXhNZfJhD7BvQMBx75L0F5gSQwuC7GdYNlwSlCD0AAh
+Qbi70VBwzeIgITBkMQcJIhLvllYo/AKD7Gv9huy4RLaIoSeofp+2Q0zUHNPl/7zymOqu+5Ox
+e1ltuJT/kd/8hU+N5WNxJTSaOK0sF1/wWFM6rWd6XQUP03VyNosAevX5tBo++iD1WY2/lFVU
+JkvAvge2WFk3c6tAwZT/tKxspFy4M/tNbDKeyvr685XKJw9ei6GcOGHD
+=5rWG
+-----END PGP PUBLIC KEY BLOCK-----
+"""
+
+# Futures
+
+def methodcaller(name, *args, **kwargs):
+ def caller(obj):
+ return getattr(obj, name)(*args, **kwargs)
+ return caller
+
+def relpath(path, start=curdir):
+ """Return a relative version of a path"""
+
+ if not path:
+ raise ValueError("no path specified")
+
+ if type(start) is unicode:
+ start_list = unicode_abspath(start).split(sep)
+ else:
+ start_list = abspath(start).split(sep)
+
+ if type(path) is unicode:
+ path_list = unicode_abspath(path).split(sep)
+ else:
+ path_list = abspath(path).split(sep)
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len(commonprefix([start_list, path_list]))
+
+ rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return curdir
+ return join(*rel_list)
+
+# End Futures
+
+
+def console_print(st=u"", f=sys.stdout, linebreak=True):
+ global enc
+ assert type(st) is unicode
+ f.write(st.encode(enc))
+ 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(u" [y/n] ", linebreak=False)
+ console_flush()
+ text = raw_input()
+ if text.lower().startswith("y"):
+ return True
+ elif text.lower().startswith("n"):
+ return False
+ else:
+ console_print(u"Sorry, I didn't understand that. Please type yes or no.")
+
+def plat():
+ if sys.platform.lower().startswith('linux'):
+ arch = platform.machine()
+ if (arch[0] == 'i' and
+ arch[1].isdigit() and
+ arch[2:4] == '86'):
+ plat = "x86"
+ elif 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
+
+def unicode_abspath(path):
+ global enc
+ assert type(path) is unicode
+ # shouldn't pass unicode to this craphead, it appends with os.getcwd() which is always a str
+ return os.path.abspath(path.encode(sys.getfilesystemencoding())).decode(sys.getfilesystemencoding())
+
+@contextmanager
+def gpgme_context(keys):
+ gpg_conf_contents = ''
+ _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()
+ ctx = gpgme.Context()
+
+ loaded = []
+ for key_file in keys:
+ 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)
+
+def verify_signature(key_file, sig_file, plain_file):
+ with gpgme_context([key_file]) as ctx:
+ sigs = ctx.verify(sig_file, plain_file, None)
+ return sigs[0].status == None
+
+def download_file_chunk(socket, buf, size):
+ progress = 0
+ with closing(socket) as f:
+ while True:
+ try:
+ chunk = os.read(f.fileno(), 4096)
+ progress += len(chunk)
+ buf.write(chunk)
+ yield (progress, True)
+ if progress == size:
+ break
+ except OSError, e:
+ if hasattr(e, 'errno') and e.errno == errno.EAGAIN:
+ # nothing left to read
+ yield (progress, False)
+ else:
+ raise
+
+def download_uri_to_buffer(uri):
+ try:
+ socket = urllib.urlopen(uri)
+ except IOError:
+ FatalVisibleError("Trouble connecting to Dropbox servers. Maybe your internet connection is down, or you need to set your http_proxy environment variable.")
+
+ fcntl.fcntl(socket, fcntl.F_SETFL, os.O_NONBLOCK)
+ size = int(socket.info()['content-length'])
+
+ buf = StringIO.StringIO()
+ download_chunk = download_file_chunk(socket, buf, size)
+
+ for _ in download_chunk:
+ pass
+
+ buf.seek(0)
+ return buf
+
+# This sets a custom User-Agent
+class DropboxURLopener(urllib.FancyURLopener):
+ version = "DropboxLinuxDownloader/@PACKAGE_VERSION@"
+urllib._urlopener = DropboxURLopener()
+
+class DownloadState(object):
+ def __init__(self):
+ try:
+ self.socket = urllib.urlopen("http://www.dropbox.com/download?plat=%s" % plat())
+ except IOError:
+ FatalVisibleError("Trouble connecting to Dropbox servers. Maybe your internet connection is down, or you need to set your http_proxy environment variable")
+
+ fcntl.fcntl(self.socket, fcntl.F_SETFL, os.O_NONBLOCK)
+ self.size = int(self.socket.info()['content-length'])
+
+ self.local_file = StringIO.StringIO()
+ self.download_chunk = download_file_chunk(self.socket, self.local_file, self.size)
+
+ def copy_data(self):
+ return self.download_chunk
+
+ def unpack(self):
+ # download signature
+ signature = download_uri_to_buffer("http://www.dropbox.com/download?plat=%s&signature=1" % plat())
+
+ self.local_file.seek(0)
+ if gpgme:
+ if not verify_signature(StringIO.StringIO(DROPBOX_PUBLIC_KEY), signature, self.local_file):
+ FatalVisibleError("Downloaded binary does not match Dropbox signature, aborting install.")
+
+ 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()):
+ 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 load_serialized_images():
+ global box_logo_pixbuf, window_icon
+ import gtk
+ box_logo_pixbuf = @IMAGEDATA64@
+ window_icon = @IMAGEDATA16@
+
+GUI_AVAILABLE = os.environ.get("DISPLAY", '')
+
+if GUI_AVAILABLE:
+ def download():
+ import pygtk
+ pygtk.require("2.0")
+ import gtk
+ import gobject
+ import pango
+ import webbrowser
+
+ load_serialized_images()
+
+ global FatalVisibleError
+ def FatalVisibleError(s):
+ error = gtk.MessageDialog(parent = None,
+ flags = gtk.DIALOG_MODAL,
+ type = gtk.MESSAGE_ERROR,
+ buttons = gtk.BUTTONS_OK,
+ message_format = s)
+ error.set_title("Error")
+ error.run()
+ gtk.main_quit()
+ sys.exit(-1)
+
+ def gtk_flush_events():
+ while gtk.events_pending():
+ gtk.main_iteration()
+
+ 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.watch:
+ gobject.source_remove(self.watch)
+ 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.one_chunk = self.download.copy_data()
+ self.watch = gobject.io_add_watch(self.download.socket,
+ gobject.IO_IN |
+ gobject.IO_PRI |
+ gobject.IO_ERR |
+ gobject.IO_HUP,
+ self.handle_data_waiting)
+ self.label.hide()
+ self.dont_show_again_align.hide()
+ self.progress.show()
+
+ def update_progress(self, text, fraction):
+ self.progress.set_text(text % int(fraction*100))
+ self.progress.set_fraction(fraction)
+ gtk_flush_events()
+
+ def handle_data_waiting(self, fd, condition):
+ if condition == gobject.IO_HUP:
+ FatalVisibleError("Connection to server unexpectedly closed.")
+ elif condition == gobject.IO_ERR:
+ FatalVisibleError("Unexpected error occurred with download.")
+ try:
+ while True:
+ progress, status = self.one_chunk.next()
+ if not status:
+ break
+ self.update_progress(DOWNLOADING, float(progress)/self.download.size)
+ except StopIteration:
+ self.update_progress(DOWNLOADING, 1.0)
+ self.unpack_dropbox()
+ return False
+ else:
+ self.update_progress(DOWNLOADING, float(progress)/self.download.size)
+ return True
+
+ def unpack_dropbox(self):
+ one_member = self.download.unpack()
+ try:
+ while True:
+ name, i, total = one_member.next()
+ self.update_progress(UNPACKING, float(i)/total)
+ except StopIteration:
+ self.update_progress(UNPACKING, 1.0)
+ gtk.main_quit()
+
+ 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))[0]
+ link_index = layout.get_text().find(LINK)
+ if index >= link_index and index < link_index+len(LINK):
+ self.hovering = True
+ self.label_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+ else:
+ self.hovering = False
+ self.label_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
+
+
+ def __init__(self):
+ super(DownloadDialog, self).__init__(parent = None,
+ title = "Dropbox Installation")
+
+ self.download = None
+ self.watch = None
+ self.hovering = False
+ self.clicked_link = False
+ self.user_cancelled = False
+
+ self.ok = ok = gtk.Button(stock=gtk.STOCK_OK)
+ ok.connect('clicked', self.handle_ok)
+ self.action_area.add(ok)
+ ok.show()
+
+ cancel = gtk.Button(stock=gtk.STOCK_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.label = gtk.Label()
+ GPG_WARNING_MSG = (u"\n\n" + GPG_WARNING) if not gpgme else u""
+ 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(gtk.gdk.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)
+ self.hbox.pack_start(self.label_box, False, False)
+ self.hbox.pack_start(self.progress, False, False)
+ self.hbox.show()
+
+ self.vbox.add(self.hbox)
+
+ try:
+ if can_reroll_autostart():
+ dont_show_again = gtk.CheckButton("_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)
+ hbox.show()
+
+ self.vbox.add(hbox)
+
+ self.set_resizable(False)
+ except:
+ import traceback
+ 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(u"\nError: %s" % s, f=sys.stderr)
+ sys.exit(-1)
+
+
+ ESC = "\x1b"
+ save = ESC+"7"
+ unsave = ESC+"8"
+ clear = ESC+"[2J"
+ 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(u"%s %s\n" % (INFO, LINK))
+ GPG_WARNING_MSG = (u"\n%s" % GPG_WARNING) if not gpgme else u""
+
+ if not yes_no_question("%s%s" % (WARNING, GPG_WARNING_MSG)):
+ return
+
+ download = DownloadState()
+ one_chunk = download.copy_data()
+
+ try:
+ while True:
+ progress = one_chunk.next()[0]
+ setprogress(DOWNLOADING, float(progress)/download.size)
+ except StopIteration:
+ setprogress(DOWNLOADING, 1.0)
+ console_print()
+ write(save)
+
+ one_member = download.unpack()
+
+ try:
+ while True:
+ name, i, total = one_member.next()
+ setprogress(UNPACKING, float(i)/total)
+ except StopIteration:
+ setprogress(UNPACKING, 1.0)
+
+ 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.isSet(): 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(u'~/.dropbox/command_socket'))
+ except socket.error, e:
+ raise DropboxCommand.CouldntConnectError()
+ self.f = self.s.makefile("r+", 4096)
+
+ def close(self):
+ self.f.close()
+ self.s.close()
+
+ def __readline(self):
+ try:
+ toret = self.f.readline().decode('utf8').rstrip(u"\n")
+ except socket.error, e:
+ 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.encode('utf8'))
+ self.f.write(u"\n".encode('utf8'))
+ self.f.writelines((u"\t".join([k] + (list(v)
+ if hasattr(v, '__iter__') else
+ [v])) + u"\n").encode('utf8')
+ for k,v in args.iteritems())
+ self.f.write(u"done\n".encode('utf8'))
+
+ self.f.flush()
+
+ # Start a ticker
+ ticker_thread = CommandTicker()
+ ticker_thread.start()
+
+ # This is the potentially long-running call.
+ try:
+ ok = self.__readline() == u"ok"
+ except KeyboardInterrupt:
+ raise DropboxCommand.BadConnectionError("Keyboard interruption detected")
+ finally:
+ # Tell the ticker to stop.
+ ticker_thread.stop()
+ ticker_thread.join()
+
+ if ok:
+ toret = {}
+ for i in range(21):
+ if i == 20:
+ raise Exception(u"close this connection!")
+
+ line = self.__readline()
+ if line == u"done":
+ break
+
+ argval = line.split(u"\t")
+ toret[argval[0]] = argval[1:]
+
+ return toret
+ else:
+ problems = []
+ for i in range(21):
+ if i == 20:
+ raise Exception(u"close this connection!")
+
+ line = self.__readline()
+ if line == u"done":
+ break
+
+ problems.append(line)
+
+ raise DropboxCommand.CommandError(u"\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(unicode(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.func_name] = meth
+ meth_aliases = [unicode(alias) for alias in aliases.iterkeys() if aliases[alias].func_name == meth.func_name]
+ if meth_aliases:
+ meth.__doc__ += u"\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(u"Dropbox isn't running!")
+ newmeth.func_name = meth.func_name
+ newmeth.__doc__ = meth.__doc__
+ return newmeth
+
+def start_dropbox():
+ db_path = os.path.expanduser(u"~/.dropbox-dist/dropboxd").encode(sys.getfilesystemencoding())
+ if os.access(db_path, os.X_OK):
+ f = open("/dev/null", "w")
+ # we don't reap the child because we're gonna die anyway, let init do it
+ a = subprocess.Popen([db_path], preexec_fn=os.setsid, cwd=os.path.expanduser("~"),
+ stderr=sys.stderr, stdout=f, close_fds=True)
+
+ # in seconds
+ interval = 0.5
+ wait_for = 60
+ for i in xrange(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(u"<empty>")
+ return
+
+ non_unicode = [i for i in range(len(list)) if not (isinstance(list[i], unicode))]
+ if non_unicode:
+ raise TypeError, ("list[i] not a string for i in %s" %
+ ", ".join(map(unicode, non_unicode)))
+
+ 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])
+ line = u"%s" % " ".join(texts)
+ for i, text in enumerate(original_texts):
+ line = line.replace(text, display_texts[i])
+ lines.append(line)
+ for line in lines:
+ console_print(line)
+
+@command
+@requires_dropbox_running
+@alias('stat')
+def filestatus(args):
+ u"""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 = [u"."], []
+ else:
+ dirs, nondirs = [], []
+
+ for a in args:
+ try:
+ (dirs if os.path.isdir(a) else nondirs).append(a.decode(enc))
+ 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 = u"%s (File doesn't exist!)" % os.path.basename(file_path)
+ return (path, path)
+ try:
+ status = dc.icon_overlay_file_status(path=file_path).get(u'status', [None])[0]
+ except DropboxCommand.CommandError, e:
+ path = u"%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 == u"up to date":
+ init, cleanup = "\x1b[32;1m", "\x1b[0m"
+ elif status == u"syncing":
+ init, cleanup = "\x1b[36;1m", "\x1b[0m"
+ elif status == u"unsyncable":
+ init, cleanup = "\x1b[41;1m", "\x1b[0m"
+ elif status == u"selsync":
+ init, cleanup = "\x1b[37;1m", "\x1b[0m"
+ else:
+ init, cleanup = '', ''
+
+ path = os.path.basename(file_path)
+ return (path, u"%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) != unicode:
+ continue
+
+ if not options.all and subname[0] == u'.':
+ continue
+
+ try:
+ clean, formatted = path_to_string(unicode_abspath(os.path.join(name, subname)))
+ clean_paths.append(clean)
+ formatted_paths.append(formatted)
+ except (UnicodeEncodeError, UnicodeDecodeError), e:
+ 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(unicode_abspath(name))
+ nondir_clean_paths.append(clean)
+ nondir_formatted_paths.append(formatted)
+ except (UnicodeEncodeError, UnicodeDecodeError), e:
+ continue
+
+ if nondir_clean_paths:
+ columnize(nondir_clean_paths, nondir_formatted_paths)
+
+ if len(nondirs) == 0:
+ console_print(dirs[0] + u":")
+ print_directory(dirs[0])
+ dirs = dirs[1:]
+
+ for name in dirs:
+ console_print()
+ console_print(name + u":")
+ print_directory(name)
+
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ else:
+ if len(args) == 0:
+ args = [name for name in sorted(os.listdir(u"."), key=methodcaller('lower')) if type(name) == unicode]
+ indent = max(len(st)+1 for st in args)
+ for file in args:
+
+ try:
+ if type(file) is not unicode:
+ file = file.decode(enc)
+ fp = unicode_abspath(file)
+ except (UnicodeEncodeError, UnicodeDecodeError), e:
+ continue
+ if not os.path.exists(fp):
+ console_print(u"%-*s %s" % \
+ (indent, file+':', "File doesn't exist"))
+ continue
+
+ try:
+ status = dc.icon_overlay_file_status(path=fp).get(u'status', [u'unknown'])[0]
+ console_print(u"%-*s %s" % (indent, file+':', status))
+ except DropboxCommand.CommandError, e:
+ console_print(u"%-*s %s" % (indent, file+':', e))
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+
+@command
+@requires_dropbox_running
+def ls(args):
+ u"""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 puburl(args):
+ u"""get public url of a file in your dropbox
+dropbox puburl FILE
+
+Prints out a public url for FILE.
+"""
+ if len(args) != 1:
+ console_print(puburl.__doc__,linebreak=False)
+ return
+
+ try:
+ with closing(DropboxCommand()) as dc:
+ try:
+ console_print(dc.get_public_link(path=unicode_abspath(args[0].decode(sys.getfilesystemencoding()))).get(u'link', [u'No Link'])[0])
+ except DropboxCommand.CommandError, e:
+ console_print(u"Couldn't get public url: " + str(e))
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+
+@command
+@requires_dropbox_running
+def status(args):
+ u"""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()[u'status']
+ if len(lines) == 0:
+ console_print(u'Idle')
+ else:
+ for line in lines:
+ console_print(line)
+ except KeyError:
+ console_print(u"Couldn't get status: daemon isn't responding")
+ except DropboxCommand.CommandError, e:
+ console_print(u"Couldn't get status: " + str(e))
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+
+@command
+def running(argv):
+ u"""return whether dropbox is running
+dropbox running
+
+Returns 1 if running 0 if not running.
+"""
+ return int(is_dropbox_running())
+
+@command
+@requires_dropbox_running
+def stop(args):
+ u"""stop dropboxd
+dropbox stop
+
+Stops the dropbox daemon.
+"""
+ try:
+ with closing(DropboxCommand()) as dc:
+ try:
+ dc.tray_action_hard_exit()
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"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(u"link_url", None)
+ if link_url is not None:
+ console_print(u"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, e:
+ pass
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+
+@command
+@requires_dropbox_running
+def lansync(argv):
+ u"""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:
+ dc.set_lan_sync(lansync='enabled' if should_lansync else 'disabled')
+
+
+@command
+@requires_dropbox_running
+def exclude(args):
+ u"""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()[u'ignore_set']]
+ lines.sort()
+ if len(lines) == 0:
+ console_print(u'No directories are being ignored.')
+ else:
+ console_print(u'Excluded: ')
+ for line in lines:
+ console_print(unicode(line))
+ except KeyError:
+ console_print(u"Couldn't get ignore set: daemon isn't responding")
+ except DropboxCommand.CommandError, e:
+ if e.args[0].startswith(u"No command exists by that name"):
+ console_print(u"This version of the client does not support this command.")
+ else:
+ console_print(u"Couldn't get ignore set: " + str(e))
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding!")
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+ elif len(args) == 1 and args[0] == u"list":
+ exclude([])
+ elif len(args) >= 2:
+ sub_command = args[0]
+ paths = args[1:]
+ absolute_paths = [unicode_abspath(path.decode(sys.getfilesystemencoding())) for path in paths]
+ if sub_command == u"add":
+ try:
+ with closing(DropboxCommand(timeout=None)) as dc:
+ try:
+ result = dc.ignore_set_add(paths=absolute_paths)
+ if result[u"ignored"]:
+ console_print(u"Excluded: ")
+ lines = [relpath(path) for path in result[u"ignored"]]
+ for line in lines:
+ console_print(unicode(line))
+ except KeyError:
+ console_print(u"Couldn't add ignore path: daemon isn't responding")
+ except DropboxCommand.CommandError, e:
+ if e.args[0].startswith(u"No command exists by that name"):
+ console_print(u"This version of the client does not support this command.")
+ else:
+ console_print(u"Couldn't get ignore set: " + str(e))
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding! [%s]" % e)
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+ elif sub_command == u"remove":
+ try:
+ with closing(DropboxCommand(timeout=None)) as dc:
+ try:
+ result = dc.ignore_set_remove(paths=absolute_paths)
+ if result[u"removed"]:
+ console_print(u"No longer excluded: ")
+ lines = [relpath(path) for path in result[u"removed"]]
+ for line in lines:
+ console_print(unicode(line))
+ except KeyError:
+ console_print(u"Couldn't remove ignore path: daemon isn't responding")
+ except DropboxCommand.CommandError, e:
+ if e.args[0].startswith(u"No command exists by that name"):
+ console_print(u"This version of the client does not support this command.")
+ else:
+ console_print(u"Couldn't get ignore set: " + str(e))
+ except DropboxCommand.BadConnectionError, e:
+ console_print(u"Dropbox isn't responding! [%s]" % e)
+ except DropboxCommand.EOFError:
+ console_print(u"Dropbox daemon stopped.")
+ except DropboxCommand.CouldntConnectError, e:
+ console_print(u"Dropbox isn't running!")
+ else:
+ console_print(exclude.__doc__, linebreak=False)
+ return
+ else:
+ console_print(exclude.__doc__, linebreak=False)
+ return
+
+@command
+def start(argv):
+ u"""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(u"Dropbox is already running!")
+ return
+
+ console_print(u"Starting Dropbox...", linebreak=False)
+ console_flush()
+ if not start_dropbox():
+ if not should_install:
+ console_print()
+ console_print(u"The Dropbox daemon is not installed!")
+ console_print(u"Run \"dropbox start -i\" to install the daemon")
+ return
+
+ # install dropbox!!!
+ try:
+ download()
+ except:
+ pass
+ else:
+ if GUI_AVAILABLE:
+ start_dropbox()
+ console_print(u"Done!")
+ else:
+ if start_dropbox():
+ if not grab_link_url_if_necessary():
+ console_print(u"Done!")
+ else:
+ if not grab_link_url_if_necessary():
+ console_print(u"Done!")
+
+
+def can_reroll_autostart():
+ return u".config" in os.listdir(os.path.expanduser(u'~'))
+
+def reroll_autostart(should_autostart):
+ home_dir = os.path.expanduser(u'~')
+ contents = os.listdir(home_dir)
+
+ # UBUNTU
+ if u".config" in contents:
+ autostart_dir = os.path.join(home_dir, u".config", u"autostart")
+ autostart_link = os.path.join(autostart_dir, u"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):
+ u"""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:]).decode('ascii'))
+ 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 help(argv):
+ u"""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(argv)
+ for command in commands:
+ if command == argv[0]:
+ console_print(commands[command].__doc__.split('\n', 1)[1].decode('ascii'))
+ return
+ for alias in aliases:
+ if alias == argv[0]:
+ console_print(aliases[alias].__doc__.split('\n', 1)[1].decode('ascii'))
+ return
+ console_print(u"unknown command '%s'" % argv[0], f=sys.stderr)
+
+def usage(argv):
+ console_print(u"Dropbox command-line interface\n")
+ console_print(u"commands:\n")
+ console_print(u"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]))
+ spacing = max(len(o[0])+3 for o in out)
+ for o in out:
+ console_print(" %-*s%s" % (spacing, o[0], o[1]))
+ console_print()
+
+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(argv)
+ 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)