1597 lines
116 KiB
Python
1597 lines
116 KiB
Python
|
#!/usr/bin/env python3
|
||
|
#
|
||
|
# Copyright (c) Dropbox, Inc.
|
||
|
#
|
||
|
# dropbox
|
||
|
# Dropbox frontend script
|
||
|
# This file is part of nautilus-dropbox 2019.02.14.
|
||
|
#
|
||
|
# 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 locale
|
||
|
import optparse
|
||
|
import os
|
||
|
import platform
|
||
|
import shutil
|
||
|
import socket
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tarfile
|
||
|
import tempfile
|
||
|
import threading
|
||
|
import _thread
|
||
|
import time
|
||
|
import traceback
|
||
|
import urllib.request
|
||
|
|
||
|
try:
|
||
|
import gpg
|
||
|
gpgme = None
|
||
|
except ImportError:
|
||
|
gpg = None
|
||
|
# Still support gpgme for now. Remove this once we only support 17.04+.
|
||
|
try:
|
||
|
import gpgme
|
||
|
except ImportError:
|
||
|
gpgme = None
|
||
|
|
||
|
from contextlib import closing, contextmanager
|
||
|
from io import BytesIO
|
||
|
from operator import methodcaller
|
||
|
from os.path import relpath
|
||
|
from posixpath import curdir, sep, pardir, join, abspath, commonprefix
|
||
|
|
||
|
INFO = "Dropbox is the easiest way to share and store your files online. Want to learn more? Head to"
|
||
|
LINK = "https://www.dropbox.com/"
|
||
|
WARNING = "In order to use Dropbox, you must download the proprietary daemon."
|
||
|
GPG_WARNING = "Note: python3-gpg (python3-gpgme for Ubuntu 16.10 and lower) is not installed, we will not be able to verify binary signatures."
|
||
|
ERROR_CONNECTING = "Trouble connecting to Dropbox servers. Maybe your internet connection is down, or you need to set your http_proxy environment variable."
|
||
|
ERROR_SIGNATURE = "Downloaded binary does not match Dropbox signature, aborting install."
|
||
|
ERROR_INVALID_DROPBOX = "Could not start the Dropbox daemon. Make sure your computer meets the minimum requirements:\nhttps://www.dropbox.com/help/desktop-web/system-requirements#desktop"
|
||
|
|
||
|
DOWNLOAD_LOCATION_FMT = "https://www.dropbox.com/download?plat=%s"
|
||
|
SIGNATURE_LOCATION_FMT = "https://www.dropbox.com/download?plat=%s&signature=1"
|
||
|
|
||
|
DOWNLOADING = "Downloading Dropbox... %d%%"
|
||
|
UNPACKING = "Unpacking Dropbox... %d%%"
|
||
|
|
||
|
PARENT_DIR = os.path.expanduser("~")
|
||
|
DROPBOX_DIST_PATH = "%s/.dropbox-dist" % PARENT_DIR
|
||
|
DROPBOXD_PATH = os.path.join(DROPBOX_DIST_PATH, "dropboxd")
|
||
|
DESKTOP_FILE = "/usr/share/applications/dropbox.desktop"
|
||
|
|
||
|
enc = locale.getpreferredencoding()
|
||
|
|
||
|
# Available from https://linux.dropbox.com/fedora/rpm-public-key.asc
|
||
|
DROPBOX_PUBLIC_KEY = b"""
|
||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||
|
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-----
|
||
|
"""
|
||
|
|
||
|
|
||
|
def console_print(st="", f=sys.stdout, linebreak=True):
|
||
|
f.write(st)
|
||
|
if linebreak: f.write(os.linesep)
|
||
|
|
||
|
def console_flush(f=sys.stdout):
|
||
|
f.flush()
|
||
|
|
||
|
def yes_no_question(question):
|
||
|
while True:
|
||
|
console_print(question, linebreak=False)
|
||
|
console_print(" [y/n] ", linebreak=False)
|
||
|
console_flush()
|
||
|
text = input()
|
||
|
if text.lower().startswith("y"):
|
||
|
return True
|
||
|
elif text.lower().startswith("n"):
|
||
|
return False
|
||
|
else:
|
||
|
console_print("Sorry, I didn't understand that. Please type yes or no.")
|
||
|
|
||
|
def plat():
|
||
|
if sys.platform.lower().startswith('linux'):
|
||
|
arch = platform.machine()
|
||
|
if (arch[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
|
||
|
|
||
|
@contextmanager
|
||
|
def gpg_context(keys):
|
||
|
gpg_conf_contents = b''
|
||
|
_gpghome = tempfile.mkdtemp(prefix='tmp.gpghome')
|
||
|
|
||
|
try:
|
||
|
os.environ['GNUPGHOME'] = _gpghome
|
||
|
fp = open(os.path.join(_gpghome, 'gpg.conf'), 'wb')
|
||
|
fp.write(gpg_conf_contents)
|
||
|
fp.close()
|
||
|
if gpg:
|
||
|
ctx = gpg.Context()
|
||
|
else:
|
||
|
ctx = gpgme.Context()
|
||
|
|
||
|
loaded = []
|
||
|
for key_file in keys:
|
||
|
if gpg:
|
||
|
ctx.op_import(key_file.read())
|
||
|
result = ctx.op_import_result()
|
||
|
key = ctx.get_key(result.imports[0].fpr)
|
||
|
else:
|
||
|
result = ctx.import_(key_file)
|
||
|
key = ctx.get_key(result.imports[0][0])
|
||
|
loaded.append(key)
|
||
|
|
||
|
ctx.signers = loaded
|
||
|
|
||
|
yield ctx
|
||
|
finally:
|
||
|
del os.environ['GNUPGHOME']
|
||
|
shutil.rmtree(_gpghome, ignore_errors=True)
|
||
|
|
||
|
class SignatureVerifyError(Exception):
|
||
|
pass
|
||
|
|
||
|
def verify_signature(key_file, sig_file, plain_file):
|
||
|
with gpg_context([key_file]) as ctx:
|
||
|
if gpg:
|
||
|
ctx.op_verify(sig_file.read(), plain_file.read(), None)
|
||
|
result = ctx.op_verify_result()
|
||
|
return result.signatures[0].status == 0
|
||
|
# gpgme exists
|
||
|
sigs = ctx.verify(sig_file, plain_file, None)
|
||
|
return sigs[0].status == None
|
||
|
|
||
|
|
||
|
def download_file_chunk(url, buf):
|
||
|
opener = urllib.request.build_opener()
|
||
|
opener.addheaders = [('User-Agent', "DropboxLinuxDownloader/2019.02.14")]
|
||
|
|
||
|
with closing(opener.open(url)) as f:
|
||
|
size = int(f.info()['content-length'])
|
||
|
bufsize = int(max(size / 200, 4096))
|
||
|
progress = 0
|
||
|
yield (0, True)
|
||
|
while True:
|
||
|
try:
|
||
|
chunk = f.read(bufsize)
|
||
|
progress += len(chunk)
|
||
|
buf.write(chunk)
|
||
|
yield (float(progress)/size, True)
|
||
|
if progress == size:
|
||
|
break
|
||
|
except OSError as e:
|
||
|
if hasattr(e, 'errno') and e.errno == errno.EAGAIN:
|
||
|
# nothing left to read
|
||
|
yield (float(progress)/size, False)
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
class DownloadState(object):
|
||
|
def __init__(self):
|
||
|
self.local_file = BytesIO()
|
||
|
|
||
|
def copy_data(self):
|
||
|
return download_file_chunk(DOWNLOAD_LOCATION_FMT % plat(), self.local_file)
|
||
|
|
||
|
def unpack(self):
|
||
|
# download signature
|
||
|
signature = BytesIO()
|
||
|
for _ in download_file_chunk(SIGNATURE_LOCATION_FMT % plat(), signature):
|
||
|
pass
|
||
|
signature.seek(0)
|
||
|
self.local_file.seek(0)
|
||
|
|
||
|
if gpg or gpgme:
|
||
|
if not verify_signature(BytesIO(DROPBOX_PUBLIC_KEY), signature, self.local_file):
|
||
|
raise SignatureVerifyError()
|
||
|
|
||
|
self.local_file.seek(0)
|
||
|
archive = tarfile.open(fileobj=self.local_file, mode='r:gz')
|
||
|
total_members = len(archive.getmembers())
|
||
|
for i, member in enumerate(archive.getmembers()):
|
||
|
filename = os.path.join(PARENT_DIR, member.name)
|
||
|
if os.path.exists(filename) and not os.path.isdir(filename):
|
||
|
os.unlink(filename)
|
||
|
archive.extract(member, PARENT_DIR)
|
||
|
yield member.name, i, total_members
|
||
|
archive.close()
|
||
|
|
||
|
def cancel(self):
|
||
|
if not self.local_file.closed:
|
||
|
self.local_file.close()
|
||
|
|
||
|
def is_dropbox_valid(self):
|
||
|
"""
|
||
|
Validate that Dropbox runs, so we can show an error
|
||
|
message to the user if it doesn't work.
|
||
|
|
||
|
Returns True if Dropbox can run, false otherwise.
|
||
|
"""
|
||
|
f = open("/dev/null", "w")
|
||
|
try:
|
||
|
a = subprocess.Popen([DROPBOXD_PATH, "/testrun", "0"], preexec_fn=os.setsid, cwd=os.path.expanduser("~"),
|
||
|
stderr=sys.stderr, stdout=f, close_fds=True)
|
||
|
except Exception as e:
|
||
|
print(e)
|
||
|
return False
|
||
|
|
||
|
# in seconds
|
||
|
interval = 0.5
|
||
|
wait_for = 30
|
||
|
for _ in range(int(wait_for / interval)):
|
||
|
ret_val = a.poll()
|
||
|
if ret_val is None:
|
||
|
time.sleep(interval)
|
||
|
continue
|
||
|
return ret_val == 0
|
||
|
|
||
|
return False
|
||
|
|
||
|
|
||
|
def load_serialized_images():
|
||
|
global box_logo_pixbuf, window_icon
|
||
|
import gi
|
||
|
gi.require_version('GdkPixbuf', '2.0')
|
||
|
from gi.repository import GdkPixbuf
|
||
|
box_logo_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
|
||
|
window_icon = GdkPixbuf.Pixbuf.new_from_data(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00f\xff2\x00d\xff3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xff3\x00d\xff3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\xff\x0c\x00b\xff\x8f\x00b\xff\xfc\x00b\xff\xfc\x00b\xff\x8f\x00j\xff\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00j\xff\x0c\x00b\xff\x8f\x00b\xff\xfc\x00b\xff\xfc\x00b\xff\x8f\x00j\xff\x0c\x00\x00\x00\x00\x00b\xffN\x00b\xff\xe2\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xe2\x00d\xffO\x00b\xffN\x00b\xff\xe2\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xe2\x00b\xffN\x00e\xffQ\x00b\xff\xe4\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xe4\x00d\xffR\x00e\xffQ\x00b\xff\xe4\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xe4\x00e\xffQ\x00\x00\x00\x00\x00b\xff\r\x00d\xff\x92\x00c\xff\xfd\x00c\xff\xfd\x00d\xff\x92\x00b\xff\r\x00\x00\x00\x00\x00\x00\x00\x00\x00b\xff\r\x00d\xff\x92\x00c\xff\xfd\x00c\xff\xfd\x00d\xff\x92\x00b\xff\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b\xffe\x00d\xfff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xfff\x00d\xfff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\xff\x0b\x00b\xff\x8c\x00b\xff\xfc\x00b\xff\xfc\x00c\xff\x8d\x00t\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00t\xff\x0b\x00b\xff\x8c\x00b\xff\xfc\x00b\xff\xfc\x00b\xff\x8c\x00t\xff\x0b\x00\x00\x00\x00\x00c\xffK\x00c\xff\xe0\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xe1\x00e\xffL\x00e\xffL\x00c\xff\xe1\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xe1\x00e\xffL\x00d\xffT\x00c\xff\xe5\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xe6\x00c\xffU\x00d\xffT\x00c\xff\xe6\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xe6\x00d\xffT\x00\x00\x00\x00\x00m\xff\x0e\x00b\xff\x94\x00c\xff\xfd\x00c\xff\xfd\x00c\xff\x95\x00i\xff\x11\x00c\xffj\x00d\xffk\x00i\xff\x11\x00c\xff\x95\x00c\xff\xfd\x00c\xff\xfd\x00c\xff\x95\x00m\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00f\xff7\x00d\xff8\x00d\xff.\x00b\xff\xc8\x00b\xff\xff\x00b\xff\xff\x00b\xff\xc8\x00g\xff/\x00f\xff7\x00f\xff7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b\xff\x7f\x00c\xff\xfb\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xfb\x00d\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\xff \x00b\xff\xb6\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00b\xff\xff\x00c\xff\xb7\x00d\xff!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\xffW\x00b\xff\xe7\x00b\xff\xe7\x00d\xffW\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00f\xff\x0f\x00p\xff\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', GdkPixbuf.Colorspace.RGB, True, 8, 16, 16, 64)
|
||
|
|
||
|
GUI_AVAILABLE = os.environ.get("DISPLAY", '')
|
||
|
|
||
|
if GUI_AVAILABLE:
|
||
|
def download():
|
||
|
import gi
|
||
|
gi.require_version('Gdk', '3.0')
|
||
|
gi.require_version('Gtk', '3.0')
|
||
|
from gi.repository import GObject
|
||
|
from gi.repository import Gdk
|
||
|
from gi.repository import Gtk
|
||
|
from gi.repository import Pango
|
||
|
import webbrowser
|
||
|
|
||
|
GObject.threads_init()
|
||
|
|
||
|
load_serialized_images()
|
||
|
|
||
|
global FatalVisibleError
|
||
|
def FatalVisibleError(s):
|
||
|
error = Gtk.MessageDialog(parent = None,
|
||
|
flags = Gtk.DialogFlags.MODAL,
|
||
|
type = Gtk.MessageType.ERROR,
|
||
|
buttons = Gtk.ButtonsType.OK,
|
||
|
message_format = s)
|
||
|
error.set_title("Error")
|
||
|
error.run()
|
||
|
Gtk.main_quit()
|
||
|
sys.exit(-1)
|
||
|
|
||
|
class GeneratorTask(object):
|
||
|
def __init__(self, generator, loop_callback, on_done=None, on_exception=None):
|
||
|
self.generator = generator
|
||
|
self.loop_callback = loop_callback
|
||
|
self.on_done = on_done
|
||
|
self.on_exception = on_exception
|
||
|
|
||
|
def _run(self, *args, **kwargs):
|
||
|
self._stopped = False
|
||
|
try:
|
||
|
for ret in self.generator(*args, **kwargs):
|
||
|
if ret is None:
|
||
|
ret = ()
|
||
|
if not isinstance(ret, tuple):
|
||
|
ret = (ret,)
|
||
|
GObject.idle_add(self.loop_callback, *ret)
|
||
|
|
||
|
if self._stopped:
|
||
|
_thread.exit()
|
||
|
except Exception as e:
|
||
|
print(e)
|
||
|
if self.on_exception is not None:
|
||
|
GObject.idle_add(self.on_exception, e)
|
||
|
else:
|
||
|
if self.on_done is not None:
|
||
|
GObject.idle_add(self.on_done)
|
||
|
|
||
|
def start(self, *args, **kwargs):
|
||
|
t = threading.Thread(target=self._run, args=args, kwargs=kwargs)
|
||
|
t.setDaemon(True)
|
||
|
t.start()
|
||
|
|
||
|
def stop(self):
|
||
|
self._stopped = True
|
||
|
|
||
|
class DownloadDialog(Gtk.Dialog):
|
||
|
def handle_delete_event(self, wid, ev, data=None):
|
||
|
self.handle_cancel(wid)
|
||
|
|
||
|
def handle_dont_show_toggle(self, button, data=None):
|
||
|
reroll_autostart(not button.get_active())
|
||
|
|
||
|
def handle_cancel(self, button):
|
||
|
if self.task:
|
||
|
self.task.stop()
|
||
|
if self.download:
|
||
|
self.download.cancel()
|
||
|
Gtk.main_quit()
|
||
|
self.user_cancelled = True
|
||
|
|
||
|
def handle_ok(self, button):
|
||
|
# begin download
|
||
|
self.ok.hide()
|
||
|
self.download = DownloadState()
|
||
|
|
||
|
self.label.hide()
|
||
|
if self.dont_show_again_align is not None:
|
||
|
self.dont_show_again_align.hide()
|
||
|
self.progress.show()
|
||
|
|
||
|
def download_progress(progress, status):
|
||
|
if not status:
|
||
|
self.task.stop()
|
||
|
self.update_progress(DOWNLOADING, progress)
|
||
|
|
||
|
def finished():
|
||
|
self.update_progress(DOWNLOADING, 1.0)
|
||
|
self.unpack_dropbox()
|
||
|
|
||
|
def error(ex):
|
||
|
FatalVisibleError(ERROR_CONNECTING)
|
||
|
|
||
|
self.update_progress(DOWNLOADING, 0)
|
||
|
self.task = GeneratorTask(self.download.copy_data,
|
||
|
download_progress,
|
||
|
finished, error).start()
|
||
|
|
||
|
def update_progress(self, text, fraction):
|
||
|
self.progress.set_text(text % int(fraction*100))
|
||
|
self.progress.set_fraction(fraction)
|
||
|
|
||
|
def unpack_dropbox(self):
|
||
|
def unpack_progress(name, i, total):
|
||
|
self.update_progress(UNPACKING, float(i)/total)
|
||
|
|
||
|
def finished():
|
||
|
self.update_progress(UNPACKING, 1.0)
|
||
|
if not self.download.is_dropbox_valid():
|
||
|
FatalVisibleError(ERROR_INVALID_DROPBOX)
|
||
|
Gtk.main_quit()
|
||
|
|
||
|
def error(ex):
|
||
|
if isinstance(ex, SignatureVerifyError):
|
||
|
FatalVisibleError(ERROR_SIGNATURE)
|
||
|
else:
|
||
|
FatalVisibleError(ERROR_CONNECTING)
|
||
|
|
||
|
self.task = GeneratorTask(self.download.unpack,
|
||
|
unpack_progress,
|
||
|
finished, error).start()
|
||
|
|
||
|
def mouse_down(self, widget, event):
|
||
|
if self.hovering:
|
||
|
self.clicked_link = True
|
||
|
|
||
|
def mouse_up(self, widget, event):
|
||
|
if self.clicked_link:
|
||
|
webbrowser.open(LINK)
|
||
|
self.clicked_link = False
|
||
|
|
||
|
def label_motion(self, widget, event):
|
||
|
offx, offy = self.label.get_layout_offsets()
|
||
|
layout = self.label.get_layout()
|
||
|
index = layout.xy_to_index(int((offx+event.x)*Pango.SCALE),
|
||
|
int((offy+event.y)*Pango.SCALE))[1]
|
||
|
link_index = layout.get_text().find(LINK)
|
||
|
if index >= link_index and index < link_index+len(LINK):
|
||
|
self.hovering = True
|
||
|
self.label_box.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
|
||
|
else:
|
||
|
self.hovering = False
|
||
|
self.label_box.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW))
|
||
|
|
||
|
|
||
|
def __init__(self):
|
||
|
super(DownloadDialog, self).__init__(parent = None,
|
||
|
title = "Dropbox Installation")
|
||
|
|
||
|
self.download = None
|
||
|
self.hovering = False
|
||
|
self.clicked_link = False
|
||
|
self.user_cancelled = False
|
||
|
self.task = None
|
||
|
|
||
|
self.ok = ok = Gtk.Button(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.progress.set_property('show-text', True)
|
||
|
|
||
|
self.label = Gtk.Label()
|
||
|
GPG_WARNING_MSG = ("\n\n" + GPG_WARNING) if not gpg and not gpgme else ""
|
||
|
self.label.set_markup('%s <span foreground="#000099" underline="single" weight="bold">%s</span>\n\n%s%s' % (INFO, LINK, WARNING, GPG_WARNING_MSG))
|
||
|
self.label.set_line_wrap(True)
|
||
|
self.label.set_property('width-request', 300)
|
||
|
self.label.show()
|
||
|
|
||
|
self.label_box = Gtk.EventBox()
|
||
|
self.label_box.add(self.label)
|
||
|
self.label_box.connect("button-release-event", self.mouse_up)
|
||
|
self.label_box.connect("button-press-event", self.mouse_down)
|
||
|
self.label_box.connect("motion-notify-event", self.label_motion)
|
||
|
|
||
|
self.label_box.show()
|
||
|
def on_realize(widget):
|
||
|
self.label_box.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
|
||
|
self.label_box.connect("realize", on_realize)
|
||
|
|
||
|
self.hbox = Gtk.HBox(spacing=10)
|
||
|
self.hbox.set_property('border-width',10)
|
||
|
self.hbox.pack_start(self.box_logo, False, False, 0)
|
||
|
self.hbox.pack_start(self.label_box, False, False, 0)
|
||
|
self.hbox.pack_start(self.progress, False, False, 0)
|
||
|
self.hbox.show()
|
||
|
|
||
|
self.vbox.add(self.hbox)
|
||
|
|
||
|
self.dont_show_again_align = None
|
||
|
|
||
|
try:
|
||
|
if can_reroll_autostart():
|
||
|
dont_show_again = Gtk.CheckButton.new_with_mnemonic("_Don't show this again")
|
||
|
dont_show_again.connect('toggled', self.handle_dont_show_toggle)
|
||
|
dont_show_again.show()
|
||
|
|
||
|
self.dont_show_again_align = Gtk.Alignment(xalign=1.0, yalign=0.0, xscale=0.0, yscale=0.0)
|
||
|
self.dont_show_again_align.add(dont_show_again)
|
||
|
self.dont_show_again_align.show()
|
||
|
|
||
|
hbox = Gtk.HBox()
|
||
|
hbox.set_property('border-width', 10)
|
||
|
hbox.pack_start(self.dont_show_again_align, True, True, 0)
|
||
|
hbox.show()
|
||
|
|
||
|
self.vbox.add(hbox)
|
||
|
|
||
|
self.set_resizable(False)
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
|
||
|
self.ok.grab_focus()
|
||
|
|
||
|
dialog = DownloadDialog()
|
||
|
dialog.show()
|
||
|
Gtk.main()
|
||
|
if dialog.user_cancelled:
|
||
|
raise Exception("user cancelled download!!!")
|
||
|
else:
|
||
|
def download():
|
||
|
global FatalVisibleError
|
||
|
def FatalVisibleError(s):
|
||
|
console_print("\nError: %s" % s, f=sys.stderr)
|
||
|
sys.exit(-1)
|
||
|
|
||
|
|
||
|
ESC = "\x1b"
|
||
|
save = ESC+"7"
|
||
|
unsave = ESC+"8"
|
||
|
erase_to_start = ESC+"[1K"
|
||
|
write = sys.stdout.write
|
||
|
flush = sys.stdout.flush
|
||
|
|
||
|
last_progress = [None, None]
|
||
|
def setprogress(text, frac):
|
||
|
if last_progress == [text, frac]:
|
||
|
return
|
||
|
if sys.stdout.isatty():
|
||
|
write(erase_to_start)
|
||
|
write(unsave)
|
||
|
console_print(text % int(100*frac), linebreak=not sys.stdout.isatty())
|
||
|
if sys.stdout.isatty():
|
||
|
flush()
|
||
|
last_progress[0], last_progress[1] = text, frac
|
||
|
|
||
|
console_print()
|
||
|
if sys.stdout.isatty():
|
||
|
write(save)
|
||
|
flush()
|
||
|
console_print("%s %s\n" % (INFO, LINK))
|
||
|
GPG_WARNING_MSG = ("\n%s" % GPG_WARNING) if not gpg and not gpgme else ""
|
||
|
|
||
|
if not yes_no_question("%s%s" % (WARNING, GPG_WARNING_MSG)):
|
||
|
return
|
||
|
|
||
|
download = DownloadState()
|
||
|
|
||
|
try:
|
||
|
for progress, status in download.copy_data():
|
||
|
if not status:
|
||
|
break
|
||
|
setprogress(DOWNLOADING, progress)
|
||
|
except Exception:
|
||
|
traceback.print_exc()
|
||
|
FatalVisibleError(ERROR_CONNECTING)
|
||
|
else:
|
||
|
setprogress(DOWNLOADING, 1.0)
|
||
|
console_print()
|
||
|
write(save)
|
||
|
|
||
|
try:
|
||
|
for _, i, total in download.unpack():
|
||
|
setprogress(UNPACKING, float(i)/total)
|
||
|
except SignatureVerifyError:
|
||
|
traceback.print_exc()
|
||
|
FatalVisibleError(ERROR_SIGNATURE)
|
||
|
except Exception:
|
||
|
traceback.print_exc()
|
||
|
FatalVisibleError(ERROR_CONNECTING)
|
||
|
else:
|
||
|
setprogress(UNPACKING, 1.0)
|
||
|
|
||
|
if not download.is_dropbox_valid():
|
||
|
FatalVisibleError(ERROR_INVALID_DROPBOX)
|
||
|
|
||
|
console_print()
|
||
|
|
||
|
class CommandTicker(threading.Thread):
|
||
|
def __init__(self):
|
||
|
threading.Thread.__init__(self)
|
||
|
self.stop_event = threading.Event()
|
||
|
|
||
|
def stop(self):
|
||
|
self.stop_event.set()
|
||
|
|
||
|
def run(self):
|
||
|
ticks = ['[. ]', '[.. ]', '[...]', '[ ..]', '[ .]', '[ ]']
|
||
|
i = 0
|
||
|
first = True
|
||
|
while True:
|
||
|
self.stop_event.wait(0.25)
|
||
|
if self.stop_event.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('~/.dropbox/command_socket'))
|
||
|
except socket.error:
|
||
|
raise DropboxCommand.CouldntConnectError()
|
||
|
self.f = self.s.makefile("rw", 4096)
|
||
|
|
||
|
def close(self):
|
||
|
self.f.close()
|
||
|
self.s.close()
|
||
|
|
||
|
def __readline(self):
|
||
|
try:
|
||
|
toret = self.f.readline().rstrip("\n")
|
||
|
except socket.error:
|
||
|
raise DropboxCommand.BadConnectionError()
|
||
|
if toret == '':
|
||
|
raise DropboxCommand.EOFError()
|
||
|
else:
|
||
|
return toret
|
||
|
|
||
|
# atttribute doesn't exist, i know what you want
|
||
|
def send_command(self, name, args):
|
||
|
self.f.write(name)
|
||
|
self.f.write("\n")
|
||
|
self.f.writelines(("\t".join([k] + ([v]
|
||
|
if isinstance(v, str) else
|
||
|
list(v))) + "\n")
|
||
|
for k,v in args.items())
|
||
|
self.f.write("done\n")
|
||
|
|
||
|
self.f.flush()
|
||
|
|
||
|
# Start a ticker
|
||
|
ticker_thread = CommandTicker()
|
||
|
ticker_thread.start()
|
||
|
|
||
|
# This is the potentially long-running call.
|
||
|
try:
|
||
|
ok = self.__readline() == "ok"
|
||
|
except KeyboardInterrupt:
|
||
|
raise DropboxCommand.BadConnectionError("Keyboard interruption detected")
|
||
|
finally:
|
||
|
# Tell the ticker to stop.
|
||
|
ticker_thread.stop()
|
||
|
ticker_thread.join()
|
||
|
|
||
|
if ok:
|
||
|
toret = {}
|
||
|
for i in range(21):
|
||
|
if i == 20:
|
||
|
raise Exception("close this connection!")
|
||
|
|
||
|
line = self.__readline()
|
||
|
if line == "done":
|
||
|
break
|
||
|
|
||
|
argval = line.split("\t")
|
||
|
toret[argval[0]] = argval[1:]
|
||
|
|
||
|
return toret
|
||
|
else:
|
||
|
problems = []
|
||
|
for i in range(21):
|
||
|
if i == 20:
|
||
|
raise Exception("close this connection!")
|
||
|
|
||
|
line = self.__readline()
|
||
|
if line == "done":
|
||
|
break
|
||
|
|
||
|
problems.append(line)
|
||
|
|
||
|
raise DropboxCommand.CommandError("\n".join(problems))
|
||
|
|
||
|
# this is the hotness, auto marshalling
|
||
|
def __getattr__(self, name):
|
||
|
try:
|
||
|
return super(DropboxCommand, self).__getattr__(name)
|
||
|
except:
|
||
|
def __spec_command(**kw):
|
||
|
return self.send_command(str(name), kw)
|
||
|
self.__setattr__(name, __spec_command)
|
||
|
return __spec_command
|
||
|
|
||
|
commands = {}
|
||
|
aliases = {}
|
||
|
|
||
|
def command(meth):
|
||
|
global commands, aliases
|
||
|
assert meth.__doc__, "All commands need properly formatted docstrings (even %r!!)" % meth
|
||
|
if hasattr(meth, 'im_func'): # bound method, if we ever have one
|
||
|
meth = meth.im_func
|
||
|
commands[meth.__name__] = meth
|
||
|
meth_aliases = [str(alias) for alias in aliases.keys() if aliases[alias].__name__ == meth.__name__]
|
||
|
if meth_aliases:
|
||
|
meth.__doc__ += "\nAliases: %s" % ",".join(meth_aliases)
|
||
|
return meth
|
||
|
|
||
|
def alias(name):
|
||
|
def decorator(meth):
|
||
|
global commands, aliases
|
||
|
assert name not in commands, "This alias is the name of a command."
|
||
|
aliases[name] = meth
|
||
|
return meth
|
||
|
return decorator
|
||
|
|
||
|
def requires_dropbox_running(meth):
|
||
|
def newmeth(*n, **kw):
|
||
|
if is_dropbox_running():
|
||
|
return meth(*n, **kw)
|
||
|
else:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
newmeth.__name__ = meth.__name__
|
||
|
newmeth.__doc__ = meth.__doc__
|
||
|
return newmeth
|
||
|
|
||
|
def start_dropbox():
|
||
|
if os.access(DROPBOXD_PATH, os.X_OK):
|
||
|
f = open("/dev/null", "w")
|
||
|
# we don't reap the child because we're gonna die anyway, let init do it
|
||
|
subprocess.Popen([DROPBOXD_PATH], preexec_fn=os.setsid, cwd=os.path.expanduser("~"),
|
||
|
stderr=sys.stderr, stdout=f, close_fds=True)
|
||
|
|
||
|
# in seconds
|
||
|
interval = 0.5
|
||
|
wait_for = 60
|
||
|
for _ in range(int(wait_for / interval)):
|
||
|
if is_dropbox_running():
|
||
|
return True
|
||
|
# back off from connect for a while
|
||
|
time.sleep(interval)
|
||
|
|
||
|
return False
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
# Extracted and modified from os.cmd.Cmd
|
||
|
def columnize(list, display_list=None, display_width=None):
|
||
|
if not list:
|
||
|
console_print("<empty>")
|
||
|
return
|
||
|
|
||
|
non_str = [i for i in range(len(list)) if not (isinstance(list[i], str))]
|
||
|
if non_str:
|
||
|
raise TypeError("list[i] not a string for i in %s" %
|
||
|
", ".join(map(str, non_str)))
|
||
|
|
||
|
if not display_width:
|
||
|
d = os.popen('stty size', 'r').read().split()
|
||
|
if d:
|
||
|
display_width = int(d[1])
|
||
|
else:
|
||
|
for item in list:
|
||
|
console_print(item)
|
||
|
return
|
||
|
|
||
|
if not display_list:
|
||
|
display_list = list
|
||
|
|
||
|
size = len(list)
|
||
|
if size == 1:
|
||
|
console_print(display_list[0])
|
||
|
return
|
||
|
|
||
|
for nrows in range(1, len(list)):
|
||
|
ncols = (size+nrows-1) // nrows
|
||
|
colwidths = []
|
||
|
totwidth = -2
|
||
|
for col in range(ncols):
|
||
|
colwidth = 0
|
||
|
for row in range(nrows):
|
||
|
i = row + nrows*col
|
||
|
if i >= size:
|
||
|
break
|
||
|
x = list[i]
|
||
|
colwidth = max(colwidth, len(x))
|
||
|
colwidths.append(colwidth)
|
||
|
totwidth += colwidth + 2
|
||
|
if totwidth > display_width:
|
||
|
break
|
||
|
if totwidth <= display_width:
|
||
|
break
|
||
|
else:
|
||
|
nrows = len(list)
|
||
|
ncols = 1
|
||
|
colwidths = [0]
|
||
|
lines = []
|
||
|
for row in range(nrows):
|
||
|
texts = []
|
||
|
display_texts = []
|
||
|
for col in range(ncols):
|
||
|
i = row + nrows*col
|
||
|
if i >= size:
|
||
|
x = ""
|
||
|
y = ""
|
||
|
else:
|
||
|
x = list[i]
|
||
|
y = display_list[i]
|
||
|
texts.append(x)
|
||
|
display_texts.append(y)
|
||
|
while texts and not texts[-1]:
|
||
|
del texts[-1]
|
||
|
original_texts = texts[:]
|
||
|
for col in range(len(texts)):
|
||
|
texts[col] = texts[col].ljust(colwidths[col])
|
||
|
texts[col] = texts[col].replace(original_texts[col], display_texts[col])
|
||
|
line = " ".join(texts)
|
||
|
lines.append(line)
|
||
|
for line in lines:
|
||
|
console_print(line)
|
||
|
|
||
|
@command
|
||
|
def update(args):
|
||
|
"""download latest version of Dropbox
|
||
|
dropbox update
|
||
|
|
||
|
Downloads the latest version of Dropbox. This should not be required
|
||
|
normally, as Dropbox automatically updates itself.
|
||
|
"""
|
||
|
download()
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
@alias('stat')
|
||
|
def filestatus(args):
|
||
|
"""get current sync status of one or more files
|
||
|
dropbox filestatus [-l] [-a] [FILE]...
|
||
|
|
||
|
Prints the current status of each FILE.
|
||
|
|
||
|
options:
|
||
|
-l --list Prints out information in a format similar to ls. Works best when your console supports color :)
|
||
|
-a --all Do not ignore entries starting with "."
|
||
|
"""
|
||
|
global enc
|
||
|
|
||
|
oparser = optparse.OptionParser()
|
||
|
oparser.add_option("-l", "--list", action="store_true", dest="list")
|
||
|
oparser.add_option("-a", "--all", action="store_true", dest="all")
|
||
|
(options, args) = oparser.parse_args(args)
|
||
|
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
if options.list:
|
||
|
# Listing.
|
||
|
|
||
|
# Separate directories from files.
|
||
|
if len(args) == 0:
|
||
|
dirs, nondirs = ["."], []
|
||
|
else:
|
||
|
dirs, nondirs = [], []
|
||
|
|
||
|
for a in args:
|
||
|
try:
|
||
|
(dirs if os.path.isdir(a) else nondirs).append(a)
|
||
|
except UnicodeDecodeError:
|
||
|
continue
|
||
|
|
||
|
if len(dirs) == 0 and len(nondirs) == 0:
|
||
|
#TODO: why?
|
||
|
exit(1)
|
||
|
|
||
|
dirs.sort(key=methodcaller('lower'))
|
||
|
nondirs.sort(key=methodcaller('lower'))
|
||
|
|
||
|
# Gets a string representation for a path.
|
||
|
def path_to_string(file_path):
|
||
|
if not os.path.exists(file_path):
|
||
|
path = "%s (File doesn't exist!)" % os.path.basename(file_path)
|
||
|
return (path, path)
|
||
|
try:
|
||
|
status = dc.icon_overlay_file_status(path=file_path).get('status', [None])[0]
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
path = "%s (%s)" % (os.path.basename(file_path), e)
|
||
|
return (path, path)
|
||
|
|
||
|
env_term = os.environ.get('TERM','')
|
||
|
supports_color = (sys.stderr.isatty() and (
|
||
|
env_term.startswith('vt') or
|
||
|
env_term.startswith('linux') or
|
||
|
'xterm' in env_term or
|
||
|
'color' in env_term
|
||
|
)
|
||
|
)
|
||
|
|
||
|
# TODO: Test when you don't support color.
|
||
|
if not supports_color:
|
||
|
path = os.path.basename(file_path)
|
||
|
return (path, path)
|
||
|
|
||
|
if status == "up to date":
|
||
|
init, cleanup = "\x1b[32;1m", "\x1b[0m"
|
||
|
elif status == "syncing":
|
||
|
init, cleanup = "\x1b[36;1m", "\x1b[0m"
|
||
|
elif status == "unsyncable":
|
||
|
init, cleanup = "\x1b[41;1m", "\x1b[0m"
|
||
|
elif status == "selsync":
|
||
|
init, cleanup = "\x1b[37;1m", "\x1b[0m"
|
||
|
else:
|
||
|
init, cleanup = '', ''
|
||
|
|
||
|
path = os.path.basename(file_path)
|
||
|
return (path, "%s%s%s" % (init, path, cleanup))
|
||
|
|
||
|
# Prints a directory.
|
||
|
def print_directory(name):
|
||
|
clean_paths = []
|
||
|
formatted_paths = []
|
||
|
for subname in sorted(os.listdir(name), key=methodcaller('lower')):
|
||
|
if type(subname) != str:
|
||
|
continue
|
||
|
|
||
|
if not options.all and subname[0] == '.':
|
||
|
continue
|
||
|
|
||
|
try:
|
||
|
clean, formatted = path_to_string(os.path.abspath(os.path.join(name, subname)))
|
||
|
clean_paths.append(clean)
|
||
|
formatted_paths.append(formatted)
|
||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||
|
continue
|
||
|
|
||
|
columnize(clean_paths, formatted_paths)
|
||
|
|
||
|
try:
|
||
|
if len(dirs) == 1 and len(nondirs) == 0:
|
||
|
print_directory(dirs[0])
|
||
|
else:
|
||
|
nondir_formatted_paths = []
|
||
|
nondir_clean_paths = []
|
||
|
for name in nondirs:
|
||
|
try:
|
||
|
clean, formatted = path_to_string(os.path.abspath(name))
|
||
|
nondir_clean_paths.append(clean)
|
||
|
nondir_formatted_paths.append(formatted)
|
||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||
|
continue
|
||
|
|
||
|
if nondir_clean_paths:
|
||
|
columnize(nondir_clean_paths, nondir_formatted_paths)
|
||
|
|
||
|
if len(nondirs) == 0:
|
||
|
console_print(dirs[0] + ":")
|
||
|
print_directory(dirs[0])
|
||
|
dirs = dirs[1:]
|
||
|
|
||
|
for name in dirs:
|
||
|
console_print()
|
||
|
console_print(name + ":")
|
||
|
print_directory(name)
|
||
|
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
else:
|
||
|
if len(args) == 0:
|
||
|
args = [name for name in sorted(os.listdir("."), key=methodcaller('lower')) if type(name) == str]
|
||
|
if len(args) == 0:
|
||
|
# Bail early if there's nothing to list to avoid crashing on indent below
|
||
|
console_print("<empty>")
|
||
|
return
|
||
|
indent = max(len(st)+1 for st in args)
|
||
|
for file in args:
|
||
|
|
||
|
try:
|
||
|
if type(file) is not str:
|
||
|
file = file.decode(enc)
|
||
|
fp = os.path.abspath(file)
|
||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||
|
continue
|
||
|
if not os.path.exists(fp):
|
||
|
console_print("%-*s %s" % \
|
||
|
(indent, file+':', "File doesn't exist"))
|
||
|
continue
|
||
|
|
||
|
try:
|
||
|
status = dc.icon_overlay_file_status(path=fp).get('status', ['unknown'])[0]
|
||
|
console_print("%-*s %s" % (indent, file+':', status))
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("%-*s %s" % (indent, file+':', e))
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def ls(args):
|
||
|
"""list directory contents with current sync status
|
||
|
dropbox ls [FILE]...
|
||
|
|
||
|
This is an alias for filestatus -l
|
||
|
"""
|
||
|
return filestatus(["-l"] + args)
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def puburl(args):
|
||
|
"""get public url of a file in your Dropbox's public folder
|
||
|
dropbox puburl FILE
|
||
|
|
||
|
Prints out a public url for FILE (which must be in your public folder).
|
||
|
"""
|
||
|
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=os.path.abspath(args[0])).get('link', ['No Link'])[0])
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("Couldn't get public url: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def sharelink(args):
|
||
|
"""get a shared link for a file in your Dropbox
|
||
|
dropbox sharelink FILE
|
||
|
|
||
|
Prints out a shared link for FILE.
|
||
|
"""
|
||
|
if len(args) != 1:
|
||
|
console_print(sharelink.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
path = os.path.abspath(args[0])
|
||
|
link = dc.get_shared_link(path=path).get('link', ['No link'])[0]
|
||
|
console_print(link)
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("Couldn't get shared link: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def proxy(args):
|
||
|
"""set proxy settings for Dropbox
|
||
|
dropbox proxy MODE [TYPE] [HOST] [PORT] [USERNAME] [PASSWORD]
|
||
|
|
||
|
Set proxy settings for Dropbox.
|
||
|
|
||
|
MODE - one of "none", "auto", "manual"
|
||
|
TYPE - one of "http", "socks4", "socks5" (only valid with "manual" mode)
|
||
|
HOST - proxy hostname (only valid with "manual" mode)
|
||
|
PORT - proxy port (only valid with "manual" mode)
|
||
|
USERNAME - (optional) proxy username (only valid with "manual" mode)
|
||
|
PASSWORD - (optional) proxy password (only valid with "manual" mode)
|
||
|
"""
|
||
|
mode = None
|
||
|
type_ = None
|
||
|
if len(args) >= 1:
|
||
|
mode = args[0].lower()
|
||
|
if len(args) >= 2:
|
||
|
type_ = args[1].lower()
|
||
|
|
||
|
if (len(args) == 0 or
|
||
|
mode not in ['none', 'auto', 'manual'] or
|
||
|
(mode == 'manual' and len(args) not in (4, 6)) or
|
||
|
(mode != 'manual' and len(args) != 1) or
|
||
|
(mode == 'manual' and type_ not in ['http', 'socks4', 'socks5'])):
|
||
|
# Print help
|
||
|
console_print(proxy.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
ARGS = ['mode', 'type', 'host', 'port', 'username', 'password']
|
||
|
|
||
|
# Load the args into a dictionary
|
||
|
kwargs = dict(zip(ARGS, args))
|
||
|
|
||
|
# Re-set these two because they were coerced to lower case
|
||
|
kwargs['mode'] = mode
|
||
|
if type_:
|
||
|
kwargs['type'] = type_
|
||
|
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
dc.set_proxy_settings(**kwargs)
|
||
|
console_print('set')
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("Couldn't set proxy: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def throttle(args):
|
||
|
"""set bandwidth limits for Dropbox
|
||
|
dropbox throttle DOWNLOAD UPLOAD
|
||
|
|
||
|
Set bandwidth limits for file sync.
|
||
|
|
||
|
DOWNLOAD - either "unlimited" or a manual limit in KB/s
|
||
|
UPLOAD - one of "unlimited", "auto", or a manual limit in KB/s
|
||
|
"""
|
||
|
if len(args) != 2:
|
||
|
console_print(throttle.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
downlimit = args[0].lower()
|
||
|
uplimit = args[1].lower()
|
||
|
|
||
|
download_limit = None
|
||
|
download_mode = None
|
||
|
if downlimit == 'unlimited':
|
||
|
download_mode = downlimit
|
||
|
else:
|
||
|
try:
|
||
|
download_limit = int(downlimit)
|
||
|
download_mode = 'manual'
|
||
|
except ValueError:
|
||
|
console_print(throttle.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
upload_limit = None
|
||
|
upload_mode = None
|
||
|
if uplimit in ['unlimited', 'auto']:
|
||
|
upload_mode = uplimit
|
||
|
else:
|
||
|
try:
|
||
|
upload_limit = int(uplimit)
|
||
|
upload_mode = 'manual'
|
||
|
except ValueError:
|
||
|
console_print(throttle.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
kwargs = {
|
||
|
'download_mode': download_mode,
|
||
|
'upload_mode': upload_mode,
|
||
|
}
|
||
|
if download_limit:
|
||
|
kwargs['download_limit'] = str(download_limit)
|
||
|
if upload_limit:
|
||
|
kwargs['upload_limit'] = str(upload_limit)
|
||
|
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
dc.set_bandwidth_limits(**kwargs)
|
||
|
console_print('set')
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("Couldn't set bandwidth limits: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def status(args):
|
||
|
"""get current status of the dropboxd
|
||
|
dropbox status
|
||
|
|
||
|
Prints out the current status of the Dropbox daemon.
|
||
|
"""
|
||
|
if len(args) != 0:
|
||
|
console_print(status.__doc__,linebreak=False)
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
lines = dc.get_dropbox_status()['status']
|
||
|
if len(lines) == 0:
|
||
|
console_print('Idle')
|
||
|
else:
|
||
|
for line in lines:
|
||
|
console_print(line)
|
||
|
grab_link_url_if_necessary()
|
||
|
except KeyError:
|
||
|
console_print("Couldn't get status: daemon isn't responding")
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
console_print("Couldn't get status: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
def running(argv):
|
||
|
"""return whether Dropbox is running
|
||
|
dropbox running
|
||
|
|
||
|
Returns 1 if running, and 0 if not running.
|
||
|
"""
|
||
|
return int(is_dropbox_running())
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def stop(args):
|
||
|
"""stop dropboxd
|
||
|
dropbox stop
|
||
|
|
||
|
Stops the Dropbox daemon.
|
||
|
"""
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
dc.tray_action_hard_exit()
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
#returns true if link is necessary
|
||
|
def grab_link_url_if_necessary():
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
link_url = dc.needs_link().get("link_url", None)
|
||
|
if link_url is not None:
|
||
|
console_print("To link this computer to a Dropbox account, visit the following url:\n%s" % link_url[0])
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
except DropboxCommand.CommandError:
|
||
|
pass
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def lansync(argv):
|
||
|
"""enables or disables LAN sync
|
||
|
dropbox lansync [y/n]
|
||
|
|
||
|
options:
|
||
|
y Dropbox will use LAN sync (default)
|
||
|
n Dropbox will not use LAN sync
|
||
|
"""
|
||
|
if len(argv) != 1:
|
||
|
console_print(lansync.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
s = argv[0].lower()
|
||
|
if s.startswith('y') or s.startswith('-y'):
|
||
|
should_lansync = True
|
||
|
elif s.startswith('n') or s.startswith('-n'):
|
||
|
should_lansync = False
|
||
|
else:
|
||
|
should_lansync = None
|
||
|
|
||
|
if should_lansync is None:
|
||
|
console_print(lansync.__doc__,linebreak=False)
|
||
|
else:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
dc.set_lan_sync(lansync='enabled' if should_lansync else 'disabled')
|
||
|
|
||
|
|
||
|
@command
|
||
|
@requires_dropbox_running
|
||
|
def exclude(args):
|
||
|
"""ignores/excludes a directory from syncing
|
||
|
dropbox exclude [list]
|
||
|
dropbox exclude add [DIRECTORY] [DIRECTORY] ...
|
||
|
dropbox exclude remove [DIRECTORY] [DIRECTORY] ...
|
||
|
|
||
|
"list" prints a list of directories currently excluded from syncing.
|
||
|
"add" adds one or more directories to the exclusion list, then
|
||
|
resynchronizes Dropbox.
|
||
|
"remove" removes one or more directories from the exclusion list, then
|
||
|
resynchronizes Dropbox.
|
||
|
|
||
|
With no arguments, executes "list".
|
||
|
|
||
|
Any specified path must be within Dropbox.
|
||
|
"""
|
||
|
if len(args) == 0:
|
||
|
try:
|
||
|
with closing(DropboxCommand()) as dc:
|
||
|
try:
|
||
|
lines = [relpath(path) for path in dc.get_ignore_set()['ignore_set']]
|
||
|
lines.sort()
|
||
|
if len(lines) == 0:
|
||
|
console_print('No directories are being ignored.')
|
||
|
else:
|
||
|
console_print('Excluded: ')
|
||
|
for line in lines:
|
||
|
console_print(str(line))
|
||
|
except KeyError:
|
||
|
console_print("Couldn't get ignore set: daemon isn't responding")
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
if e.args[0].startswith("No command exists by that name"):
|
||
|
console_print("This version of the client does not support this command.")
|
||
|
else:
|
||
|
console_print("Couldn't get ignore set: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError:
|
||
|
console_print("Dropbox isn't responding!")
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
elif len(args) == 1 and args[0] == "list":
|
||
|
exclude([])
|
||
|
elif len(args) >= 2:
|
||
|
sub_command = args[0]
|
||
|
paths = args[1:]
|
||
|
absolute_paths = [os.path.abspath(path) for path in paths]
|
||
|
if sub_command == "add":
|
||
|
try:
|
||
|
with closing(DropboxCommand(timeout=None)) as dc:
|
||
|
try:
|
||
|
result = dc.ignore_set_add(paths=absolute_paths)
|
||
|
if result["ignored"]:
|
||
|
console_print("Excluded: ")
|
||
|
lines = [relpath(path) for path in result["ignored"]]
|
||
|
for line in lines:
|
||
|
console_print(str(line))
|
||
|
except KeyError:
|
||
|
console_print("Couldn't add ignore path: daemon isn't responding")
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
if e.args[0].startswith("No command exists by that name"):
|
||
|
console_print("This version of the client does not support this command.")
|
||
|
else:
|
||
|
console_print("Couldn't get ignore set: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError as e:
|
||
|
console_print("Dropbox isn't responding! [%s]" % e)
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
elif sub_command == "remove":
|
||
|
try:
|
||
|
with closing(DropboxCommand(timeout=None)) as dc:
|
||
|
try:
|
||
|
result = dc.ignore_set_remove(paths=absolute_paths)
|
||
|
if result["removed"]:
|
||
|
console_print("No longer excluded: ")
|
||
|
lines = [relpath(path) for path in result["removed"]]
|
||
|
for line in lines:
|
||
|
console_print(str(line))
|
||
|
except KeyError:
|
||
|
console_print("Couldn't remove ignore path: daemon isn't responding")
|
||
|
except DropboxCommand.CommandError as e:
|
||
|
if e.args[0].startswith("No command exists by that name"):
|
||
|
console_print("This version of the client does not support this command.")
|
||
|
else:
|
||
|
console_print("Couldn't get ignore set: " + str(e))
|
||
|
except DropboxCommand.BadConnectionError as e:
|
||
|
console_print("Dropbox isn't responding! [%s]" % e)
|
||
|
except DropboxCommand.EOFError:
|
||
|
console_print("Dropbox daemon stopped.")
|
||
|
except DropboxCommand.CouldntConnectError:
|
||
|
console_print("Dropbox isn't running!")
|
||
|
else:
|
||
|
console_print(exclude.__doc__, linebreak=False)
|
||
|
return
|
||
|
else:
|
||
|
console_print(exclude.__doc__, linebreak=False)
|
||
|
return
|
||
|
|
||
|
@command
|
||
|
def start(argv):
|
||
|
"""start dropboxd
|
||
|
dropbox start [-i]
|
||
|
|
||
|
Starts the Dropbox daemon, dropboxd. If dropboxd is already running,
|
||
|
this will do nothing.
|
||
|
|
||
|
options:
|
||
|
-i --install auto install dropboxd if not available on the system
|
||
|
"""
|
||
|
|
||
|
should_install = "-i" in argv or "--install" in argv
|
||
|
|
||
|
# first check if dropbox is already running
|
||
|
if is_dropbox_running():
|
||
|
if not grab_link_url_if_necessary():
|
||
|
console_print("Dropbox is already running!")
|
||
|
return
|
||
|
|
||
|
console_print("Starting Dropbox...", linebreak=False)
|
||
|
console_flush()
|
||
|
if not start_dropbox():
|
||
|
if not should_install:
|
||
|
console_print()
|
||
|
console_print("The Dropbox daemon is not installed!")
|
||
|
console_print("Run \"dropbox start -i\" to install the daemon")
|
||
|
return
|
||
|
|
||
|
# install dropbox!!!
|
||
|
try:
|
||
|
download()
|
||
|
except:
|
||
|
traceback.print_exc()
|
||
|
else:
|
||
|
if GUI_AVAILABLE:
|
||
|
start_dropbox()
|
||
|
console_print("Done!")
|
||
|
else:
|
||
|
if start_dropbox():
|
||
|
if not grab_link_url_if_necessary():
|
||
|
console_print("Done!")
|
||
|
else:
|
||
|
if not grab_link_url_if_necessary():
|
||
|
console_print("Done!")
|
||
|
|
||
|
|
||
|
def can_reroll_autostart():
|
||
|
return ".config" in os.listdir(os.path.expanduser('~'))
|
||
|
|
||
|
def reroll_autostart(should_autostart):
|
||
|
home_dir = os.path.expanduser('~')
|
||
|
contents = os.listdir(home_dir)
|
||
|
|
||
|
# UBUNTU
|
||
|
if ".config" in contents:
|
||
|
autostart_dir = os.path.join(home_dir, ".config", "autostart")
|
||
|
autostart_link = os.path.join(autostart_dir, "dropbox.desktop")
|
||
|
if should_autostart:
|
||
|
if os.path.exists(DESKTOP_FILE):
|
||
|
if not os.path.exists(autostart_dir):
|
||
|
os.makedirs(autostart_dir)
|
||
|
shutil.copyfile(DESKTOP_FILE, autostart_link)
|
||
|
elif os.path.exists(autostart_link):
|
||
|
os.remove(autostart_link)
|
||
|
|
||
|
|
||
|
|
||
|
@command
|
||
|
def autostart(argv):
|
||
|
"""automatically start Dropbox at login
|
||
|
dropbox autostart [y/n]
|
||
|
|
||
|
options:
|
||
|
n Dropbox will not start automatically at login
|
||
|
y Dropbox will start automatically at login (default)
|
||
|
|
||
|
Note: May only work on current Ubuntu distributions.
|
||
|
"""
|
||
|
if len(argv) != 1:
|
||
|
console_print(''.join(autostart.__doc__.split('\n', 1)[1:]))
|
||
|
return
|
||
|
|
||
|
s = argv[0].lower()
|
||
|
if s.startswith('y') or s.startswith('-y'):
|
||
|
should_autostart = True
|
||
|
elif s.startswith('n') or s.startswith('-n'):
|
||
|
should_autostart = False
|
||
|
else:
|
||
|
should_autostart = None
|
||
|
|
||
|
if should_autostart is None:
|
||
|
console_print(autostart.__doc__,linebreak=False)
|
||
|
else:
|
||
|
reroll_autostart(should_autostart)
|
||
|
|
||
|
@command
|
||
|
def version(argv):
|
||
|
"""print version information for Dropbox
|
||
|
dropbox version
|
||
|
|
||
|
Prints the version information for the Dropbox proprietary daemon, if
|
||
|
it's installed, and the Dropbox command-line interface.
|
||
|
"""
|
||
|
dropbox_daemon_version = "Not installed"
|
||
|
try:
|
||
|
with open(os.path.join(DROPBOX_DIST_PATH, 'VERSION')) as f:
|
||
|
dropbox_daemon_version = f.read().strip()
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
console_print("Dropbox daemon version: %s" % dropbox_daemon_version)
|
||
|
console_print("Dropbox command-line interface version: 2019.02.14")
|
||
|
|
||
|
@command
|
||
|
def help(argv):
|
||
|
"""provide help
|
||
|
dropbox help [COMMAND]
|
||
|
|
||
|
With no arguments, print a list of commands and a short description of
|
||
|
each. With a command, print descriptive help on how to use the
|
||
|
command.
|
||
|
"""
|
||
|
if not argv:
|
||
|
return usage()
|
||
|
for command in commands:
|
||
|
if command == argv[0]:
|
||
|
console_print(commands[command].__doc__.split('\n', 1)[1].strip())
|
||
|
return
|
||
|
for alias in aliases:
|
||
|
if alias == argv[0]:
|
||
|
console_print(aliases[alias].__doc__.split('\n', 1)[1].strip())
|
||
|
return
|
||
|
console_print("unknown command '%s'" % argv[0], f=sys.stderr)
|
||
|
|
||
|
def usage():
|
||
|
console_print("Dropbox command-line interface\n")
|
||
|
console_print("commands:\n")
|
||
|
console_print("Note: use dropbox help <command> to view usage for a specific command.\n")
|
||
|
out = []
|
||
|
for command in commands:
|
||
|
out.append((command, commands[command].__doc__.splitlines()[0]))
|
||
|
out.sort(key=lambda x: x[0])
|
||
|
spacing = max(len(o[0])+3 for o in out)
|
||
|
for o in out:
|
||
|
console_print(" %-*s%s" % (spacing, o[0], o[1]))
|
||
|
|
||
|
def main(argv):
|
||
|
global commands
|
||
|
|
||
|
# now we need to find out if one of the commands are in the
|
||
|
# argv list, and if so split the list at the point to
|
||
|
# separate the argv list at that point
|
||
|
cut = None
|
||
|
for i in range(len(argv)):
|
||
|
if argv[i] in commands or argv[i] in aliases:
|
||
|
cut = i
|
||
|
break
|
||
|
|
||
|
if cut == None:
|
||
|
usage()
|
||
|
os._exit(0)
|
||
|
return
|
||
|
|
||
|
# lol no options for now
|
||
|
globaloptionparser = optparse.OptionParser()
|
||
|
globaloptionparser.parse_args(argv[0:i])
|
||
|
|
||
|
# now dispatch and run
|
||
|
result = None
|
||
|
if argv[i] in commands:
|
||
|
result = commands[argv[i]](argv[i+1:])
|
||
|
elif argv[i] in aliases:
|
||
|
result = aliases[argv[i]](argv[i+1:])
|
||
|
|
||
|
# flush, in case output is rerouted to a file.
|
||
|
console_flush()
|
||
|
|
||
|
# done
|
||
|
return result
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
ret = main(sys.argv)
|
||
|
if ret is not None:
|
||
|
sys.exit(ret)
|