Dropbox – Restore a folder to a specific date

Some times, because of errors, bad luck or virus attacks you might need to have one specific folder in your dropbox rolled-back to a previous date. This Python Script, prepared by clark800 -Kudos Clark!- is a magical solution that will save you time and sweat.

Recently, I’ve been forced to use it, with some minor tweaks, so I thought it would be a good idea to share it here, just in the form it worked for me in Python 3.5.2.

Instructions:

  1. Install Python 3.5.2 from the official Python downloads page.
  2. Make sure to add your Python.exe location to your PATH system variable, assuming you’re in Windows.
  3. Open a command prompt (Windows+R > cmd) and enter the following to install the Dropbox python package >>
    pip install dropbox
  4. Create a new Dropbox App to allow changes in your account from the developers section. Keep the keys as you’ll need them for the script.
  5. Save the script as restore.py y update it with your keys.
  6. Run the script from a command prompt >>
    python restore.py /path/inside/your/dropbox YYYY-MM-DD
  7. Your path must start with “/”, where”/” is the root of your Dropbox. See below.
  8. First time you run the script you will have to authorise it by calling the url provided.

I suggest you test it with a dummy folder before breaking any important stuff.

#!/usr/bin/env python
import sys, os, dropbox, time
from datetime import datetime

APP_KEY = ''   # INSERT APP_KEY HERE
APP_SECRET = ''     # INSERT APP_SECRET HERE
DELAY = 0.2 # delay between each file (try to stay under API rate limits)

HELP_MESSAGE = 
"""Note: You must specify the path starting with "/", where "/" is the root
of your dropbox folder. So if your dropbox directory is at "/home/user/dropbox"
and you want to restore "/home/user/dropbox/folder", the ROOTPATH is "/folder".
"""

HISTORY_WARNING = 
"""Dropbox only keeps historical file versions for 30 days (unless you have
enabled extended version history). Please specify a cutoff date within the past
30 days, or if you have extended version history, you may remove this check
from the source code."""

def authorize():
    flow = dropbox.client.DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET)
    authorize_url = 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.')
    #try:
        #input = input
    #except NameError:
        #pass
    code = input("Enter the authorization code here: ").strip()
    access_token, user_id = flow.finish(code)
    return access_token


def login(token_save_path):
    if os.path.exists(token_save_path):
        with open(token_save_path) as token_file:
            access_token = token_file.read()
    else:
        access_token = authorize()
        with open(token_save_path, 'w') as token_file:
            token_file.write(access_token)
    return dropbox.client.DropboxClient(access_token)


def parse_date(s):
    a = s.split('+')[0].strip()
    return datetime.strptime(a, '%a, %d %b %Y %H:%M:%S')


def restore_file(client, path, cutoff_datetime, is_deleted, verbose=False):
    revisions = client.revisions(path)
    revision_dict = dict((parse_date(r['modified']), r) for r in revisions)

    # skip if current revision is the same as it was at the cutoff
    if max(revision_dict.keys()) < cutoff_datetime:
        if verbose:
            print(path + ' SKIP')
        return

    # look for the most recent revision before the cutoff
    pre_cutoff_modtimes = [d for d in revision_dict.keys()
                           if d < cutoff_datetime]
    if len(pre_cutoff_modtimes) > 0:
        modtime = max(pre_cutoff_modtimes)
        rev = revision_dict[modtime]['rev']
        if verbose:
            print(path + ' ' + str(modtime))
        client.restore(path, rev)
    else:   # there were no revisions before the cutoff, so delete
        if verbose:
            print(path + ' ' + ('SKIP' if is_deleted else 'DELETE'))
        if not is_deleted:
            client.file_delete(path)


def restore_folder(client, path, cutoff_datetime, verbose=False):
    if verbose:
        print(path)
        #print('Restoring folder: ' + path.encode('utf-8'))
    try:
        folder = client.metadata(path, list=True,
                                 include_deleted=True)
    except dropbox.rest.ErrorResponse as e:
        print(str(e))
        print(HELP_MESSAGE)
        return
    for item in folder.get('contents', []):
        if item.get('is_dir', False):
            restore_folder(client, item['path'], cutoff_datetime, verbose)
        else:
            restore_file(client, item['path'], cutoff_datetime,
                         item.get('is_deleted', False), verbose)
        time.sleep(DELAY)


def main():
    if len(sys.argv) != 3:
        usage = 'usage: {0} ROOTPATH YYYY-MM-DDn{1}'
        sys.exit(usage.format(sys.argv[0], HELP_MESSAGE))
    root_path_encoded, cutoff = sys.argv[1:]
    #root_path = root_path_encoded.decode('utf-8')
    root_path = root_path_encoded
    cutoff_datetime = datetime(*map(int, cutoff.split('-')))
    if (datetime.utcnow() - cutoff_datetime).days >= 30:
        sys.exit(HISTORY_WARNING)
    if cutoff_datetime > datetime.utcnow():
        sys.exit('Cutoff date must be in the past')
    client = login('token.dat')
    restore_folder(client, root_path, cutoff_datetime, verbose=True)


if __name__ == '__main__':
    main()

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit exceeded. Please complete the captcha once again.

  • Before submitting your inquiry, take a look at the basic information on data protection here.

    Modelical.com informs you that the personal data you provide will be processed by MODELICAL CONSULTORIA S.L. as the party responsible for this website.

    Purpose of the collection and processing of personal data: To send the information that the user requires through the website. - Legitimation: Consent of the interested party. - Recipients: Hosting: Gigas, 100% Spanish and 100% secure hosting. - Rights: You may exercise your rights of access, rectification, limitation and deletion of unsubscribe@modelical.com data as well as the right to lodge a complaint with a supervisory authority.