Skip to content

Instantly share code, notes, and snippets.

@mlgill
Forked from wrenoud/DropboxSync.py
Last active February 12, 2019 18:11
Show Gist options
  • Save mlgill/8311088 to your computer and use it in GitHub Desktop.
Save mlgill/8311088 to your computer and use it in GitHub Desktop.
DropboxSync: iOS (Pythonista), synchronizes all files to/from Dropbox directory
import os, sys, pickle, console, re
sys.path += ['lib']
import dropboxsetup
# dropbox_sync
# by Michelle L. Gill, michelle@michellelynngill.com
# requires my dropboxsetup module (https://gist.github.com/8311046)
# Change log
# 2013/01/07: initial version
# 2013/01/09: added regex filtering
##################################################################
# #
# User specified parameters #
# #
##################################################################
####### REGEX FILTERS NOT FULLY TESTED YET. USE DEBUG FIRST #######
# only files which match regexes in this list will be considered
# leave empty to consider all files
include_list = []
# then files which match regular expressions in this list will be removed
# leave empty for no exclusions
# empty directories are not uploaded
exclude_list = []
# set this to true to test regular expressions instead of uploading
debug_regex = False
##################################################################
# #
# Probably don't need to change anything below this point #
# #
##################################################################
STATE_FILE = '.dropbox_state'
class dropbox_state:
def __init__(self):
self.cursor = None
self.local_files = {}
self.remote_files = {}
# use ignore_path to prevent download of recently uploaded files
def execute_delta(self, client, ignore_path = None):
delta = client.delta(self.cursor)
self.cursor = delta['cursor']
for entry in delta['entries']:
path = entry[0][1:]
meta = entry[1]
# this skips the path if we just uploaded it
if path != ignore_path:
if meta != None:
path = meta['path'][1:] # caps sensitive
if meta['is_dir']:
print '\n\tMaking Directory:',path
self.makedir_local(path)
elif path not in self.remote_files:
print '\n\tNot in local'
self.download(client, path)
elif meta['rev'] != self.remote_files[path]['rev']:
print '\n\tOutdated revision'
self.download(client, path)
# remove file or directory
else:
if os.path.isdir(path):
print '\n\tRemoving Directory:', path
os.removedirs(path)
elif os.path.isfile(path):
print '\n\tRemoving File:', path
os.remove(path)
del self.local_files[path]
del self.remote_files[path]
else:
pass # file already doesn't exist localy
# makes dirs if necessary, downloads, and adds to local state data
def download(self, client, path):
print '\tDownloading:', path
# TODO: what if there is a folder there...?
head, tail = os.path.split(path)
# make the folder if it doesn't exist yet
if not os.path.exists(head) and head != '':
os.makedirs(head)
#open file to write
local = open(path,'w')
remote, meta = client.get_file_and_metadata(os.path.join('/',path))
local.write(remote.read())
#clean up
remote.close()
local.close()
# add to local repository
self.local_files[path] = {'modified': os.path.getmtime(path)}
self.remote_files[path] = meta
def upload(self, client, path):
print '\tUploading:', path
local = open(path,'r')
meta = client.put_file(os.path.join('/',path), local, True)
local.close()
self.local_files[path] = {'modified': os.path.getmtime(path)}
self.remote_files[path] = meta
# clean out the delta for the file upload
self.execute_delta(client, ignore_path=meta['path'])
def delete(self, client, path):
print '\tFile deleted locally. Deleting on Dropbox:',path
try:
client.file_delete(path)
except:
# file was probably already deleted
print '\tFile already removed from Dropbox'
del self.local_files[path]
del self.remote_files[path]
# safely makes local dir
def makedir_local(self,path):
if not os.path.exists(path): # no need to make a dir that exists
os.makedirs(path)
elif os.path.isfile(path): # if there is a file there ditch it
os.remove(path)
del self.files[path]
os.makedir(path)
# recursively list files on dropbox
def _listfiles(self, client, path = '/'):
meta = client.metadata(path)
filelist = []
for item in meta['contents']:
if item['is_dir']:
filelist += self._listfiles(client,item['path'])
else:
filelist.append(item['path'])
return filelist
def download_all(self, client, path = '/'):
filelist = self._listfiles(client)
for file in filelist:
self.download(client, file[1:]) # trim root slash
def check_state(self, client, path):
# lets see if we've seen it before
if path not in self.local_files:
# upload it!
self.upload(client, path)
elif os.path.getmtime(path) > self.local_files[path]['modified']:
# newer file than last sync
self.upload(client, path)
else:
pass # looks like everything is good
def loadstate():
fyle = open(STATE_FILE,'r')
state = pickle.load(fyle)
fyle.close()
return state
def savestate(state):
fyle = open(STATE_FILE,'w')
pickle.dump(state,fyle)
fyle.close()
if __name__ == '__main__':
token_filename = 'dropbox_sync.token'
app_key = 'xxxxxx'
app_secret = 'xxxxxx'
console.show_activity()
print """
****************************************
* Dropbox File Syncronization *
****************************************"""
_, client = dropboxsetup.init(token_filename, app_key, app_secret)
print '\nLoading local state'
# lets see if we can unpickle
try:
state = loadstate()
except:
print '\nCannot find state file. ***Making new local state***'
# Aaaah, we have nothing, probably first run
state = dropbox_state()
print '\nDownloading everything from Dropbox'
# no way to check what we have locally is newer, gratuitous dl
state.download_all(client)
print '\nUpdating state from Dropbox'
state.execute_delta(client)
print '\nChecking for new or updated local files'
# back to business, lets see if there is anything new or changed localy
filelist = []
for root, dirnames, filenames in os.walk('.'):
for filename in filenames:
if filename != STATE_FILE:
filelist.append( os.path.join(root, filename)[2:])
####### REGEX FILTERS NOT FULLY TESTED YET #######
filelist = set(filelist)
if debug_regex:
allfiles = filelist
# remove all files not in the includes list
if len(include_list) >= 1:
filelistinc = set()
for reg in include_list:
regc = re.compile(reg)
filelistinc = filelistinc | set([x for x in filelist if re.search(regc,x)])
filelist = filelistinc
if debug_regex:
incfiles_keep = filelist
incfiles_filt = allfiles - incfiles_keep
# remove the excludes
for reg in exclude_list:
regc = re.compile(reg)
filelist = filelist - set([x for x in filelist if re.search(regc,x)])
if debug_regex:
excfiles_keep = filelist
excfiles_filt = incfiles_keep - excfiles_keep
print '\nThe following files were removed from sync list because they did not match include filters:\n'
print incfiles_filt
print '\nThe following files were then removed from sync list because they matched exclude filters:\n'
print excfiles_filt
print '\n The following files remain in the sync list after filtering:\n'
print filelist
else:
filelist = list(filelist)
for file in filelist:
state.check_state(client,file)
print '\nChecking for deleted local files'
old_list = state.local_files.keys()
for file in old_list:
if file not in filelist:
state.delete(client, file)
print '\nSaving local state'
savestate(state)
print '\nSync complete'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment