~ubuntu-branches/ubuntu/saucy/nautilus-dropbox/saucy-proposed

« back to all changes in this revision

Viewing changes to .pc/download-via-https.patch/dropbox.in

  • Committer: Bazaar Package Importer
  • Author(s): Raphaël Hertzog
  • Date: 2011-06-09 09:02:14 UTC
  • Revision ID: james.westby@ubuntu.com-20110609090214-0ghapun702iv09bk
Tags: 0.6.7-2
* Add lintian override for the use of killall.
* Include full license for CC-BY-ND.
* Switch to HTTPS for the download, but we still have to implement
  HTTPS certificate validation.
* Add the "XS-Autobuild: yes" header as per
  http://lists.debian.org/debian-devel-announce/2006/11/msg00012.html

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
#
 
3
# Copyright 2008 Evenflow, Inc.
 
4
#
 
5
# dropbox
 
6
# Dropbox frontend script
 
7
# This file is part of nautilus-dropbox @PACKAGE_VERSION@.
 
8
#
 
9
# nautilus-dropbox is free software: you can redistribute it and/or modify
 
10
# it under the terms of the GNU General Public License as published by
 
11
# the Free Software Foundation, either version 3 of the License, or
 
12
# (at your option) any later version.
 
13
#
 
14
# nautilus-dropbox is distributed in the hope that it will be useful,
 
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
# GNU General Public License for more details.
 
18
#
 
19
# You should have received a copy of the GNU General Public License
 
20
# along with nautilus-dropbox.  If not, see <http://www.gnu.org/licenses/>.
 
21
#
 
22
from __future__ import with_statement
 
23
 
 
24
import sys
 
25
import optparse
 
26
import socket
 
27
import os
 
28
import shutil
 
29
import time
 
30
import platform
 
31
import tarfile
 
32
import threading
 
33
import urllib
 
34
import subprocess
 
35
import fcntl
 
36
import errno
 
37
import locale
 
38
from contextlib import closing
 
39
from posixpath import curdir, sep, pardir, join, abspath, commonprefix
 
40
 
 
41
INFO = u"Dropbox is the easiest way to share and store your files online. Want to learn more? Head to"
 
42
LINK = u"http://www.dropbox.com/"
 
43
WARNING = u"In order to use Dropbox, you must download the proprietary daemon."
 
44
 
 
45
DOWNLOADING = u"Downloading Dropbox... %d%%"
 
46
UNPACKING = u"Unpacking Dropbox... %d%%"
 
47
 
 
48
PARENT_DIR = os.path.expanduser("/var/lib/dropbox")
 
49
DROPBOXD_PATH = "%s/.dropbox-dist/dropboxd" % PARENT_DIR
 
50
 
 
51
enc = locale.getpreferredencoding()
 
52
 
 
53
# Futures
 
54
 
 
55
def methodcaller(name, *args, **kwargs):
 
56
    def caller(obj):
 
57
        return getattr(obj, name)(*args, **kwargs)
 
58
    return caller
 
59
 
 
60
def relpath(path, start=curdir):
 
61
    """Return a relative version of a path"""
 
62
 
 
63
    if not path:
 
64
        raise ValueError("no path specified")
 
65
 
 
66
    if type(start) is unicode:
 
67
        start_list = unicode_abspath(start).split(sep)
 
68
    else:
 
69
        start_list = abspath(start).split(sep)
 
70
 
 
71
    if type(path) is unicode:
 
72
        path_list = unicode_abspath(path).split(sep)
 
73
    else:
 
74
        path_list = abspath(path).split(sep)
 
75
 
 
76
    # Work out how much of the filepath is shared by start and path.
 
77
    i = len(commonprefix([start_list, path_list]))
 
78
 
 
79
    rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
 
80
    if not rel_list:
 
81
        return curdir
 
82
    return join(*rel_list)
 
83
 
 
84
# End Futures
 
85
 
 
86
 
 
87
def console_print(st=u"", f=sys.stdout, linebreak=True):
 
88
    global enc
 
89
    assert type(st) is unicode
 
90
    f.write(st.encode(enc))
 
91
    if linebreak: f.write(os.linesep)
 
92
 
 
93
def console_flush(f=sys.stdout):
 
94
    f.flush()
 
95
 
 
96
def yes_no_question(question):
 
97
    while True:
 
98
        console_print(question, linebreak=False)
 
99
        console_print(u" [y/n] ", linebreak=False)
 
100
        console_flush()
 
101
        text = raw_input()
 
102
        if text.lower().startswith("y"):
 
103
            return True
 
104
        elif text.lower().startswith("n"):
 
105
            return False
 
106
        else:
 
107
            console_print(u"Sorry, I didn't understand that. Please type yes or no.")
 
108
    
 
109
def plat():
 
110
    if sys.platform.lower().startswith('linux'):
 
111
        arch = subprocess.Popen(["dpkg", "--print-architecture"],
 
112
                                stdout=subprocess.PIPE).communicate()[0]
 
113
        arch = arch.rstrip("\n")
 
114
        if arch == "i386":
 
115
            plat = "x86"
 
116
        elif arch == "amd64":
 
117
            plat = "x86_64"
 
118
        else:
 
119
            FatalVisibleError("Platform not supported")
 
120
        return "lnx.%s" % plat
 
121
    else:
 
122
        FatalVisibleError("Platform not supported")
 
123
 
 
124
def is_dropbox_running():
 
125
    pidfile = os.path.expanduser("~/.dropbox/dropbox.pid")
 
126
 
 
127
    try:
 
128
        with open(pidfile, "r") as f:
 
129
            pid = int(f.read())
 
130
        with open("/proc/%d/cmdline" % pid, "r") as f:
 
131
            cmdline = f.read().lower()
 
132
    except:
 
133
        cmdline = ""
 
134
 
 
135
    return "dropbox" in cmdline
 
136
 
 
137
def unicode_abspath(path):
 
138
    global enc
 
139
    assert type(path) is unicode
 
140
    # shouldn't pass unicode to this craphead, it appends with os.getcwd() which is always a str 
 
141
    return os.path.abspath(path.encode(sys.getfilesystemencoding())).decode(sys.getfilesystemencoding())
 
142
 
 
143
# This sets a custom User-Agent
 
144
class DropboxURLopener(urllib.FancyURLopener):
 
145
    version = "DropboxLinuxDownloader/@PACKAGE_VERSION@"
 
146
urllib._urlopener = DropboxURLopener()
 
147
 
 
148
class DownloadState(object):
 
149
    def __init__(self):
 
150
        try:
 
