3 # Copyright 2011 GRNET S.A. All rights reserved.
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
9 # 1. Redistributions of source code must retain the above
10 # copyright notice, this list of conditions and the following
13 # 2. Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials
16 # provided with the distribution.
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
40 from lib import transfer
41 from lib.client import Pithos_Client, Fault
42 from lib.hashmap import merkle
43 from lib.util import get_user, get_auth, get_server
48 SQL_CREATE_TABLE = '''CREATE TABLE IF NOT EXISTS files (
49 path TEXT PRIMARY KEY, hash TEXT)'''
57 class LocalState(object):
59 dbpath = os.path.expanduser('~/.psyncdb')
60 self.conn = sqlite3.connect(dbpath)
61 self.conn.execute(SQL_CREATE_TABLE)
65 sql = 'SELECT hash FROM files WHERE path = ?'
66 ret = self.conn.execute(sql, (path,)).fetchone()
67 return ret[0] if ret else 'DEL'
69 def put(self, path, hash):
70 sql = 'INSERT OR REPLACE INTO files VALUES (?, ?)'
71 self.conn.execute(sql, (path, hash))
75 class CurrentState(object):
76 def __init__(self, dir):
80 fullpath = os.path.join(self.dir, path)
81 if os.path.exists(fullpath):
82 if os.path.isdir(fullpath):
85 return merkle(fullpath)
89 def fullpath(self, path):
90 return os.path.join(self.dir, path)
93 class RemoteState(object):
94 def __init__(self, client):
95 self.container = 'pithos'
97 self.container = CONTAINER
101 meta = self.client.retrieve_object_metadata(self.container, path)
104 if meta.get('content-type', None) == 'application/directory':
110 def download(path, S):
111 fullpath = cstate.fullpath(path)
115 if os.path.exists(fullpath):
119 transfer.download(client, CONTAINER, path, fullpath)
120 assert cstate.get(path) == S
124 fullpath = cstate.fullpath(path)
126 client.delete_object(CONTAINER, path)
128 client.create_directory_marker(CONTAINER, path)
130 prefix, name = os.path.split(path)
133 transfer.upload(client, fullpath, CONTAINER, prefix, name)
134 assert rstate.get(path) == S
137 def resolve_conflict(path):
138 fullpath = cstate.fullpath(path)
139 if os.path.exists(fullpath):
140 os.rename(fullpath, fullpath + '.local')
162 # At this point both local and remote states have changes since last sync
165 # We were lucky, both had the same change
168 # Conflict, try to resolve it
169 resolve_conflict(path)
180 root = pending.pop(0)
184 dirpath = os.path.join(dir, root)
185 if os.path.exists(dirpath):
186 for filename in os.listdir(dirpath):
187 path = os.path.join(root, filename)
188 if os.path.isdir(os.path.join(dir, path)):
193 for object in client.list_objects(CONTAINER, prefix=root,
194 delimiter='/', format='json'):
195 if 'subdir' in object:
197 name = str(object['name'])
198 if object['content_type'] == 'application/directory':
203 pending += sorted(dirs)
209 global client, lstate, cstate, rstate
211 if len(sys.argv) != 2:
212 print 'syntax: %s <dir>' % sys.argv[0]
216 client = Pithos_Client(get_server(), get_auth(), get_user())
218 lstate = LocalState()
219 cstate = CurrentState(dir)
220 rstate = RemoteState(client)
222 for path in walk(dir):
223 print 'Syncing', path
227 if __name__ == '__main__':