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
46 DEFAULT_CONTAINER = 'pithos'
50 return os.environ['PITHOS_SYNC_CONTAINER']
52 return DEFAULT_CONTAINER
55 SQL_CREATE_TABLE = '''CREATE TABLE IF NOT EXISTS files (
56 path TEXT PRIMARY KEY, hash TEXT)'''
64 class LocalState(object):
66 dbpath = os.path.expanduser('~/.psyncdb')
67 self.conn = sqlite3.connect(dbpath)
68 self.conn.execute(SQL_CREATE_TABLE)
72 sql = 'SELECT hash FROM files WHERE path = ?'
73 ret = self.conn.execute(sql, (path,)).fetchone()
74 return ret[0] if ret else 'DEL'
76 def put(self, path, hash):
77 sql = 'INSERT OR REPLACE INTO files VALUES (?, ?)'
78 self.conn.execute(sql, (path, hash))
82 class CurrentState(object):
83 def __init__(self, dir):
87 fullpath = os.path.join(self.dir, path)
88 if os.path.exists(fullpath):
89 if os.path.isdir(fullpath):
92 return merkle(fullpath)
96 def fullpath(self, path):
97 return os.path.join(self.dir, path)
100 class RemoteState(object):
101 def __init__(self, client):
103 self.container = get_container()
107 meta = self.client.retrieve_object_metadata(self.container, path)
110 if meta.get('content-type', None) == 'application/directory':
116 def download(path, S):
117 fullpath = cstate.fullpath(path)
121 if os.path.exists(fullpath):
125 transfer.download(client, get_container(), path, fullpath)
126 assert cstate.get(path) == S
130 fullpath = cstate.fullpath(path)
132 client.delete_object(get_container(), path)
134 client.create_directory_marker(get_container(), path)
136 prefix, name = os.path.split(path)
139 transfer.upload(client, fullpath, get_container(), prefix, name)
140 assert rstate.get(path) == S
143 def resolve_conflict(path):
144 fullpath = cstate.fullpath(path)
145 if os.path.exists(fullpath):
146 os.rename(fullpath, fullpath + '.local')
168 # At this point both local and remote states have changes since last sync
171 # We were lucky, both had the same change
174 # Conflict, try to resolve it
175 resolve_conflict(path)
186 root = pending.pop(0)
190 dirpath = os.path.join(dir, root)
191 if os.path.exists(dirpath):
192 for filename in os.listdir(dirpath):
193 path = os.path.join(root, filename)
194 if os.path.isdir(os.path.join(dir, path)):
199 for object in client.list_objects(get_container(), prefix=root,
200 delimiter='/', format='json'):
201 if 'subdir' in object:
203 name = str(object['name'])
204 if object['content_type'] == 'application/directory':
209 pending += sorted(dirs)
215 global client, lstate, cstate, rstate
217 if len(sys.argv) != 2:
218 print 'syntax: %s <dir>' % sys.argv[0]
222 client = Pithos_Client(get_server(), get_auth(), get_user())
224 lstate = LocalState()
225 cstate = CurrentState(dir)
226 rstate = RemoteState(client)
228 for path in walk(dir):
229 print 'Syncing', path
233 if __name__ == '__main__':