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:
- Install Python 3.5.2 from the official Python downloads page.
- Make sure to add your Python.exe location to your PATH system variable, assuming you’re in Windows.
- Open a command prompt (Windows+R > cmd) and enter the following to install the Dropbox python package >>
pip install dropbox
- 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.
- Save the script as restore.py y update it with your keys.
- Run the script from a command prompt >>
python restore.py /path/inside/your/dropbox YYYY-MM-DD
- Your path must start with “/”, where”/” is the root of your Dropbox. See below.
- 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()