[
  {
    "path": ".gitignore",
    "content": "token.dat\n*.py[cod]\n*.swp\n*.swo\n*~\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>\n"
  },
  {
    "path": "README.md",
    "content": "dropbox-restore\n===============\n\n### 📣 I no longer use Dropbox so I can't maintain this script. If you would like to be the maintainer, you can create a pull request that updates to the latest API and I will grant you collaborator access so you can manage the project (if the pull request passes a code review). The pull request should preserve all the former functionality and update to the latest API and not make any other changes.\n\n_**The Dropbox API changed and this script no longer works; you can try using https://github.com/clark800/dropbox-restore/pull/48 though it does not restore to the exact previous state**_\n\nRestore any dropbox folder to a previous state. If a file did not exist at the specified time, it will be deleted.\n\nExample\n-------\nTo restore the folder \"/photos/nyc\" to the way it was on March 9th, 2013:\n\n    python2.7 restore.py /photos/nyc 2013-03-09\n    \nNote that the path \"/photos/nyc\" should be relative to your Dropbox folder; it should not include the path to the Dropbox folder on your hard drive. You will be prompted to confirm access to your Dropbox account through your web browser.\n\nInstallation\n------------\n1. Obtain Dropbox APP\\_KEY and APP\\_SECRET by creating a Dropbox App: https://www.dropbox.com/developers/apps/create\n2. Make sure that Python 2.7 and pip are installed. \n3. Then install the Dropbox Python API with the following command.\n\n    sudo pip install dropbox\n\n4. Download restore.py from Github\n5. Insert APP\\_KEY and APP\\_SECRET at the top of restore.py\n\nTime\n----\nSpecifying a time is not officially supported because the time zone is ignored currently. However, it seems like Dropbox always uses UTC, so you can try specifying UTC times at your own risk by specifying the date and time in the format YYYY-MM-DD-HH-MM-SS on the command line. Be warned that Dropbox's documentation does not guarantee that they will always use UTC, so this can break at any time.\n\nDonations\n---------\nIf you would like to make a donation, you can use the PayPal button on my website: http://cclark.me\n"
  },
  {
    "path": "restore.py",
    "content": "#!/usr/bin/env python\nimport sys, os, dropbox, time\nfrom datetime import datetime\n\nAPP_KEY = 'hacwza866qep9o6'   # INSERT APP_KEY HERE\nAPP_SECRET = 'kgipko61g58n6uc'     # INSERT APP_SECRET HERE\nDELAY = 0.2 # delay between each file (try to stay under API rate limits)\n\nHELP_MESSAGE = \\\n\"\"\"Note: You must specify the path starting with \"/\", where \"/\" is the root\nof your dropbox folder. So if your dropbox directory is at \"/home/user/dropbox\"\nand you want to restore \"/home/user/dropbox/folder\", the ROOTPATH is \"/folder\".\n\"\"\"\n\nHISTORY_WARNING = \\\n\"\"\"Dropbox only keeps historical file versions for 30 days (unless you have\nenabled extended version history). Please specify a cutoff date within the past\n30 days, or if you have extended version history, you may remove this check\nfrom the source code.\"\"\"\n\ndef authorize():\n    flow = dropbox.client.DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET)\n    authorize_url = flow.start()\n    print('1. Go to: ' + authorize_url)\n    print('2. Click \"Allow\" (you might have to log in first)')\n    print('3. Copy the authorization code.')\n    try:\n        input = raw_input\n    except NameError:\n        pass\n    code = input(\"Enter the authorization code here: \").strip()\n    access_token, user_id = flow.finish(code)\n    return access_token\n\n\ndef login(token_save_path):\n    if os.path.exists(token_save_path):\n        with open(token_save_path) as token_file:\n            access_token = token_file.read()\n    else:\n        access_token = authorize()\n        with open(token_save_path, 'w') as token_file:\n            token_file.write(access_token)\n    return dropbox.client.DropboxClient(access_token)\n\n\ndef parse_date(s):\n    a = s.split('+')[0].strip()\n    return datetime.strptime(a, '%a, %d %b %Y %H:%M:%S')\n\n\ndef restore_file(client, path, cutoff_datetime, is_deleted, verbose=False):\n    revisions = client.revisions(path.encode('utf8'))\n    revision_dict = dict((parse_date(r['modified']), r) for r in revisions)\n\n    # skip if current revision is the same as it was at the cutoff\n    if max(revision_dict.keys()) < cutoff_datetime:\n        if verbose:\n            print(path.encode('utf8') + ' SKIP')\n        return\n\n    # look for the most recent revision before the cutoff\n    pre_cutoff_modtimes = [d for d in revision_dict.keys()\n                           if d < cutoff_datetime]\n    if len(pre_cutoff_modtimes) > 0:\n        modtime = max(pre_cutoff_modtimes)\n        rev = revision_dict[modtime]['rev']\n        if verbose:\n            print(path.encode('utf8') + ' ' + str(modtime))\n        client.restore(path.encode('utf8'), rev)\n    else:   # there were no revisions before the cutoff, so delete\n        if verbose:\n            print(path.encode('utf8') + ' ' + ('SKIP' if is_deleted else 'DELETE'))\n        if not is_deleted:\n            client.file_delete(path.encode('utf8'))\n\n\ndef restore_folder(client, path, cutoff_datetime, verbose=False):\n    if verbose:\n        print('Restoring folder: ' + path.encode('utf8'))\n    try:\n        folder = client.metadata(path.encode('utf8'), list=True,\n                                 include_deleted=True)\n    except dropbox.rest.ErrorResponse as e:\n        print(str(e))\n        print(HELP_MESSAGE)\n        return\n    for item in folder.get('contents', []):\n        if item.get('is_dir', False):\n            restore_folder(client, item['path'], cutoff_datetime, verbose)\n        else:\n            restore_file(client, item['path'], cutoff_datetime,\n                         item.get('is_deleted', False), verbose)\n        time.sleep(DELAY)\n\n\ndef main():\n    if len(sys.argv) != 3:\n        usage = 'usage: {0} ROOTPATH YYYY-MM-DD\\n{1}'\n        sys.exit(usage.format(sys.argv[0], HELP_MESSAGE))\n    root_path_encoded, cutoff = sys.argv[1:]\n    root_path = root_path_encoded.decode(sys.stdin.encoding)\n    cutoff_datetime = datetime(*map(int, cutoff.split('-')))\n    if (datetime.utcnow() - cutoff_datetime).days >= 30:\n        sys.exit(HISTORY_WARNING)\n    if cutoff_datetime > datetime.utcnow():\n        sys.exit('Cutoff date must be in the past')\n    client = login('token.dat')\n    restore_folder(client, root_path, cutoff_datetime, verbose=True)\n\n\nif __name__ == '__main__':\n    main()\n"
  }
]