151
            self.file = urllib.urlopen("http://www.dropbox.com/download?plat=%s" % plat())
 
152
        except IOError:
 
153
            FatalVisibleError("Trouble connecting to Dropbox servers. Maybe your internet connection is down, or you need to set your http_proxy environment variable.")
 
154
            
 
155
        fcntl.fcntl(self.file, fcntl.F_SETFL, os.O_NONBLOCK)
 
156
        
 
157
        self.size = int(self.file.info()['content-length'])
 
158
        self.progress = 0
 
159
 
 
160
        self.local_path = "%s/dropbox.tar.gz" % PARENT_DIR
 
161
        self.local_file = open(self.local_path, 'wb')
 
162
 
 
163
    def copy_data(self):
 
164
        while True:
 
165
            try:
 
166
                chunk = os.read(self.file.fileno(), 4096)
 
167
                self.progress += len(chunk)
 
168
                self.local_file.write(chunk)
 
169
                yield True
 
170
                if self.progress == self.size:
 
171
                    break
 
172
            except OSError, e:
 
173
                if hasattr(e, 'errno') and e.errno == errno.EAGAIN:
 
174
                    # nothing left to read
 
175
                    yield False
 
176
                else:
 
177
                    raise
 
178
 
 
179
        self.file.close()
 
180
 
 
181
    def unpack(self):
 
182
        self.local_file.close()
 
183
        archive = tarfile.open(self.local_path, 'r:gz')
 
184
        total_members = len(archive.getmembers())
 
185
        for i, member in enumerate(archive.getmembers()):
 
186
            archive.extract(member, PARENT_DIR)
 
187
            yield member.name, i, total_members
 
188
        archive.close()
 
189
        os.remove(self.local_path)
 
190
 
 
191
    def cancel(self):
 
192
        if not self.local_file.closed:
 
193
            self.local_file.close()
 
194
        if os.path.exists(self.local_path):
 
195
            os.remove(self.local_path)
 
196
 
 
197
def load_serialized_images():
 
198
    global box_logo_pixbuf, window_icon
 
199
    import gtk
 
200
    box_logo_pixbuf = @IMAGEDATA64@
 
201
    window_icon = @IMAGEDATA16@
 
202
 
 
203
GUI_AVAILABLE = os.environ.get("DISPLAY", '')
 
204
 
 
205
if GUI_AVAILABLE:
 
206
    def download():
 
207
        import pygtk
 
208
        pygtk.require("2.0")
 
209
        import gtk
 
210
        import gobject
 
211
        import pango
 
212
        import webbrowser
 
213
 
 
214
        load_serialized_images()
 
215
 
 
216
        global FatalVisibleError
 
217
        def FatalVisibleError(s):
 
218
            error = gtk.MessageDialog(parent = None,
 
219
                                      flags = gtk.DIALOG_MODAL,
 
220
                                      type = gtk.MESSAGE_ERROR,
 
221
                                      buttons = gtk.BUTTONS_OK,
 
222
                                      message_format = s)
 
223
            error.set_title("Error")
 
224
            error.run()
 
225
            gtk.main_quit()
 
226
            sys.exit(-1)
 
227
 
 
228
        def gtk_flush_events():
 
229
            while gtk.events_pending():
 
230
                gtk.main_iteration()
 
231
 
 
232
        class DownloadDialog(gtk.Dialog):
 
233
            def handle_delete_event(self, wid, ev, data=None):
 
234
                self.handle_cancel(wid)
 
235
 
 
236
            def handle_dont_show_toggle(self, button, data=None):
 
237
                reroll_autostart(not button.get_active())
 
238
 
 
239
            def handle_cancel(self, button):
 
240
                if self.watch:
 
241
                    gobject.source_remove(self.watch)
 
242
                if self.download:
 
243
                    self.download.cancel()
 
244
                gtk.main_quit()
 
245
                self.user_cancelled = True
 
246
 
 
247
            def handle_ok(self, button):
 
248
                # begin download
 
249
                self.ok.hide()
 
250
                self.download = DownloadState()
 
251
                self.one_chunk = self.download.copy_data()
 
252
                self.watch = gobject.io_add_watch(self.download.file,
 
253
                                                  gobject.IO_IN | 
 
254
                                                  gobject.IO_PRI |
 
255
                                                  gobject.IO_ERR |
 
256
                                                  gobject.IO_HUP,
 
257
                                                  self.handle_data_waiting)
 
258
                self.label.hide()
 
259
                self.dont_show_again_align.hide()
 
260
                self.progress.show()
 
261
 
 
262
            def update_progress(self, text, fraction):
 
263
                self.progress.set_text(text % int(fraction*100))
 
264
                self.progress.set_fraction(fraction)
 
265
                gtk_flush_events()
 
266
 
 
267
            def handle_data_waiting(self, fd, condition):
 
268
                if condition == gobject.IO_HUP:
 
269
                    FatalVisibleError("Connection to server unexpectedly closed.")
 
270
                elif condition == gobject.IO_ERR:
 
271
                    FatalVisibleError("Unexpected error occurred with download.")
 
272
                try:
 
273
                    while self.one_chunk.next():
 
274
                        self.update_progress(DOWNLOADING, float(self.download.progress)/self.download.size)
 
275
                except StopIteration:
 
276
                    self.update_progress(DOWNLOADING, 1.0)
 
277
                    self.unpack_dropbox()
 
278
                    return False
 
279
                else:
 
280
                    self.update_progress(DOWNLOADING, float(self.download.progress)/self.download.size)
 
281
                    return True
 
282
 
 
283
            def unpack_dropbox(self):
 
284
                one_member = self.download.unpack()
 
285
                try:
 
286
                    while True:
 
287
                        name, i, total = one_member.next()
 
288
                        self.update_progress(UNPACKING, float(i)/total)
 
289
                except StopIteration:
 
290
                    self.update_progress(UNPACKING, 1.0)
 
291
                    gtk.main_quit()
 
292
 
 
293
            def mouse_down(self, widget, event):
 
294
                if self.hovering:
 
295
                    self.clicked_link = True
 
296
 
 
297
            def mouse_up(self, widget, event):
 
298
                if self.clicked_link:
 
299
                    webbrowser.open(LINK)
 
300
                    self.clicked_link = False
 
301
 
 
302
            def label_motion(self, widget, event):
 
303
                offx, offy = self.label.get_layout_offsets()
 
304
                layout = self.label.get_layout()
 
305
                index = layout.xy_to_index(int((offx+event.x)*pango.SCALE),
 
306
                                           int((offy+event.y)*pango.SCALE))[0]
 
