mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
factor out file uploads; use for incremental sync too
This commit is contained in:
parent
20d753591d
commit
dd5b2056fb
1 changed files with 63 additions and 61 deletions
124
anki/sync.py
124
anki/sync.py
|
@ -15,7 +15,7 @@ from hooks import runHook
|
||||||
if simplejson.__version__ < "1.7.3":
|
if simplejson.__version__ < "1.7.3":
|
||||||
raise Exception("SimpleJSON must be 1.7.3 or later.")
|
raise Exception("SimpleJSON must be 1.7.3 or later.")
|
||||||
|
|
||||||
CHUNK_SIZE = 32768
|
CHUNK_SIZE = 65536
|
||||||
MIME_BOUNDARY = "Anki-sync-boundary"
|
MIME_BOUNDARY = "Anki-sync-boundary"
|
||||||
SYNC_HOST = os.environ.get("SYNC_HOST") or "dev.ankiweb.net"
|
SYNC_HOST = os.environ.get("SYNC_HOST") or "dev.ankiweb.net"
|
||||||
SYNC_PORT = int(os.environ.get("SYNC_PORT") or 80)
|
SYNC_PORT = int(os.environ.get("SYNC_PORT") or 80)
|
||||||
|
@ -420,28 +420,17 @@ class RemoteServer(Syncer):
|
||||||
self.hkey = cont
|
self.hkey = cont
|
||||||
return cont
|
return cont
|
||||||
|
|
||||||
def gzipped(self, data):
|
|
||||||
buf = StringIO()
|
|
||||||
fn = gzip.GzipFile(mode="wb", fileobj=buf)
|
|
||||||
fn.write(data)
|
|
||||||
fn.close()
|
|
||||||
res = buf.getvalue()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def applyChanges(self, **kwargs):
|
def applyChanges(self, **kwargs):
|
||||||
self.con = httplib2.Http(timeout=60)
|
self.con = httplib2.Http(timeout=60)
|
||||||
return self._run("applyChanges", kwargs)
|
return self._run("applyChanges", kwargs)
|
||||||
|
|
||||||
|
def _vars(self):
|
||||||
|
return dict(k=self.hkey)
|
||||||
|
|
||||||
def _run(self, cmd, data):
|
def _run(self, cmd, data):
|
||||||
data['k'] = self.hkey
|
return simplejson.loads(
|
||||||
data = self.gzipped(simplejson.dumps(data))
|
postData(self.con, cmd, StringIO(simplejson.dumps(data)),
|
||||||
data = urllib.urlencode(dict(data=data))
|
self._vars()))
|
||||||
headers = {'Content-Type': 'application/octet-stream'}
|
|
||||||
resp, cont = self.con.request(SYNC_URL+cmd, "POST", body=data,
|
|
||||||
headers=headers)
|
|
||||||
if resp['status'] != '200':
|
|
||||||
raise Exception("Invalid response code: %s" % resp['status'])
|
|
||||||
return simplejson.loads(cont)
|
|
||||||
|
|
||||||
# Full syncing
|
# Full syncing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -452,11 +441,16 @@ class FullSyncer(object):
|
||||||
self.deck = deck
|
self.deck = deck
|
||||||
self.hkey = hkey
|
self.hkey = hkey
|
||||||
|
|
||||||
|
def _vars(self):
|
||||||
|
return dict(k=self.hkey)
|
||||||
|
|
||||||
|
def _con(self):
|
||||||
|
return httplib2.Http(timeout=60)
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
self.deck.close()
|
self.deck.close()
|
||||||
h = httplib2.Http(timeout=60)
|
resp, cont = self._con().request(
|
||||||
resp, cont = h.request(
|
SYNC_URL+"download?" + urllib.urlencode(self._vars()))
|
||||||
SYNC_URL+"download?" + urllib.urlencode(dict(k=self.hkey)))
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
raise Exception("Invalid response code: %s" % resp['status'])
|
raise Exception("Invalid response code: %s" % resp['status'])
|
||||||
tpath = self.deck.path + ".tmp"
|
tpath = self.deck.path + ".tmp"
|
||||||
|
@ -469,43 +463,51 @@ class FullSyncer(object):
|
||||||
|
|
||||||
def upload(self):
|
def upload(self):
|
||||||
self.deck.beforeUpload()
|
self.deck.beforeUpload()
|
||||||
# compressed post body support is flaky, so bundle into a zip
|
assert postData(self._con(), "upload", open(self.deck.path, "rb"),
|
||||||
f = StringIO()
|
self._vars(), comp=6) == "OK"
|
||||||
z = zipfile.ZipFile(f, mode="w", compression=zipfile.ZIP_DEFLATED)
|
|
||||||
z.write(self.deck.path, "col.anki")
|
# We don't want to post the payload as a form var, as the percent-encoding is
|
||||||
z.close()
|
# costly. We could send it as a raw post, but more HTTP clients seem to
|
||||||
# build an upload body
|
# support file uploading, so this is the more compatible choice.
|
||||||
f2 = StringIO()
|
def postData(http, method, fobj, vars, comp=1):
|
||||||
fields = dict(k=self.hkey)
|
bdry = "--"+MIME_BOUNDARY
|
||||||
# post vars
|
# write out post vars, including session key and compression flag
|
||||||
for (key, value) in fields.items():
|
buf = StringIO()
|
||||||
f2.write('--' + MIME_BOUNDARY + "\r\n")
|
vars = vars or {}
|
||||||
f2.write('Content-Disposition: form-data; name="%s"\r\n' % key)
|
vars['c'] = 1 if comp else 0
|
||||||
f2.write('\r\n')
|
for (key, value) in vars.items():
|
||||||
f2.write(value)
|
buf.write(bdry + "\r\n")
|
||||||
f2.write('\r\n')
|
buf.write(
|
||||||
# file header
|
'Content-Disposition: form-data; name="%s"\r\n\r\n%s\r\n' %
|
||||||
f2.write('--' + MIME_BOUNDARY + "\r\n")
|
(key, value))
|
||||||
f2.write(
|
# file header
|
||||||
'Content-Disposition: form-data; name="deck"; filename="deck"\r\n')
|
buf.write(bdry + "\r\n")
|
||||||
f2.write('Content-Type: application/octet-stream\r\n')
|
buf.write("""\
|
||||||
f2.write('\r\n')
|
Content-Disposition: form-data; name="data"; filename="data"\r\n\
|
||||||
f2.write(f.getvalue())
|
Content-Type: application/octet-stream\r\n\r\n""")
|
||||||
f.close()
|
# write file into buffer, optionally compressing
|
||||||
f2.write('\r\n--' + MIME_BOUNDARY + '--\r\n\r\n')
|
if comp:
|
||||||
size = f2.tell()
|
tgt = gzip.GzipFile(mode="wb", fileobj=buf, compresslevel=comp)
|
||||||
# connection headers
|
else:
|
||||||
headers = {
|
tgt = buf
|
||||||
'Content-type': 'multipart/form-data; boundary=%s' %
|
while 1:
|
||||||
MIME_BOUNDARY,
|
data = fobj.read(CHUNK_SIZE)
|
||||||
'Content-length': str(size),
|
if not data:
|
||||||
'Host': SYNC_HOST,
|
if comp:
|
||||||
}
|
tgt.close()
|
||||||
body = f2.getvalue()
|
break
|
||||||
f2.close()
|
tgt.write(data)
|
||||||
h = httplib2.Http(timeout=60)
|
buf.write('\r\n' + bdry + '--\r\n')
|
||||||
resp, cont = h.request(
|
size = buf.tell()
|
||||||
SYNC_URL+"upload", "POST", headers=headers, body=body)
|
# connection headers
|
||||||
if resp['status'] != '200':
|
headers = {
|
||||||
raise Exception("Invalid response code: %s" % resp['status'])
|
'Content-Type': 'multipart/form-data; boundary=%s' % MIME_BOUNDARY,
|
||||||
assert cont == "OK"
|
'Content-Length': str(size),
|
||||||
|
}
|
||||||
|
body = buf.getvalue()
|
||||||
|
buf.close()
|
||||||
|
resp, cont = http.request(
|
||||||
|
SYNC_URL+method, "POST", headers=headers, body=body)
|
||||||
|
if resp['status'] != '200':
|
||||||
|
raise Exception("Invalid response code: %s" % resp['status'])
|
||||||
|
return cont
|
||||||
|
|
Loading…
Reference in a new issue