示例#1
0
class DropboxArchiver(ServiceArchiver):
    ARCHIVES = 'dropbox'
    REQUIRED_KEYS = ['access_token', 'folder']

    def __init__(self, service):
        try:
            self.client = DropboxClient(service['access_token'])
            self.folder_name = service['folder']
        except KeyError as e:
            raise DropboxArchiver('Missing argument "%s"' % e.message)

        super(DropboxArchiver, self).__init__(service)

    def clone(self):
        header = self.build_header(self.folder_name, self.versions)
        logging.info('Archiving {} items from "{}"'.format(len(header), self.folder_name))
        return chord(header, clone_done.s(self.folder_name, self.cid))

    def build_header(self, folder, versions=None):
        header = []

        for item in self.client.metadata(folder)['contents']:

            if item['is_dir']:
                header.extend(self.build_header(item['path'], versions=versions))
            else:
                header.append(self.build_file_chord(item, versions=versions))

        return header

    def build_file_chord(self, item, versions=None):
        if not versions:
            return fetch.si(self, item['path'], rev=None)

        header = []

        for rev in self.client.revisions(item['path'], versions):
            header.append(fetch.si(self, item['path'], rev=rev['rev']))

        return chord(header, file_done.s())
示例#2
0
class DropboxAPI(StorageAPI, AppendOnlyLog):
    "dropbox@auth : dropbox.com account with auth info"

    def __init__(self):
        from params import AUTH_DIR
        authdir = AUTH_DIR
        self.auth_file = os.path.join(authdir, 'dropbox.auth')
        try:
            with open(self.auth_file, 'r') as file:
                ACCESS_TOKEN = file.readline().rstrip()
                USER_ID = file.readline().rstrip()
        except IOError:
            ACCESS_TOKEN, USER_ID = self._authorize()

        self.client = DropboxClient(ACCESS_TOKEN)

    def sid(self):
        return util.md5("dropbox") % 10000

    def copy(self):
        return DropboxAPI()

    def _authorize(self):
        dbg.info('Request access token from Dropbox')
        flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET)
        authorize_url = flow.start()
        # print 'Open auth url:', authorize_url
        #browser = webdriver.PhantomJS(service_log_path=os.path.join(tempfile.gettempdir(), 'ghostdriver.log'))
        #browser = webdriver.PhantomJS(service_log_path=os.path.join(tempfile.gettempdir(), 'ghostdriver.log'), service_args=['--ignore-ssl-errors=true', '--ssl-protocol=tlsv1'])
        # Change to rely on browser
        print(
            "We need to authorize access to Dropbox. Please visit the following URL and authorize the access:"
        )
        print(authorize_url)
        print("")
        code = raw_input("Input the code you got: ").strip()
        #code = #raw_input("Enter the authorization code here: ").strip()
        access_token, user_id = flow.finish(code)
        with open(self.auth_file, 'w') as file:
            file.write(access_token + "\n")
            file.write(user_id + "\n")

        dbg.info('Authentication successful')

        return (access_token, user_id)

    # return: list of file paths
    def listdir(self, path):
        dic = self.client.metadata(path)
        lst = map(lambda x: x["path"], dic["contents"])
        lst = map(lambda x: x.split("/")[-1], lst)
        return lst

    def exists(self, path):
        try:
            dic = self.client.metadata(path)
            if (dic.has_key("is_deleted") and dic["is_deleted"]): return False
            return True
        except:
            return False

    def get(self, path):
        """Get the file content

    Args:
      path: string

    Returns:
      content: string
    """

        conn = self.client.get_file(path)
        content = conn.read()
        conn.close()
        return content

    def get_file_rev(self, path, rev):
        # get file of a previous version with rev hash_id
        content = None
        try:
            conn = self.client.get_file(path, rev=rev)
            content = conn.read()
            conn.close()
        except ErrorResponse as detail:
            #print "[get_file_rev] File doesn't exist", detail
            return None
        return content

    def put(self, path, content):
        """Upload the file

    Args:
      path: string
      content: string, size <= 4MB

    Returns: None
    """
        from dropbox.rest import ErrorResponse
        strobj = StringIO(content)

        try:
            metadata = self.client.put_file(path,
                                            strobj,
                                            overwrite=False,
                                            autorename=False)
        except ErrorResponse as e:
            if e.status == 409:
                raise ItemAlreadyExists(e.status, e.reason)
            else:
                raise APIError(e.status, e.reason)
        return True

    def putdir(self, path):
        self.client.file_create_folder(path)

    def update(self, path, content):
        """Update the file
    Args and returns same as put
    """
        strobj = StringIO(content)
        metadata = self.client.put_file(path, strobj, overwrite=True)
        return True

    def rm(self, path):
        """Delete the file

    Args:
      path: string
    """
        self.client.file_delete(path)

    def rmdir(self, path):
        self.client.file_delete(path)

    def metadata(self, path):
        # only for file, not dir
        _md = self.client.metadata(path)
        md = {}
        md['size'] = _md['bytes']
        md['mtime'] = util.convert_time(_md['modified'])
        return md

    def delta(self, path=None, cursor=None):
        resp = self.client.delta(cursor=cursor, path_prefix=path)
        cursor = resp['cursor']
        changes = []

        for entry in resp['entries']:
            event = {}
            if entry[1]:
                # we don't care about delete event
                event['path'] = entry[0]
                if entry[1]['is_dir']:
                    event['type'] = 'folder'
                else:
                    event['type'] = 'file'
                changes.append(event)

        return cursor, changes

    def poll(self, path=None, cursor=None, timeout=30):
        # timeout max 480
        import requests
        import time

        from error import PollError

        beg_time = time.time()
        end_time = beg_time + timeout
        curr_time = beg_time

        url = 'https://api-notify.dropbox.com/1/longpoll_delta'
        params = {}
        changes = []
        if path:
            path = util.format_path(path)

        if not cursor:
            cursor, _ = self.delta(path)
            curr_time = time.time()

        while True:
            params['cursor'] = cursor
            params['timeout'] = max(30, int(end_time -
                                            curr_time))  # minimum 30 second

            resp = requests.request('GET', url, params=params)
            obj = resp.json()
            if 'error' in obj:
                raise PollError(resp.status_code, resp.text)

            if obj['changes']:
                cursor, _delta = self.delta(path, cursor)
                changes.extend(_delta)

            if changes:
                break
            curr_time = time.time()
            if curr_time > end_time:
                break

        return cursor, changes

    def init_log(self, path):
        if not self.exists(path):
            self.put(path, '')

    def reset_log(self, path):
        if self.exists(path):
            self.rm(path)

    def append(self, path, msg):
        self.update(path, msg)

    def get_logs(self, path, last_clock):

        length = 5
        # latest revision comes first
        revisions = self.client.revisions(path, rev_limit=length)
        if not revisions:
            return [], None

        new_logs = []
        new_clock = revisions[0]['rev']
        end = False  # if reach to end

        while True:
            for metadata in revisions:
                if last_clock and metadata['rev'] == last_clock:
                    end = True
                    break
            if end: break
            if len(revisions) < length: break
            # still have logs unread, double the length
            length *= 2
            revisions = self.client.revisions(path, rev_limit=length)

        # download the content of unseen rev
        for metadata in revisions:
            if last_clock and metadata['rev'] == last_clock:
                break
            if 'is_deleted' in metadata and metadata['is_deleted']: continue
            msg = self.get_file_rev(path, metadata['rev'])
            if len(msg) > 0:
                new_logs.insert(0, msg)

        return new_logs, new_clock

    def __msg_index(self, fn):
        return eval(fn[3:])

    def init_log2(self, path):
        if not self.exists(path):
            self.putdir(path)

    def append2(self, path, msg):
        path = util.format_path(path)
        lst = sorted(self.listdir(path))
        if lst:
            index = self.__msg_index(lst[-1]) + 1
        else:
            index = 0

        while True:
            fn = 'msg%d' % index
            fpath = path + '/' + fn
            try:
                self.put(fpath, msg)
            except ItemAlreadyExists:
                index += 1
            else:
                break

    def get_logs2(self, path, last_clock):
        path = util.format_path(path)
        lst = self.listdir(path)
        if not lst:
            return [], None

        srt = {}
        for fn in lst:
            srt[self.__msg_index(fn)] = fn
        lst = [srt[i] for i in sorted(srt.keys(), reverse=True)]
        new_logs = []
        new_clock = self.__msg_index(lst[0])

        for fn in lst:
            if last_clock == None and self.__msg_index(fn) == last_clock: break
            msg = self.get(path + '/' + fn)
            new_logs.insert(0, msg)

        return new_logs, new_clock

    def share(self, path, target_email):
        url = "https://www.dropbox.com/"
        print 'Get access token from Dropbox'
        print 'Open auth url:', url
        browser = webdriver.PhantomJS(
            service_log_path=os.path.join(tempfile.gettempdir(),
                                          'ghostdriver.log'),
            service_args=['--ignore-ssl-errors=true', '--ssl-protocol=tlsv1'])
        browser.get(url)
        try:
            wait = WebDriverWait(browser, 30)
            btn = wait.until(
                EC.element_to_be_clickable(
                    (By.XPATH, "//div[@id='sign-in']/a")))
            btn.click()
            email = wait.until(
                EC.element_to_be_clickable(
                    (By.XPATH, "//input[@id='login_email']")))
            email.send_keys(raw_input("Enter your Dropbox email:"))
            pwd = browser.find_element_by_xpath(
                "//input[@id='login_password']")
            pwd.send_keys(getpass.getpass("Enter your Dropbox password:"******"//a[text()='%s']" % path)))
            target_folder.click()
            wait.until(EC.title_contains("%s" % path))
            share_btn = browser.find_element_by_xpath(
                "//a[@id='global_share_button']")
            share_btn.click()
            target = wait.until(
                EC.element_to_be_clickable((
                    By.XPATH,
                    "//form[@class='invite-more-form']//input[@spellcheck][@type='text']"
                )))
            target.send_keys(target_email)
            confirm_btn = browser.find_element_by_xpath(
                "//form[@class='invite-more-form']//input[@type='button'][1]")
            confirm_btn.click()
        except:
            print(browser.title)
            assert False
            # print(browser.current_url)
            # print(browser.page_source)
            pass