307
                link_index = layout.get_text().find(LINK)
 
308
                if index >= link_index and index < link_index+len(LINK):
 
309
                    self.hovering = True
 
310
                    self.label_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
 
311
                else:
 
312
                    self.hovering = False
 
313
                    self.label_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
 
314
 
 
315
 
 
316
            def __init__(self):
 
317
                super(DownloadDialog, self).__init__(parent = None,
 
318
                                                     title = "Dropbox Installation")
 
319
 
 
320
                self.download = None
 
321
                self.watch = None
 
322
                self.hovering = False
 
323
                self.clicked_link = False
 
324
                self.user_cancelled = False
 
325
 
 
326
                self.ok = ok = gtk.Button(stock=gtk.STOCK_OK)
 
327
                ok.connect('clicked', self.handle_ok)
 
328
                self.action_area.add(ok)
 
329
                ok.show()
 
330
 
 
331
                cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
 
332
                cancel.connect('clicked', self.handle_cancel)
 
333
                self.action_area.add(cancel)
 
334
                cancel.show()
 
335
 
 
336
                self.connect('delete_event', self.handle_delete_event)
 
337
 
 
338
                self.box_logo = gtk.image_new_from_pixbuf(box_logo_pixbuf)
 
339
                self.box_logo.show()
 
340
 
 
341
                self.set_icon(window_icon)
 
342
 
 
343
                self.progress = gtk.ProgressBar()
 
344
                self.progress.set_property('width-request', 300)
 
345
 
 
346
                self.label = gtk.Label()
 
347
                self.label.set_markup('%s <span foreground="#000099" underline="single" weight="bold">%s</span>\n\n%s' % (INFO, LINK, WARNING))
 
348
                self.label.set_line_wrap(True)
 
349
                self.label.set_property('width-request', 300)
 
350
                self.label.show()
 
351
 
 
352
                self.label_box = gtk.EventBox()
 
353
                self.label_box.add(self.label)
 
354
                self.label_box.connect("button-release-event", self.mouse_up)
 
355
                self.label_box.connect("button-press-event", self.mouse_down)
 
356
                self.label_box.connect("motion-notify-event", self.label_motion)
 
357
 
 
358
                self.label_box.show()
 
359
                def on_realize(widget):
 
360
                    self.label_box.add_events(gtk.gdk.POINTER_MOTION_MASK)
 
361
                self.label_box.connect("realize", on_realize)
 
362
 
 
363
                self.hbox = gtk.HBox(spacing=10)
 
364
                self.hbox.set_property('border-width',10)
 
365
                self.hbox.pack_start(self.box_logo, False, False)
 
366
                self.hbox.pack_start(self.label_box, False, False)
 
367
                self.hbox.pack_start(self.progress, False, False)
 
368
                self.hbox.show()
 
369
 
 
370
                self.vbox.add(self.hbox)
 
371
 
 
372
                try:
 
373
                    if can_reroll_autostart():
 
374
                        dont_show_again = gtk.CheckButton("_Don't show this again")
 
375
                        dont_show_again.connect('toggled', self.handle_dont_show_toggle)
 
376
                        dont_show_again.show()
 
377
 
 
378
                        self.dont_show_again_align = gtk.Alignment(xalign=1.0, yalign=0.0, xscale=0.0, yscale=0.0)
 
379
                        self.dont_show_again_align.add(dont_show_again)
 
380
                        self.dont_show_again_align.show()
 
381
 
 
382
                        hbox = gtk.HBox()
 
383
                        hbox.set_property('border-width', 10)
 
384
                        hbox.pack_start(self.dont_show_again_align, True, True)
 
385
                        hbox.show()
 
386
 
 
387
                        self.vbox.add(hbox)
 
388
 
 
389
                    self.set_resizable(False)
 
390
                except:
 
391
                    import traceback
 
392
                    traceback.print_exc()
 
393
 
 
394
                self.ok.grab_focus()
 
395
 
 
396
        dialog = DownloadDialog()
 
397
        dialog.show()
 
398
        gtk.main()
 
399
        if dialog.user_cancelled:
 
400
            raise Exception("user cancelled download!!!")
 
401
else:
 
402
    def download():
 
403
        global FatalVisibleError
 
404
        def FatalVisibleError(s):
 
405
            console_print(u"\nError: %s" % s, f=sys.stderr)
 
406
            sys.exit(-1)
 
407
 
 
408
 
 
409
        ESC = "\x1b"
 
410
        save = ESC+"7"
 
411
        unsave = ESC+"8"
 
412
        clear = ESC+"[2J"
 
413
        erase_to_start = ESC+"[1K"
 
414
        write = sys.stdout.write
 
415
        flush = sys.stdout.flush
 
416
 
 
417
        last_progress = [None, None]
 
418
        def setprogress(text, frac):
 
419
            if last_progress == [text, frac]:
 
420
                return
 
421
            if sys.stdout.isatty():
 
422
                write(erase_to_start)
 
423
                write(unsave)
 
424
            console_print(text % int(100*frac), linebreak=not sys.stdout.isatty())
 
425
            if sys.stdout.isatty():
 
426
                flush()
 
427
            last_progress[0], last_progress[1] = text, frac
 
428
 
 
429
        console_print()
 
430
        if sys.stdout.isatty():
 
431
            write(save)
 
432
            flush()
 
433
        console_print(u"%s %s\n" % (INFO, LINK))
 
434
 
 
435
        download = DownloadState()
 
436
        one_chunk = download.copy_data()
 
437
 
 
438
        try:    
 
439
            while True:
 
440
                one_chunk.next()
 
441
                setprogress(DOWNLOADING, float(download.progress)/download.size)
 
442
        except StopIteration:
 
443
            setprogress(DOWNLOADING, 1.0)
 
444
            console_print()
 
445
            write(save)
 
446
 
 
447
        one_member = download.unpack()
 
448
 
 
449
        try:
 
450
            while True:
 
451
                name, i, total = one_member.next()
 
452
                setprogress(UNPACKING, float(i)/total)
 
453
        except StopIteration:
 
454
            setprogress(UNPACKING, 1.0)
 
455
 
 
456
        console_print()
 
457
 
 
458
class CommandTicker(threading.Thread):
 
459
    def __init__(self):
 
460
        threading.Thread.__init__(self)
 
461
        self.stop_event = threading.Event()
 
462
 
 
463
    def stop(self):
 
464
        self.stop_event.set()
 
465
 
 
466
    def run(self):
 
467
        ticks = ['[.  ]', '[.. ]', '[...]', '[ ..]', '[  .]', '[   ]']
 
468
        i = 0
 
469
        first = True
 
470
        while True:
 
471
            self.stop_event.wait(0.25)
 
472
            if self.stop_event.isSet(): break
 
473
            if i == len(ticks):
 
474
                first = False
 
475
                i = 0
 
476
            if not first:
 
477
                sys.stderr.write("\r%s\r" % ticks[i])
 
478
                sys.stderr.flush()
 
479
            i += 1
 
480
        sys.stderr.flush()
 
481
                
 
482
 
 
483
class DropboxCommand(object):
 
484
    class CouldntConnectError(Exception): pass
 
485
    class BadConnectionError(Exception): pass
 
486
    class EOFError(Exception): pass
 
487
    class CommandError(Exception): pass
 
488
 
 
489
    def __init__(self, timeout=5):
 
490
        self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
491
        self.s.settimeout(timeout)
 
492
        try:
 
493
            self.s.connect(os.path.expanduser(u'~/.dropbox/command_socket'))
 
494
        except socket.error, e:
 
495
            raise DropboxCommand.CouldntConnectError()
 
496
        self.f = self.s.makefile("r+", 4096)
 
497
 
 
498
    def close(self):
 
499
        self.f.close()
 
500
        self.s.close()
 
501
 
 
502
    def __readline(self):
 
503
        try:
 
504
            toret = self.f.readline().decode('utf8').rstrip(u"\n")
 
505
        except socket.error, e:
 
506
            raise DropboxCommand.BadConnectionError()
 
507
        if toret == '':
 
508
            raise DropboxCommand.EOFError()
 
509
        else:
 
510
            return toret
 
511
 
 
512
    # atttribute doesn't exist, i know what you want
 
513
    def send_command(self, name, args):
 
514
        self.f.write(name.encode('utf8'))
 
515
        self.f.write(u"\n".encode('utf8'))
 
516
        self.f.writelines((u"\t".join([k] + (list(v)
 
517
                                             if hasattr(v, '__iter__') else
 
518
                                             [v])) + u"\n").encode('utf8')
 
519
                          for k,v in args.iteritems())
 
520
        self.f.write(u"done\n".encode('utf8'))
 
521
                
 
522
        self.f.flush()
 
523
 
 
524
        # Start a ticker
 
525
        ticker_thread = CommandTicker()
 
526
        ticker_thread.start()
 
527
 
 
528
        # This is the potentially long-running call. 
 
529
        try:
 
530
            ok = self.__readline() == u"ok"
 
531
        except KeyboardInterrupt:
 
532
            raise DropboxCommand.BadConnectionError("Keyboard interruption detected")
 
533
        finally:    
 
534
            # Tell the ticker to stop. 
 
535
            ticker_thread.stop()
 
536
            ticker_thread.join()
 
537
        
 
538
        if ok:
 
539
            toret = {}
 
540
            for i in range(21):
 
541
                if i == 20:
 
542
                    raise Exception(u"close this connection!")
 
543
 
 
544
                line = self.__readline()
 
545
                if line == u"done":
 
546
                    break
 
547
                        
 
548
                argval = line.split(u"\t")
 
549
                toret[argval[0]] = argval[1:]
 
550
 
 
551
            return toret
 
552
        else:
 
553
            problems = []
 
554
            for i in range(21):
 
555
                if i == 20:
 
556
                    raise Exception(u"close this connection!")
 
557
 
 
558
                line = self.__readline()
 
559
                if line == u"done":
 
560
                    break
 
561
                        
 
562
                problems.append(line)
 
563
                    
 
564
            raise DropboxCommand.CommandError(u"\n".join(problems))
 
565
 
 
566
    # this is the hotness, auto marshalling
 
567
    def __getattr__(self, name):
 
568
        try:
 
569
            return super(DropboxCommand, self).__getattr__(name)
 
570
        except:
 
571
            def __spec_command(**kw):
 
572
                return self.send_command(unicode(name), kw)
 
573
            self.__setattr__(name, __spec_command)
 
574
            return __spec_command
 
575
 
 
576
commands = {}
 
577
aliases = {}
 
578
 
 
579
def command(meth):
 
580
    global commands, aliases
 
581
    assert meth.__doc__, "All commands need properly formatted docstrings (even %r!!)" % meth
 
582
    if hasattr(meth, 'im_func'): # bound method, if we ever have one
 
583
        meth = meth.im_func
 
584
    commands[meth.func_name] = meth
 
585
    meth_aliases = [unicode(alias) for alias in aliases.iterkeys() if aliases[alias].func_name == meth.func_name]
 
586
    if meth_aliases:
 
587
        meth.__doc__ += u"\nAliases: %s" % ",".join(meth_aliases)
 
588
    return meth
 
589
 
 
590
def alias(name):
 
591
    def decorator(meth):
 
592
        global commands, aliases
 
593
        assert name not in commands, "This alias is the name of a command."
 
594
        aliases[name] = meth
 
595
        return meth
 
596
    return decorator
 
597
 
 
598
def requires_dropbox_running(meth):
 
599
    def newmeth(*n, **kw):
 
600
        if is_dropbox_running():
 
601
            return meth(*n, **kw)
 
602
        else:
 
603
            console_print(u"Dropbox isn't running!")            
 
604
    newmeth.func_name = meth.func_name
 
605
    newmeth.__doc__ = meth.__doc__
 
606
    return newmeth
 
607
 
 
608
def start_dropbox():
 
609
    db_path = os.path.expanduser(DROPBOXD_PATH).encode(sys.getfilesystemencoding())
 
610
    if os.access(db_path, os.X_OK):
 
611
        f = open("/dev/null", "w")
 
612
        # we don't reap the child because we're gonna die anyway, let init do it
 
613
        a = subprocess.Popen([db_path], preexec_fn=os.setsid, cwd=os.path.expanduser("~"),
 
614
                             stderr=sys.stderr, stdout=f, close_fds=True)
 
615
        
 
616
        # in seconds
 
617
        interval = 0.5
 
618
        wait_for = 60
 
619
        for i in xrange(int(wait_for / interval)):
 
620
            if is_dropbox_running():
 
621
                return True
 
622
            # back off from connect for a while
 
623
            time.sleep(interval)
 
624
                
 
625
        return False
 
626
    else:
 
627
        return False
 
628
 
 
629
# Extracted and modified from os.cmd.Cmd
 
630
def columnize(list, display_list=None, display_width=None):
 
631
    if not list:
 
632
        console_print(u"<empty>")
 
633
        return
 
634
        
 
635
    non_unicode = [i for i in range(len(list)) if not (isinstance(list[i], unicode))]
 
