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 cStringIO import StringIO
41 from hashlib import md5
43 from lib.client import Pithos_Client, Fault
46 SQL_CREATE_TABLE = '''CREATE TABLE IF NOT EXISTS files (
47 path TEXT PRIMARY KEY, hash TEXT)'''
50 class LocalState(object):
52 dbpath = os.path.expanduser('~/.psyncdb')
53 self.conn = sqlite3.connect(dbpath)
54 self.conn.execute(SQL_CREATE_TABLE)
58 sql = 'SELECT hash FROM files WHERE path = ?'
59 ret = self.conn.execute(sql, (path,)).fetchone()
60 return ret[0] if ret else ''
62 def put(self, path, hash):
63 sql = 'INSERT OR REPLACE INTO files VALUES (?, ?)'
64 self.conn.execute(sql, (path, hash))
68 class CurrentState(object):
69 def __init__(self, dir):
73 return os.listdir(self.dir)
76 fullpath = os.path.join(self.dir, path)
77 if os.path.exists(fullpath):
78 with open(fullpath) as f:
80 return md5(data).hexdigest()
85 fullpath = os.path.join(self.dir, path)
86 if not os.path.exists(fullpath):
88 with open(fullpath) as f:
91 def write(self, path, data):
92 fullpath = os.path.join(self.dir, path)
96 with open(fullpath, 'w') as f:
99 def resolve_conflict(self, path):
100 fullpath = os.path.join(self.dir, path)
101 os.rename(fullpath, fullpath + '.local')
104 class RemoteState(object):
106 host = os.environ['PITHOS_SERVER']
107 user = os.environ['PITHOS_USER']
108 token = os.environ['PITHOS_AUTH']
109 self.container = 'pithos'
110 self.client = Pithos_Client(host, token, user)
113 return self.client.list_objects(self.container)
117 meta = self.client.retrieve_object_metadata(self.container, path)
122 def read(self, path):
124 return self.client.retrieve_object(self.container, path)
128 def write(self, path, data):
130 self.client.delete_object(self.container, path)
133 self.client.create_object(self.container, path, f=f)
136 def sync(path, lstate, cstate, rstate):
137 s0 = lstate.get(path)
138 s1 = cstate.get(path)
144 data = rstate.read(path)
145 cstate.write(path, data)
146 assert cstate.get(path) == s
153 data = cstate.read(path)
154 rstate.write(path, data)
155 assert rstate.get(path) == s1
159 # At this point both local and remote states have changes since last sync
162 # We were lucky, both had the same change
165 # Conflict, try to resolve it
166 cstate.resolve_conflict(path)
167 data = rstate.read(path)
168 cstate.write(path, data)
169 assert cstate.get(path) == s
174 if len(sys.argv) != 2:
175 print 'syntax: %s <dir>' % sys.argv[0]
178 lstate = LocalState()
179 cstate = CurrentState(sys.argv[1])
180 rstate = RemoteState()
182 local_files = set(cstate.list())
183 remote_files = set(rstate.list())
185 for path in local_files | remote_files:
186 sync(path, lstate, cstate, rstate)
189 if __name__ == '__main__':