示例#3
0
class DropboxAPI(StorageAPI, AppendOnlyLog):
  "dropbox@auth : dropbox.com account with auth info"

  def __init__(self):
    from params import AUTH_DIR
    authdir = AUTH_DIR 
    self.auth_file = os.path.join(authdir, 'dropbox.auth')
    try:
      with open(self.auth_file, 'r') as file:
        ACCESS_TOKEN = file.readline().rstrip()
        USER_ID = file.readline().rstrip()
    except IOError:
      ACCESS_TOKEN, USER_ID = self._authorize()

    self.client = DropboxClient(ACCESS_TOKEN)

  def sid(self):
    return util.md5("dropbox") % 10000

  def copy(self):
    return DropboxAPI()


  def _authorize(self):
    dbg.info('Request access token from Dropbox')
    flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET)
    authorize_url = flow.start()
    # print 'Open auth url:', authorize_url
    #browser = webdriver.PhantomJS(service_log_path=os.path.join(tempfile.gettempdir(), 'ghostdriver.log'))
    #browser = webdriver.PhantomJS(service_log_path=os.path.join(tempfile.gettempdir(), 'ghostdriver.log'), service_args=['--ignore-ssl-errors=true', '--ssl-protocol=tlsv1'])
    # Change to rely on browser
    print("We need to authorize access to Dropbox. Please visit the following URL and authorize the access:")
    print(authorize_url)
    print("")
    code = raw_input("Input the code you got: ").strip()
    #code = #raw_input("Enter the authorization code here: ").strip()
    access_token, user_id = flow.finish(code)
    with open(self.auth_file, 'w') as file:
      file.write(access_token + "\n")
      file.write(user_id + "\n")

    dbg.info('Authentication successful')

    return (access_token, user_id)

  # return: list of file paths
  def listdir(self, path):
    dic = self.client.metadata(path)
    lst = map(lambda x:x["path"], dic["contents"])
    lst = map(lambda x:x.split("/")[-1], lst)
    return lst

  def exists(self, path):
    try:
      dic = self.client.metadata(path)
      if(dic.has_key("is_deleted") and dic["is_deleted"]): return False
      return True
    except:
      return False

  def get(self, path):
    """Get the file content

    Args:
      path: string

    Returns:
      content: string
    """

    conn = self.client.get_file(path)
    content = conn.read()
    conn.close()
    return content

  def get_file_rev(self, path, rev):
    # get file of a previous version with rev hash_id
    content = None
    try:
      conn = self.client.get_file(path, rev=rev)
      content = conn.read()
      conn.close()
    except ErrorResponse as detail:
      #print "[get_file_rev] File doesn't exist", detail
      return None
    return content

  def put(self, path, content):
    """Upload the file

    Args:
      path: string
      content: string, size <= 4MB

    Returns: None
    """
    from dropbox.rest import ErrorResponse
    strobj = StringIO(content)

    try:
      metadata = self.client.put_file(path, strobj, overwrite=False, autorename=False)
    except ErrorResponse as e:
      if e.status == 409:
        raise ItemAlreadyExists(e.status, e.reason)
      else:
        raise APIError(e.status, e.reason)
    return True

  def putdir(self, path):
    self.client.file_create_folder(path)

  def update(self, path, content):
    """Update the file
    Args and returns same as put
    """
    strobj = StringIO(content)
    metadata = self.client.put_file(path, strobj, overwrite=True)
    return True

  def rm(self, path):
    """Delete the file

    Args:
      path: string
    """
    self.client.file_delete(path)

  def rmdir(self, path):
    self.client.file_delete(path)

  def metadata(self, path):
    # only for file, not dir
    _md = self.client.metadata(path)
    md = {}
    md['size'] = _md['bytes']
    md['mtime'] = util.convert_time(_md['modified'])
    return md

  def delta(self, path=None, cursor=None):
    resp = self.client.delta(cursor=cursor, path_prefix=path)
    cursor = resp['cursor']
    changes = []

    for entry in resp['entries']:
      event = {}
      if entry[1]:
        # we don't care about delete event
        event['path'] = entry[0]
        if entry[1]['is_dir']:
          event['type'] = 'folder'
        else:
          event['type'] = 'file'
        changes.append(event)

    return cursor, changes

  def poll(self, path=None, cursor=None, timeout=30):
    # timeout max 480
    import requests
    import time

    from error import PollError

    beg_time = time.time()
    end_time = beg_time + timeout
    curr_time = beg_time

    url = 'https://api-notify.dropbox.com/1/longpoll_delta'
    params = {}
    changes = []
    if path:
      path = util.format_path(path)

    if not cursor:
      cursor, _ = self.delta(path)
      curr_time = time.time()

    while True:
      params['cursor'] = cursor
      params['timeout'] = max(30, int(end_time - curr_time)) # minimum 30 second

      resp = requests.request('GET', url, params=params)
      obj = resp.json()
      if 'error' in obj:
        raise PollError(resp.status_code, resp.text)

      if obj['changes']:
        cursor, _delta = self.delta(path, cursor)
        changes.extend(_delta)
      
      if changes:
        break
      curr_time = time.time()
      if curr_time > end_time:
        break

    return cursor, changes

  def init_log(self, path):
    if not self.exists(path):
      self.put(path, '')

  def reset_log(self, path):
    if self.exists(path):
      self.rm(path)

  def append(self, path, msg):
    self.update(path, msg)

  def get_logs(self, path, last_clock):

    length = 5
    # latest revision comes first
    revisions = self.client.revisions(path, rev_limit=length)
    if not revisions:
      return [], None

    new_logs = []
    new_clock = revisions[0]['rev']
    end = False # if reach to end

    while True:
      for metadata in revisions:
        if last_clock and metadata['rev'] == last_clock:
          end = True
          break
      if end: break
      if len(revisions) < length: break
      # still have logs unread, double the length
      length *= 2
      revisions = self.client.revisions(path, rev_limit=length)

    # download the content of unseen rev
    for metadata in revisions:
      if last_clock and metadata['rev'] == last_clock:
        break
      if 'is_deleted' in metadata and metadata['is_deleted']: continue
      msg = self.get_file_rev(path, metadata['rev'])
      if len(msg) > 0:
        new_logs.insert(0, msg)

    return new_logs, new_clock

  def __msg_index(self, fn):
    return eval(fn[3:])

  def init_log2(self, path):
    if not self.exists(path):
      self.putdir(path)

  def append2(self, path, msg):
    path = util.format_path(path)
    lst = sorted(self.listdir(path))
    if lst:
      index = self.__msg_index(lst[-1]) + 1
    else:
      index = 0
    
    while True:
      fn = 'msg%d' % index
      fpath = path + '/' + fn
      try:
        self.put(fpath, msg)
      except ItemAlreadyExists:
        index += 1
      else:
        break

  def get_logs2(self, path, last_clock):
    path = util.format_path(path)
    lst = self.listdir(path)
    if not lst:
      return [], None

    srt = {}
    for fn in lst:
      srt[self.__msg_index(fn)] = fn
    lst = [srt[i] for i in sorted(srt.keys(), reverse=True)]
    new_logs = []
    new_clock = self.__msg_index(lst[0])

    for fn in lst:
      if last_clock == None and self.__msg_index(fn) == last_clock: break
      msg = self.get(path + '/' + fn)
      new_logs.insert(0, msg)

    return new_logs, new_clock

  def share(self, path, target_email):
    url = "https://www.dropbox.com/"
    print 'Get access token from Dropbox'
    print 'Open auth url:', url
    browser = webdriver.PhantomJS(service_log_path=os.path.join(tempfile.gettempdir(), 'ghostdriver.log'), service_args=['--ignore-ssl-errors=true', '--ssl-protocol=tlsv1'])
    browser.get(url)
    try:
      wait = WebDriverWait(browser, 30)
      btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[@id='sign-in']/a")))
      btn.click()
      email = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@id='login_email']")))
      email.send_keys(raw_input("Enter your Dropbox email:"))
      pwd = browser.find_element_by_xpath("//input[@id='login_password']") 
      pwd.send_keys(getpass.getpass("Enter your Dropbox password:"******"//a[text()='%s']" % path)))
      target_folder.click()
      wait.until(EC.title_contains("%s" % path))
      share_btn = browser.find_element_by_xpath("//a[@id='global_share_button']")
      share_btn.click()
      target = wait.until(EC.element_to_be_clickable((By.XPATH, "//form[@class='invite-more-form']//input[@spellcheck][@type='text']")))
      target.send_keys(target_email)
      confirm_btn = browser.find_element_by_xpath("//form[@class='invite-more-form']//input[@type='button'][1]")
      confirm_btn.click()
    except:
      print(browser.title)
      assert False
      # print(browser.current_url)
      # print(browser.page_source)    
      pass