636
    if non_unicode:
 
637
        raise TypeError, ("list[i] not a string for i in %s" %
 
638
                          ", ".join(map(unicode, non_unicode)))
 
639
 
 
640
    if not display_width:
 
641
        d = os.popen('stty size', 'r').read().split()
 
642
        if d:
 
643
            display_width = int(d[1])
 
644
        else:
 
645
            for item in list:
 
646
                console_print(item)
 
647
            return
 
648
        
 
649
    if not display_list:
 
650
        display_list = list
 
651
        
 
652
    size = len(list)
 
653
    if size == 1:
 
654
        console_print(display_list[0])
 
655
        return
 
656
 
 
657
    for nrows in range(1, len(list)):
 
658
        ncols = (size+nrows-1) // nrows
 
659
        colwidths = []
 
660
        totwidth = -2
 
661
        for col in range(ncols):
 
662
            colwidth = 0
 
663
            for row in range(nrows):
 
664
                i = row + nrows*col
 
665
                if i >= size:
 
666
                    break
 
667
                x = list[i]
 
668
                colwidth = max(colwidth, len(x))
 
669
            colwidths.append(colwidth)
 
670
            totwidth += colwidth + 2
 
671
            if totwidth > display_width:
 
672
                break
 
673
        if totwidth <= display_width:
 
674
            break
 
675
    else:
 
676
        nrows = len(list)
 
677
        ncols = 1
 
678
        colwidths = [0]
 
679
    lines = []
 
680
    for row in range(nrows):
 
681
        texts = []
 
682
        display_texts = []
 
683
        for col in range(ncols):
 
684
            i = row + nrows*col
 
685
            if i >= size:
 
686
                x = ""
 
687
                y = ""
 
688
            else:
 
689
                x = list[i]
 
690
                y = display_list[i]
 
691
            texts.append(x)
 
692
            display_texts.append(y)
 
693
        while texts and not texts[-1]:
 
694
            del texts[-1]
 
695
        original_texts = texts[:]
 
696
        for col in range(len(texts)):
 
697
            texts[col] = texts[col].ljust(colwidths[col])
 
698
        line = u"%s" % "  ".join(texts)
 
699
        for i, text in enumerate(original_texts):
 
700
            line = line.replace(text, display_texts[i])
 
701
        lines.append(line)
 
702
    for line in lines:
 
703
        console_print(line)
 
704
 
 
705
@command
 
706
def update(args):
 
707
    u"""download latest version of dropbox
 
708
dropbox update
 
709
 
 
710
Downloads the latest version of dropbox.
 
711
"""
 
712
    download()
 
713
 
 
714
@command
 
715
@requires_dropbox_running
 
716
@alias('stat')
 
717
def filestatus(args):
 
718
    u"""get current sync status of one or more files
 
719
dropbox filestatus [-l] [-a] [FILE]...
 
720
 
 
721
Prints the current status of each FILE.
 
722
 
 
723
options:
 
724
  -l --list  prints out information in a format similar to ls. works best when your console supports color :)
 
725
  -a --all   do not ignore entries starting with .
 
726
"""
 
727
    global enc
 
728
 
 
729
    oparser = optparse.OptionParser()
 
730
    oparser.add_option("-l", "--list", action="store_true", dest="list")
 
731
    oparser.add_option("-a", "--all", action="store_true", dest="all")
 
732
    (options, args) = oparser.parse_args(args)
 
733
 
 
734
    try:
 
735
        with closing(DropboxCommand()) as dc:
 
736
            if options.list:
 
737
                # Listing. 
 
738
 
 
739
                # Separate directories from files. 
 
740
                if len(args) == 0:
 
741
                    dirs, nondirs = [u"."], []
 
742
                else:
 
743
                    dirs, nondirs = [], []
 
744
 
 
745
                    for a in args:
 
746
                        try:
 
747
                            (dirs if os.path.isdir(a) else nondirs).append(a.decode(enc))
 
748
                        except UnicodeDecodeError:
 
749
                            continue
 
750
 
 
751
                    if len(dirs) == 0 and len(nondirs) == 0:
 
752
                        #TODO: why? 
 
753
                        exit(1)
 
754
 
 
755
                dirs.sort(key=methodcaller('lower'))
 
756
                nondirs.sort(key=methodcaller('lower'))
 
757
 
 
758
                # Gets a string representation for a path. 
 
759
                def path_to_string(file_path):
 
760
                    if not os.path.exists(file_path):
 
761
                        path = u"%s (File doesn't exist!)" % os.path.basename(file_path)
 
762
                        return (path, path)
 
763
                    try:
 
764
                        status = dc.icon_overlay_file_status(path=file_path).get(u'status', [None])[0]
 
765
                    except DropboxCommand.CommandError, e:
 
766
                        path =  u"%s (%s)" % (os.path.basename(file_path), e) 
 
767
                        return (path, path)
 
768
 
 
769
                    env_term = os.environ.get('TERM','')
 
770
                    supports_color = (sys.stderr.isatty() and (
 
771
                                        env_term.startswith('vt') or 
 
772
                                        env_term.startswith('linux') or 
 
773
                                        'xterm' in env_term or 
 
774
                                        'color' in env_term
 
775
                                        )
 
776
                                     )
 
777
                    
 
778
                    # TODO: Test when you don't support color.    
 
779
                    if not supports_color:
 
780
                        path = os.path.basename(file_path)
 
781
                        return (path, path)
 
782
 
 
783
                    if status == u"up to date":
 
784
                        init, cleanup = "\x1b[32;1m", "\x1b[0m"
 
785
                    elif status == u"syncing":
 
786
                        init, cleanup = "\x1b[36;1m", "\x1b[0m"
 
787
                    elif status == u"unsyncable":
 
788
                        init, cleanup = "\x1b[41;1m", "\x1b[0m"
 
789
                    elif status == u"selsync":
 
790
                        init, cleanup = "\x1b[37;1m", "\x1b[0m"
 
791
                    else:
 
792
                        init, cleanup = '', ''
 
793
 
 
794
                    path = os.path.basename(file_path)
 
795
                    return (path, u"%s%s%s" % (init, path, cleanup))
 
796
 
 
797
                # Prints a directory. 
 
798
                def print_directory(name):
 
799
                    clean_paths = []
 
800
                    formatted_paths = []
 
801
                    for subname in sorted(os.listdir(name), key=methodcaller('lower')):
 
802
                        if type(subname) != unicode:
 
803
                            continue
 
804
                        
 
805
                        if not options.all and subname[0] == u'.':
 
806
                            continue
 
