Skip to content

Instantly share code, notes, and snippets.

@herbmann
Forked from freekrai/DropboxSync.py
Last active December 16, 2015 00:28
Show Gist options
  • Save herbmann/5347372 to your computer and use it in GitHub Desktop.
Save herbmann/5347372 to your computer and use it in GitHub Desktop.
Syncing Dropbox with Pythonista for iPad/iPhone. (See http://rogerstringer.com/2012/12/03/useful-pythonista-links/ for details.) This edit is designed to fix a recursive directory delete issue.
import os
import sys
import pickle
import console
from collections import namedtuple
import dropboxlogin # this code can be found here https://gist.github.com/4034526
STATE_FILE = '.dropbox_state'
WalkNode = namedtuple( 'WalkNode', ['isdir', 'path'] )
def recursive_walk(d):
'''Walk through a tree with root at directory d and yield all files below it in tuple pairs'''
isdir = os.path.isdir(d)
if isdir:
for path in (os.path.join(d,f) for f in os.listdir(d)):
for n in recursive_walk(path): yield n
yield WalkNode(isdir, d)
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):
for d,p in recursive_walk(path):
if d:
print '\n\tRemoving Directory:', p
os.removedirs(p)
else:
print '\n\tRemoving File:', p
os.remove(p)
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__':
console.show_activity()
print """
****************************************
* Dropbox File Syncronization *
****************************************"""
client = dropboxlogin.get_client()
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:])
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