class DropboxDownloader:
    def __init__(self, output_folder):
        self.output_folder = output_folder
        self.c = None

        self.connect_to_dropbox()

    def connect_to_dropbox(self):
        """
        Connect to Dropbox, allowing us to use their API.
        """
        auth_flow = DropboxOAuth2FlowNoRedirect("cmru2e8mi7ikbbf", "21417x86w06gpdh")

        authorize_url = auth_flow.start()
        print("1. Go to: " + authorize_url)
        print("2. Click \"Allow\" (you might have to log in first).")
        print("3. Copy the authorization code.")
        auth_code = input("Enter the authorization code here: ").strip()

        try:
            access_token, user_id = auth_flow.finish(auth_code)
        except dbrest.ErrorResponse as e:
            print(('Error: %s' % (e,)))
            return None

        self.c = DropboxClient(access_token)

    def _recursive_file_search(self, path, pattern):
        """
        Searches recursively for files.

        :param path: Path to search
        :param pattern: Glob-style pattern (eg. *.tex)
        :return:
        """
        matches = []
        for root, dirnames, filenames in os.walk(path):
            for filename in fnmatch.filter(filenames, pattern):
                matches.append(os.path.join(root, filename))

        return matches

    def download_revisions(self, filename, output_folder=None):
        """
        Download all available revisions of the given filename (must be relative to the Dropbox root),
        storing them in output_folder.

        :param filename: Relative path to file inside the Dropbox folder
        :param output_folder: Folder to download to - defaults to None, meaning it uses the class attribute
        output_folder
        """
        revs = self.c.revisions(filename)

        for rev in revs:
            print(rev)
            revision_id = rev['rev']
            mod_time = rev['client_mtime'].replace(" ", "_").replace(":", "").replace("+", "").replace(',', '')

            if output_folder is None:
                output_folder = self.output_folder

            if not os.path.exists(output_folder):
                os.mkdir(output_folder)

            folder = os.path.join(output_folder, os.path.splitext(os.path.basename(filename))[0])

            if not os.path.exists(folder):
                os.mkdir(folder)

            out_filename = os.path.join(folder, '%s.tex' % (mod_time))

            if not os.path.exists(out_filename):
                outfile = open(out_filename, 'wb')
                with self.c.get_file(filename, rev=revision_id) as f:
                    outfile.write(f.read())

                outfile.close()
            else:
                print("Already done, skipping")

    def download_history_for_files(self, folder, globstring, dropbox_location, recursive=True):
        """
        Download all available revisions for a given set of files.

        :param folder: The full path to the Dropbox folder which contains the files you're interested in
        :param globstring: The globstring (eg. *.txt) to use to select files
        :param dropbox_location: The full path to the root of your Dropbox folder
        :param recursive: Whether to search recursively (default) or not
        """
        if recursive:
            files = self._recursive_file_search(folder, globstring)
        else:
            files = glob(folder + globstring)

        print(files)
        for f in files:
            print(f)
            dropboxpath = os.path.relpath(f, dropbox_location).replace("\\", "/")
            self.download_revisions(dropboxpath)
class DropboxDownloader:
    def __init__(self, output_folder):
        self.output_folder = output_folder
        self.c = None

        self.connect_to_dropbox()

    def connect_to_dropbox(self):
        """
        Connect to Dropbox, allowing us to use their API.
        """
        auth_flow = DropboxOAuth2FlowNoRedirect("cmru2e8mi7ikbbf",
                                                "21417x86w06gpdh")

        authorize_url = auth_flow.start()
        print("1. Go to: " + authorize_url)
        print("2. Click \"Allow\" (you might have to log in first).")
        print("3. Copy the authorization code.")
        auth_code = input("Enter the authorization code here: ").strip()

        try:
            access_token, user_id = auth_flow.finish(auth_code)
        except dbrest.ErrorResponse as e:
            print(('Error: %s' % (e, )))
            return None

        self.c = DropboxClient(access_token)

    def _recursive_file_search(self, path, pattern):
        """
        Searches recursively for files.

        :param path: Path to search
        :param pattern: Glob-style pattern (eg. *.tex)
        :return:
        """
        matches = []
        for root, dirnames, filenames in os.walk(path):
            for filename in fnmatch.filter(filenames, pattern):
                matches.append(os.path.join(root, filename))

        return matches

    def download_revisions(self, filename, output_folder=None):
        """
        Download all available revisions of the given filename (must be relative to the Dropbox root),
        storing them in output_folder.

        :param filename: Relative path to file inside the Dropbox folder
        :param output_folder: Folder to download to - defaults to None, meaning it uses the class attribute
        output_folder
        """
        revs = self.c.revisions(filename)

        for rev in revs:
            print(rev)
            revision_id = rev['rev']
            mod_time = rev['client_mtime'].replace(" ", "_").replace(
                ":", "").replace("+", "").replace(',', '')

            if output_folder is None:
                output_folder = self.output_folder

            if not os.path.exists(output_folder):
                os.mkdir(output_folder)

            folder = os.path.join(
                output_folder,
                os.path.splitext(os.path.basename(filename))[0])

            if not os.path.exists(folder):
                os.mkdir(folder)

            out_filename = os.path.join(folder, '%s.tex' % (mod_time))

            if not os.path.exists(out_filename):
                outfile = open(out_filename, 'wb')
                with self.c.get_file(filename, rev=revision_id) as f:
                    outfile.write(f.read())

                outfile.close()
            else:
                print("Already done, skipping")

    def download_history_for_files(self,
                                   folder,
                                   globstring,
                                   dropbox_location,
                                   recursive=True):
        """
        Download all available revisions for a given set of files.

        :param folder: The full path to the Dropbox folder which contains the files you're interested in
        :param globstring: The globstring (eg. *.txt) to use to select files
        :param dropbox_location: The full path to the root of your Dropbox folder
        :param recursive: Whether to search recursively (default) or not
        """
        if recursive:
            files = self._recursive_file_search(folder, globstring)
        else:
            files = glob(folder + globstring)

        print(files)
        for f in files:
            print(f)
            dropboxpath = os.path.relpath(f,
                                          dropbox_location).replace("\\", "/")
            self.download_revisions(dropboxpath)