807
 
 
808
                        try:
 
809
                            clean, formatted = path_to_string(unicode_abspath(os.path.join(name, subname)))
 
810
                            clean_paths.append(clean)
 
811
                            formatted_paths.append(formatted)
 
812
                        except (UnicodeEncodeError, UnicodeDecodeError), e:
 
813
                            continue
 
814
                    
 
815
                    columnize(clean_paths, formatted_paths)
 
816
 
 
817
                try:
 
818
                    if len(dirs) == 1 and len(nondirs) == 0:
 
819
                        print_directory(dirs[0])
 
820
                    else:
 
821
                        nondir_formatted_paths = []
 
822
                        nondir_clean_paths = []
 
823
                        for name in nondirs:
 
824
                            try:
 
825
                                clean, formatted = path_to_string(unicode_abspath(name))
 
826
                                nondir_clean_paths.append(clean)
 
827
                                nondir_formatted_paths.append(formatted)
 
828
                            except (UnicodeEncodeError, UnicodeDecodeError), e:
 
829
                                continue
 
830
 
 
831
                        if nondir_clean_paths:        
 
832
                            columnize(nondir_clean_paths, nondir_formatted_paths)
 
833
                        
 
834
                        if len(nondirs) == 0:
 
835
                            console_print(dirs[0] + u":")
 
836
                            print_directory(dirs[0])
 
837
                            dirs = dirs[1:]
 
838
 
 
839
                        for name in dirs:
 
840
                            console_print()
 
841
                            console_print(name + u":")
 
842
                            print_directory(name)
 
843
                    
 
844
                except DropboxCommand.EOFError:
 
845
                    console_print(u"Dropbox daemon stopped.")
 
846
                except DropboxCommand.BadConnectionError, e:
 
847
                    console_print(u"Dropbox isn't responding!")
 
848
            else:
 
849
                if len(args) == 0:
 
850
                    args = [name for name in sorted(os.listdir(u"."), key=methodcaller('lower')) if type(name) == unicode]
 
851
                indent = max(len(st)+1 for st in args)
 
852
                for file in args:
 
853
 
 
854
                    try:
 
855
                        if type(file) is not unicode:
 
856
                            file = file.decode(enc)
 
857
                        fp = unicode_abspath(file)
 
858
                    except (UnicodeEncodeError, UnicodeDecodeError), e:
 
859
                        continue
 
860
                    if not os.path.exists(fp):
 
861
                        console_print(u"%-*s %s" % \
 
862
                                          (indent, file+':', "File doesn't exist"))
 
863
                        continue
 
864
                        
 
865
                    try:
 
866
                        status = dc.icon_overlay_file_status(path=fp).get(u'status', [u'unknown'])[0]
 
867
                        console_print(u"%-*s %s" % (indent, file+':', status))
 
868
                    except DropboxCommand.CommandError, e:
 
869
                        console_print(u"%-*s %s" % (indent, file+':', e))
 
870
    except DropboxCommand.CouldntConnectError, e:
 
871
        console_print(u"Dropbox isn't running!")
 
872
 
 
873
@command
 
874
@requires_dropbox_running
 
875
def ls(args):
 
876
    u"""list directory contents with current sync status
 
877
dropbox ls [FILE]...
 
878
 
 
879
This is an alias for filestatus -l
 
880
"""
 
881
    return filestatus(["-l"] + args)
 
882
 
 
883
@command
 
884
@requires_dropbox_running
 
885
def puburl(args):
 
886
    u"""get public url of a file in your dropbox
 
887
dropbox puburl FILE
 
888
 
 
889
Prints out a public url for FILE.
 
890
"""
 
891
    if len(args) != 1:
 
892
        console_print(puburl.__doc__,linebreak=False)
 
893
        return
 
894
 
 
895
    try:
 
896
        with closing(DropboxCommand()) as dc:
 
897
            try:
 
898
                console_print(dc.get_public_link(path=unicode_abspath(args[0].decode(sys.getfilesystemencoding()))).get(u'link', [u'No Link'])[0])
 
899
            except DropboxCommand.CommandError, e:
 
900
                console_print(u"Couldn't get public url: " + str(e))
 
901
            except DropboxCommand.BadConnectionError, e:
 
902
                console_print(u"Dropbox isn't responding!")        
 
903
            except DropboxCommand.EOFError:
 
904
                console_print(u"Dropbox daemon stopped.")
 
905
    except DropboxCommand.CouldntConnectError, e:
 
906
        console_print(u"Dropbox isn't running!")
 
907
 
 
908
@command
 
909
@requires_dropbox_running
 
910
def status(args):
 
911
    u"""get current status of the dropboxd
 
912
dropbox status
 
913
 
 
914
Prints out the current status of the Dropbox daemon.
 
915
"""
 
916
    if len(args) != 0:
 
917
        console_print(status.__doc__,linebreak=False)
 
918
        return
 
919
 
 
920
    try:
 
921
        with closing(DropboxCommand()) as dc:
 
922
            try:
 
923
                lines = dc.get_dropbox_status()[u'status']
 
924
                if len(lines) == 0:
 
925
                    console_print(u'Idle')                    
 
926
                else:
 
927
                    for line in lines:
 
928
                        console_print(line)
 
929
            except KeyError:
 
930
                console_print(u"Couldn't get status: daemon isn't responding")
 
931
            except DropboxCommand.CommandError, e:
 
932
                console_print(u"Couldn't get status: " + str(e))
 
933
            except DropboxCommand.BadConnectionError, e:
 
934
                console_print(u"Dropbox isn't responding!")
 
935
            except DropboxCommand.EOFError:
 
936
                console_print(u"Dropbox daemon stopped.")
 
937
    except DropboxCommand.CouldntConnectError, e:
 
938
        console_print(u"Dropbox isn't running!")
 
939
 
 
940
@command
 
941
def running(argv):
 
942
    u"""return whether dropbox is running
 
943
dropbox running
 
944
 
 
945
Returns 1 if running 0 if not running.
 
946
"""
 
947
    return int(is_dropbox_running())
 
948
 
 
949
@command
 
950
@requires_dropbox_running
 
951
def stop(args):
 
952
    u"""stop dropboxd
 
953
dropbox stop
 
954
 
 
955
Stops the dropbox daemon.
 
956
"""
 
957
    try:
 
958
        with closing(DropboxCommand()) as dc:
 
959
            try:
 
960
                dc.tray_action_hard_exit()
 
961
            except DropboxCommand.BadConnectionError, e:
 
962
                console_print(u"Dropbox isn't responding!")        
 
963
            except DropboxCommand.EOFError:
 
