Revision 99d0e277
b/tools/pithos-sync | ||
---|---|---|
36 | 36 |
import os |
37 | 37 |
import sqlite3 |
38 | 38 |
import sys |
39 |
import shutil |
|
39 | 40 |
|
40 | 41 |
from lib import transfer |
41 | 42 |
from lib.client import Pithos_Client, Fault |
... | ... | |
52 | 53 |
return DEFAULT_CONTAINER |
53 | 54 |
|
54 | 55 |
|
55 |
SQL_CREATE_TABLE = '''CREATE TABLE IF NOT EXISTS files ( |
|
56 |
path TEXT PRIMARY KEY, hash TEXT)''' |
|
56 |
def create_dir(path): |
|
57 |
if not os.path.exists(path): |
|
58 |
os.makedirs(path) |
|
59 |
if not os.path.isdir(path): |
|
60 |
raise RuntimeError("Cannot open '%s'" % (path,)) |
|
61 |
|
|
57 | 62 |
|
58 | 63 |
client = None |
64 |
lpath = None |
|
65 |
trash = None |
|
59 | 66 |
lstate = None |
60 | 67 |
cstate = None |
61 | 68 |
rstate = None |
62 | 69 |
|
70 |
class Trash(object): |
|
71 |
def __init__(self): |
|
72 |
self.tpath = os.path.join(lpath, 'trash') |
|
73 |
create_dir(self.tpath) |
|
74 |
|
|
75 |
dbpath = os.path.join(lpath, 'trash.db') |
|
76 |
self.conn = sqlite3.connect(dbpath) |
|
77 |
sql = '''CREATE TABLE IF NOT EXISTS files ( |
|
78 |
path TEXT PRIMARY KEY, hash TEXT)''' |
|
79 |
self.conn.execute(sql) |
|
80 |
self.conn.commit() |
|
81 |
|
|
82 |
def put(self, path, hash): |
|
83 |
# XXX Maintain path. |
|
84 |
shutil.copy(path, basename(path)) |
|
85 |
sql = 'INSERT OR REPLACE INTO files VALUES (?, ?)' |
|
86 |
self.conn.execute(sql, (basename(path), hash)) |
|
87 |
self.conn.commit() |
|
88 |
|
|
89 |
def search(self, hash): |
|
90 |
sql = 'SELECT path FROM files WHERE hash = ?' |
|
91 |
ret = self.conn.execute(sql, (hash,)).fetchone() |
|
92 |
return ret[0] if ret else None |
|
93 |
|
|
94 |
def empty(self): |
|
95 |
sql = 'DELETE FROM files' |
|
96 |
self.conn.execute(sql) |
|
97 |
self.conn.commit() |
|
98 |
shutil.rmtree(self.tpath) |
|
99 |
|
|
100 |
|
|
101 |
class CurrentState(object): |
|
102 |
def __init__(self, dir): |
|
103 |
self.dir = dir |
|
104 |
|
|
105 |
def get(self, path): |
|
106 |
fullpath = os.path.join(self.dir, path) |
|
107 |
if os.path.exists(fullpath): |
|
108 |
if os.path.isdir(fullpath): |
|
109 |
return 'DIR' |
|
110 |
else: |
|
111 |
return merkle(fullpath) |
|
112 |
else: |
|
113 |
return 'DEL' |
|
114 |
|
|
115 |
def fullpath(self, path): |
|
116 |
return os.path.join(self.dir, path) |
|
117 |
|
|
63 | 118 |
|
64 | 119 |
class LocalState(object): |
65 | 120 |
def __init__(self): |
66 |
dbpath = os.path.expanduser('~/.psyncdb')
|
|
121 |
dbpath = os.path.join(lpath, 'state.db')
|
|
67 | 122 |
self.conn = sqlite3.connect(dbpath) |
68 |
self.conn.execute(SQL_CREATE_TABLE) |
|
123 |
sql = '''CREATE TABLE IF NOT EXISTS files ( |
|
124 |
path TEXT PRIMARY KEY, hash TEXT)''' |
|
125 |
self.conn.execute(sql) |
|
69 | 126 |
self.conn.commit() |
70 | 127 |
|
71 | 128 |
def get(self, path): |
... | ... | |
78 | 135 |
self.conn.execute(sql, (path, hash)) |
79 | 136 |
self.conn.commit() |
80 | 137 |
|
138 |
def search(self, hash): |
|
139 |
sql = 'SELECT path FROM files WHERE hash = ?' |
|
140 |
ret = self.conn.execute(sql, (hash,)).fetchone() |
|
141 |
return ret[0] if ret else None |
|
142 |
|
|
81 | 143 |
|
82 | 144 |
class CurrentState(object): |
83 | 145 |
def __init__(self, dir): |
... | ... | |
113 | 175 |
return meta['etag'] |
114 | 176 |
|
115 | 177 |
|
116 |
def download(path, S):
|
|
178 |
def update_local(path, S):
|
|
117 | 179 |
fullpath = cstate.fullpath(path) |
118 | 180 |
if S == 'DEL': |
119 | 181 |
os.remove(fullpath) |
... | ... | |
122 | 184 |
os.remove(fullpath) |
123 | 185 |
os.mkdir(fullpath) |
124 | 186 |
else: |
187 |
# XXX Covert paths. |
|
188 |
# file = lstate.search(S) |
|
189 |
# if file: |
|
190 |
# shutil.copy(file, fullpath) |
|
191 |
# else: |
|
192 |
# transfer.download(client, get_container(), path, fullpath) |
|
125 | 193 |
transfer.download(client, get_container(), path, fullpath) |
194 |
# XXX Check either MD5 or Merkle hashes. |
|
126 | 195 |
assert cstate.get(path) == S |
127 | 196 |
|
128 | 197 |
|
129 |
def upload(path, S):
|
|
198 |
def update_remote(path, S):
|
|
130 | 199 |
fullpath = cstate.fullpath(path) |
131 | 200 |
if S == 'DEL': |
132 | 201 |
client.delete_object(get_container(), path) |
... | ... | |
154 | 223 |
if C == L: |
155 | 224 |
# No local changes |
156 | 225 |
if R != L: |
157 |
download(path, R)
|
|
226 |
update_local(path, R)
|
|
158 | 227 |
lstate.put(path, R) |
159 | 228 |
return |
160 | 229 |
|
161 | 230 |
if R == L: |
162 | 231 |
# No remote changes |
163 | 232 |
if C != L: |
164 |
upload(path, C)
|
|
233 |
update_remote(path, C)
|
|
165 | 234 |
lstate.put(path, C) |
166 | 235 |
return |
167 | 236 |
|
... | ... | |
173 | 242 |
else: |
174 | 243 |
# Conflict, try to resolve it |
175 | 244 |
resolve_conflict(path) |
176 |
download(path, R)
|
|
245 |
update_local(path, R)
|
|
177 | 246 |
lstate.put(path, R) |
178 | 247 |
|
179 | 248 |
|
... | ... | |
212 | 281 |
|
213 | 282 |
|
214 | 283 |
def main(): |
215 |
global client, lstate, cstate, rstate |
|
284 |
global client, lpath, trash, lstate, cstate, rstate
|
|
216 | 285 |
|
217 | 286 |
if len(sys.argv) != 2: |
218 | 287 |
print 'syntax: %s <dir>' % sys.argv[0] |
... | ... | |
221 | 290 |
dir = sys.argv[1] |
222 | 291 |
client = Pithos_Client(get_server(), get_auth(), get_user()) |
223 | 292 |
|
293 |
lpath = os.path.expanduser('~/.pithos-sync/') |
|
294 |
create_dir(lpath) |
|
295 |
|
|
296 |
trash = Trash() |
|
224 | 297 |
lstate = LocalState() |
225 | 298 |
cstate = CurrentState(dir) |
226 | 299 |
rstate = RemoteState(client) |
Also available in: Unified diff