Repository: clark800/dropbox-restore Branch: master Commit: 6bd43b38f4c8 Files: 4 Total size: 7.3 KB Directory structure: gitextract_pmzq5jn_/ ├── .gitignore ├── LICENSE ├── README.md └── restore.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ token.dat *.py[cod] *.swp *.swo *~ ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ dropbox-restore =============== ### 📣 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. _**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**_ Restore any dropbox folder to a previous state. If a file did not exist at the specified time, it will be deleted. Example ------- To restore the folder "/photos/nyc" to the way it was on March 9th, 2013: python2.7 restore.py /photos/nyc 2013-03-09 Note 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. Installation ------------ 1. Obtain Dropbox APP\_KEY and APP\_SECRET by creating a Dropbox App: https://www.dropbox.com/developers/apps/create 2. Make sure that Python 2.7 and pip are installed. 3. Then install the Dropbox Python API with the following command. sudo pip install dropbox 4. Download restore.py from Github 5. Insert APP\_KEY and APP\_SECRET at the top of restore.py Time ---- Specifying 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. Donations --------- If you would like to make a donation, you can use the PayPal button on my website: http://cclark.me ================================================ FILE: restore.py ================================================ #!/usr/bin/env python import sys, os, dropbox, time from datetime import datetime APP_KEY = 'hacwza866qep9o6' # INSERT APP_KEY HERE APP_SECRET = 'kgipko61g58n6uc' # 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 = raw_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.encode('utf8')) 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.encode('utf8') + ' 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.encode('utf8') + ' ' + str(modtime)) client.restore(path.encode('utf8'), rev) else: # there were no revisions before the cutoff, so delete if verbose: print(path.encode('utf8') + ' ' + ('SKIP' if is_deleted else 'DELETE')) if not is_deleted: client.file_delete(path.encode('utf8')) def restore_folder(client, path, cutoff_datetime, verbose=False): if verbose: print('Restoring folder: ' + path.encode('utf8')) try: folder = client.metadata(path.encode('utf8'), 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-DD\n{1}' sys.exit(usage.format(sys.argv[0], HELP_MESSAGE)) root_path_encoded, cutoff = sys.argv[1:] root_path = root_path_encoded.decode(sys.stdin.encoding) 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()