964
                console_print(u"Dropbox daemon stopped.")
 
965
    except DropboxCommand.CouldntConnectError, e:
 
966
        console_print(u"Dropbox isn't running!")
 
967
 
 
968
#returns true if link is necessary
 
969
def grab_link_url_if_necessary():
 
970
    try:
 
971
        with closing(DropboxCommand()) as dc:
 
972
            try:
 
973
                link_url = dc.needs_link().get(u"link_url", None)
 
974
                if link_url is not None:
 
975
                    console_print(u"To link this computer to a dropbox account, visit the following url:\n%s" % link_url[0])
 
976
                    return True
 
977
                else:
 
978
                    return False
 
979
            except DropboxCommand.CommandError, e:
 
980
                pass
 
981
            except DropboxCommand.BadConnectionError, e:
 
982
                console_print(u"Dropbox isn't responding!")        
 
983
            except DropboxCommand.EOFError:
 
984
                console_print(u"Dropbox daemon stopped.")
 
985
    except DropboxCommand.CouldntConnectError, e:
 
986
        console_print(u"Dropbox isn't running!")
 
987
 
 
988
@command
 
989
@requires_dropbox_running
 
990
def exclude(args):
 
991
    u"""ignores/excludes a directory from syncing
 
992
dropbox exclude [list]
 
993
dropbox exclude add [DIRECTORY] [DIRECTORY] ...
 
994
dropbox exclude remove [DIRECTORY] [DIRECTORY] ...
 
995
 
 
996
"list" prints a list of directories currently excluded from syncing.  
 
997
"add" adds one or more directories to the exclusion list, then resynchronizes Dropbox. 
 
998
"remove" removes one or more directories from the exclusion list, then resynchronizes Dropbox.
 
999
With no arguments, executes "list". 
 
1000
Any specified path must be within Dropbox.
 
1001
"""
 
1002
    if len(args) == 0:
 
1003
        try:
 
1004
            with closing(DropboxCommand()) as dc:
 
1005
                try:
 
1006
                    lines = [relpath(path) for path in dc.get_ignore_set()[u'ignore_set']]
 
1007
                    lines.sort()
 
1008
                    if len(lines) == 0:
 
1009
                        console_print(u'No directories are being ignored.') 
 
1010
                    else:
 
1011
                        console_print(u'Excluded: ') 
 
1012
                        for line in lines:
 
1013
                            console_print(unicode(line))
 
1014
                except KeyError:
 
1015
                    console_print(u"Couldn't get ignore set: daemon isn't responding")
 
1016
                except DropboxCommand.CommandError, e:
 
1017
                    if e.args[0].startswith(u"No command exists by that name"):
 
1018
                        console_print(u"This version of the client does not support this command.")
 
1019
                    else:
 
1020
                        console_print(u"Couldn't get ignore set: " + str(e))
 
1021
                except DropboxCommand.BadConnectionError, e:
 
1022
                    console_print(u"Dropbox isn't responding!")
 
1023
                except DropboxCommand.EOFError:
 
1024
                    console_print(u"Dropbox daemon stopped.")
 
1025
        except DropboxCommand.CouldntConnectError, e:
 
1026
            console_print(u"Dropbox isn't running!")
 
1027
    elif len(args) == 1 and args[0] == u"list":
 
1028
        exclude([])
 
1029
    elif len(args) >= 2:
 
1030
        sub_command = args[0]
 
1031
        paths = args[1:]
 
1032
        absolute_paths = [unicode_abspath(path.decode(sys.getfilesystemencoding())) for path in paths]
 
1033
        if sub_command == u"add": 
 
1034
            try:
 
1035
                with closing(DropboxCommand(timeout=None)) as dc:
 
1036
                    try: 
 
1037
                        result = dc.ignore_set_add(paths=absolute_paths)
 
1038
                        if result[u"ignored"]:
 
1039
                            console_print(u"Excluded: ") 
 
1040
                            lines = [relpath(path) for path in result[u"ignored"]]
 
1041
                            for line in lines:
 
1042
                                console_print(unicode(line))
 
1043
                    except KeyError:
 
1044
                        console_print(u"Couldn't add ignore path: daemon isn't responding")
 
1045
                    except DropboxCommand.CommandError, e:
 
1046
                        if e.args[0].startswith(u"No command exists by that name"):
 
1047
                            console_print(u"This version of the client does not support this command.")
 
1048
                        else:
 
1049
                            console_print(u"Couldn't get ignore set: " + str(e))
 
1050
                    except DropboxCommand.BadConnectionError, e:
 
1051
                        console_print(u"Dropbox isn't responding! [%s]" % e)
 
1052
                    except DropboxCommand.EOFError:
 
1053
                        console_print(u"Dropbox daemon stopped.")
 
1054
            except DropboxCommand.CouldntConnectError, e:
 
1055
                console_print(u"Dropbox isn't running!")
 
1056
        elif sub_command == u"remove":
 
1057
            try:
 
1058
                with closing(DropboxCommand(timeout=None)) as dc:
 
1059
                    try:
 
1060
                        result = dc.ignore_set_remove(paths=absolute_paths)
 
1061
                        if result[u"removed"]:
 
1062
                            console_print(u"No longer excluded: ") 
 
1063
                            lines = [relpath(path) for path in result[u"removed"]]
 
1064
                            for line in lines:
 
1065
                                console_print(unicode(line))
 
1066
                    except KeyError:
 
1067
                        console_print(u"Couldn't remove ignore path: daemon isn't responding")
 
1068
                    except DropboxCommand.CommandError, e:
 
1069
                        if e.args[0].startswith(u"No command exists by that name"):
 
1070
                            console_print(u"This version of the client does not support this command.")
 
1071
                        else:
 
1072
                            console_print(u"Couldn't get ignore set: " + str(e))
 
1073
                    except DropboxCommand.BadConnectionError, e:
 
1074
                        console_print(u"Dropbox isn't responding! [%s]" % e)
 
1075
                    except DropboxCommand.EOFError:
 
1076
                        console_print(u"Dropbox daemon stopped.")
 
1077
            except DropboxCommand.CouldntConnectError, e:
 
1078
                console_print(u"Dropbox isn't running!")
 
1079
        else: 
 
1080
            console_print(exclude.__doc__, linebreak=False)
 
1081
            return
 
1082
    else:
 
1083
        console_print(exclude.__doc__, linebreak=False)
 
1084
        return
 
1085
 
 
1086
@command
 
1087
def start(argv):
 
1088
    u"""start dropboxd
 
1089
dropbox start [-i]
 
1090
    
 
1091
Starts the dropbox daemon, dropboxd. If dropboxd is already running, this will do nothing.
 
1092
 
 
1093
options:
 
1094
  -i --install  auto install dropboxd if not available on the system
 
1095
"""
 
1096
 
 
1097
    should_install = "-i" in argv or "--install" in argv
 
1098
    
 
1099
    # first check if dropbox is already running
 
1100
    if is_dropbox_running():
 
1101
        if not grab_link_url_if_necessary():
 
1102
            console_print(u"Dropbox is already running!")
 
1103
        return
 
1104
 
 
1105
    console_print(u"Starting Dropbox...", linebreak=False)
 
1106
    console_flush()
 
1107
    if not start_dropbox():
 
1108
        if not should_install:
 
1109
            console_print()
 
1110
            console_print(u"The Dropbox daemon is not installed!")
 
1111
            console_print(u"Run \"dropbox start -i\" to install the daemon")
 
1112
            return
 
1113
 
 
1114
        # install dropbox!!!
 
1115
        try:
 
1116
            download()
 
1117
        except:
 
1118
            pass
 
1119
        else:
 
1120
            if GUI_AVAILABLE:
 
1121
                start_dropbox()
 
1122
                console_print(u"Done!")
 
1123
            else:
 
1124
                if start_dropbox():
 
1125
                    if not grab_link_url_if_necessary():
 
1126
                        console_print(u"Done!")
 
1127
    else:
 
1128
        if not grab_link_url_if_necessary():
 
1129
            console_print(u"Done!")
 
1130
 
 
1131
 
 
1132
def can_reroll_autostart():
 
1133
    return u".config" in os.listdir(os.path.expanduser(u'~'))
 
1134
 
 
1135
def reroll_autostart(should_autostart):
 
1136
    home_dir = os.path.expanduser(u'~')
 
1137
    contents = os.listdir(home_dir)
 
1138
 
 
1139
    # UBUNTU
 
1140
    if u".config" in contents:
 
1141
        autostart_dir = os.path.join(home_dir, u".config", u"autostart")
 
1142
        autostart_link = os.path.join(autostart_dir, u"%s.desktop" % "dropbox") #BUILD_KEY.lower()
 
1143
        desktop_file = u"/usr/share/applications/%s.desktop" % "dropbox" #BUILD_KEY.lower()
 
1144
        if should_autostart:
 
1145
            if os.path.exists(desktop_file):
 
1146
                if not os.path.exists(autostart_dir):
 
1147
                    os.makedirs(autostart_dir)
 
1148
                shutil.copyfile(desktop_file, autostart_link)
 
1149
        elif os.path.exists(autostart_link):
 
1150
            os.remove(autostart_link)
 
1151
 
 
1152
 
 
1153
 
 
1154
@command
 
1155
def autostart(argv):
 
1156
    u"""automatically start dropbox at login
 
1157
dropbox autostart [y/n]
 
1158
 
 
1159
options:
 
1160
  n  dropbox will not start automatically at login
 
1161
  y  dropbox will start automatically at login (default)
 
1162
 
 
1163
Note: May only work on current Ubuntu distributions.
 
1164
"""
 
1165
    if len(argv) != 1:
 
1166
        console_print(''.join(autostart.__doc__.split('\n', 1)[1:]).decode('ascii'))
 
1167
        return
 
1168
 
 
1169
    s = argv[0].lower()
 
1170
    if s.startswith('y') or s.startswith('-y'):
 
1171
        should_autostart = True
 
1172
    elif s.startswith('n') or s.startswith('-n'):
 
1173
        should_autostart = False
 
1174
    else:
 
1175
        should_autostart = None
 
1176
 
 
1177
    if should_autostart is None:
 
1178
        console_print(autostart.__doc__,linebreak=False)
 
1179
    else:
 
1180
        reroll_autostart(should_autostart)
 
1181
 
 
1182
@command
 
1183
def help(argv):
 
1184
    u"""provide help
 
1185
dropbox help [COMMAND]
 
1186
 
 
1187
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.
 
1188
"""
 
1189
    if not argv:
 
1190
        return usage(argv)
 
1191
    for command in commands:
 
1192
        if command == argv[0]:
 
1193
            console_print(commands[command].__doc__.split('\n', 1)[1].decode('ascii'))
 
1194
            return
 
1195
    for alias in aliases:
 
1196
        if alias == argv[0]:
 
1197
            console_print(aliases[alias].__doc__.split('\n', 1)[1].decode('ascii'))
 
1198
            return
 
1199
    console_print(u"unknown command '%s'" % argv[0], f=sys.stderr)
 
1200
 
 
1201
def usage(argv):
 
1202
    console_print(u"Dropbox command-line interface\n")
 
1203
    console_print(u"commands:\n")
 
1204
    console_print(u"Note: use dropbox help <command> to view usage for a specific command.\n")
 
1205
    out = []
 
1206
    for command in commands:
 
1207
        out.append((command, commands[command].__doc__.splitlines()[0]))
 
1208
    spacing = max(len(o[0])+3 for o in out)
 
1209
    for o in out:
 
1210
        console_print(" %-*s%s" % (spacing, o[0], o[1]))
 
1211
    console_print()
 
1212
    
 
1213
def main(argv):
 
1214
    global commands
 
1215
 
 
1216
    # now we need to find out if one of the commands are in the
 
1217
    # argv list, and if so split the list at the point to
 
1218
    # separate the argv list at that point
 
1219
    cut = None
 
1220
    for i in range(len(argv)):
 
1221
        if argv[i] in commands or argv[i] in aliases:
 
1222
            cut = i
 
1223
            break
 
1224
    
 
1225
    if cut == None:
 
1226
        usage(argv)
 
1227
        os._exit(0)
 
1228
        return
 
1229
 
 
1230
    # lol no options for now
 
1231
    globaloptionparser = optparse.OptionParser()
 
1232
    globaloptionparser.parse_args(argv[0:i])
 
1233
 
 
1234
    # now dispatch and run
 
1235
    result = None
 
1236
    if argv[i] in commands:
 
1237
        result = commands[argv[i]](argv[i+1:])
 
1238
    elif argv[i] in aliases:
 
1239
        result = aliases[argv[i]](argv[i+1:])
 
1240
 
 
1241
    # flush, in case output is rerouted to a file. 
 
1242
    console_flush()
 
1243
 
 
1244
    # done
 
1245
    return result
 
1246
 
 
1247
if __name__ == "__main__":
 
1248
    ret = main(sys.argv)
 
1249
    if ret is not None:
 
1250
        sys.exit(ret)