basic sync support

currently no progress shown on large uploads/downloads
This commit is contained in:
Damien Elmes 2016-07-04 15:45:53 +10:00
parent a1caa93054
commit 94463991bc
3 changed files with 36 additions and 38 deletions

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net> # Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import io
import re import re
import traceback import traceback
import urllib.request, urllib.parse, urllib.error import urllib.request, urllib.parse, urllib.error
@ -451,7 +451,7 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
########################################################################## ##########################################################################
def mediaChangesZip(self): def mediaChangesZip(self):
f = StringIO() f = io.BytesIO()
z = zipfile.ZipFile(f, "w", compression=zipfile.ZIP_DEFLATED) z = zipfile.ZipFile(f, "w", compression=zipfile.ZIP_DEFLATED)
fnames = [] fnames = []
@ -469,7 +469,7 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
if csum: if csum:
self.col.log("+media zip", fname) self.col.log("+media zip", fname)
z.write(fname, str(c)) z.writestr(fname, str(c))
meta.append((normname, str(c))) meta.append((normname, str(c)))
sz += os.path.getsize(fname) sz += os.path.getsize(fname)
else: else:
@ -485,11 +485,11 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
def addFilesFromZip(self, zipData): def addFilesFromZip(self, zipData):
"Extract zip data; true if finished." "Extract zip data; true if finished."
f = StringIO(zipData) f = io.BytesIO(zipData)
z = zipfile.ZipFile(f, "r") z = zipfile.ZipFile(f, "r")
media = [] media = []
# get meta info first # get meta info first
meta = json.loads(z.read("_meta")) meta = json.loads(z.read("_meta").decode("utf8"))
# then loop through all files # then loop through all files
cnt = 0 cnt = 0
for i in z.infolist(): for i in z.infolist():

View File

@ -3,6 +3,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import urllib.request, urllib.parse, urllib.error import urllib.request, urllib.parse, urllib.error
import io
import sys import sys
import gzip import gzip
import random import random
@ -553,21 +554,21 @@ class HttpSyncer(object):
# support file uploading, so this is the more compatible choice. # support file uploading, so this is the more compatible choice.
def req(self, method, fobj=None, comp=6, badAuthRaises=False): def req(self, method, fobj=None, comp=6, badAuthRaises=False):
BOUNDARY="Anki-sync-boundary" BOUNDARY=b"Anki-sync-boundary"
bdry = "--"+BOUNDARY bdry = b"--"+BOUNDARY
buf = StringIO() buf = io.BytesIO()
# post vars # post vars
self.postVars['c'] = 1 if comp else 0 self.postVars['c'] = 1 if comp else 0
for (key, value) in list(self.postVars.items()): for (key, value) in list(self.postVars.items()):
buf.write(bdry + "\r\n") buf.write(bdry + b"\r\n")
buf.write( buf.write(
'Content-Disposition: form-data; name="%s"\r\n\r\n%s\r\n' % ('Content-Disposition: form-data; name="%s"\r\n\r\n%s\r\n' %
(key, value)) (key, value)).encode("utf8"))
# payload as raw data or json # payload as raw data or json
if fobj: if fobj:
# header # header
buf.write(bdry + "\r\n") buf.write(bdry + b"\r\n")
buf.write("""\ buf.write(b"""\
Content-Disposition: form-data; name="data"; filename="data"\r\n\ Content-Disposition: form-data; name="data"; filename="data"\r\n\
Content-Type: application/octet-stream\r\n\r\n""") Content-Type: application/octet-stream\r\n\r\n""")
# write file into buffer, optionally compressing # write file into buffer, optionally compressing
@ -582,11 +583,11 @@ Content-Type: application/octet-stream\r\n\r\n""")
tgt.close() tgt.close()
break break
tgt.write(data) tgt.write(data)
buf.write('\r\n' + bdry + '--\r\n') buf.write(b'\r\n' + bdry + b'--\r\n')
size = buf.tell() size = buf.tell()
# connection headers # connection headers
headers = { headers = {
'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY, 'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY.decode("utf8"),
'Content-Length': str(size), 'Content-Length': str(size),
} }
body = buf.getvalue() body = buf.getvalue()
@ -617,12 +618,12 @@ class RemoteServer(HttpSyncer):
"Returns hkey or none if user/pw incorrect." "Returns hkey or none if user/pw incorrect."
self.postVars = dict() self.postVars = dict()
ret = self.req( ret = self.req(
"hostKey", StringIO(json.dumps(dict(u=user, p=pw))), "hostKey", io.BytesIO(json.dumps(dict(u=user, p=pw)).encode("utf8")),
badAuthRaises=False) badAuthRaises=False)
if not ret: if not ret:
# invalid auth # invalid auth
return return
self.hkey = json.loads(ret)['key'] self.hkey = json.loads(ret.decode("utf8"))['key']
return self.hkey return self.hkey
def meta(self): def meta(self):
@ -631,13 +632,13 @@ class RemoteServer(HttpSyncer):
s=self.skey, s=self.skey,
) )
ret = self.req( ret = self.req(
"meta", StringIO(json.dumps(dict( "meta", io.BytesIO(json.dumps(dict(
v=SYNC_VER, cv="ankidesktop,%s,%s"%(anki.version, platDesc())))), v=SYNC_VER, cv="ankidesktop,%s,%s"%(anki.version, platDesc()))).encode("utf8")),
badAuthRaises=False) badAuthRaises=False)
if not ret: if not ret:
# invalid auth # invalid auth
return return
return json.loads(ret) return json.loads(ret.decode("utf8"))
def applyChanges(self, **kw): def applyChanges(self, **kw):
return self._run("applyChanges", kw) return self._run("applyChanges", kw)
@ -659,7 +660,7 @@ class RemoteServer(HttpSyncer):
def _run(self, cmd, data): def _run(self, cmd, data):
return json.loads( return json.loads(
self.req(cmd, StringIO(json.dumps(data)))) self.req(cmd, io.BytesIO(json.dumps(data).encode("utf8"))).decode("utf8"))
# Full syncing # Full syncing
########################################################################## ##########################################################################
@ -707,7 +708,7 @@ class FullSyncer(HttpSyncer):
return False return False
# apply some adjustments, then upload # apply some adjustments, then upload
self.col.beforeUpload() self.col.beforeUpload()
if self.req("upload", open(self.col.path, "rb")) != "OK": if self.req("upload", open(self.col.path, "rb")) != b"OK":
return False return False
return True return True
@ -868,8 +869,8 @@ class RemoteMediaServer(HttpSyncer):
k=self.hkey, k=self.hkey,
v="ankidesktop,%s,%s"%(anki.version, platDesc()) v="ankidesktop,%s,%s"%(anki.version, platDesc())
) )
ret = self._dataOnly(json.loads(self.req( ret = self._dataOnly(self.req(
"begin", StringIO(json.dumps(dict()))))) "begin", io.BytesIO(json.dumps(dict()).encode("utf8"))))
self.skey = ret['sk'] self.skey = ret['sk']
return ret return ret
@ -878,25 +879,25 @@ class RemoteMediaServer(HttpSyncer):
self.postVars = dict( self.postVars = dict(
sk=self.skey, sk=self.skey,
) )
resp = json.loads( return self._dataOnly(
self.req("mediaChanges", StringIO(json.dumps(kw)))) self.req("mediaChanges", io.BytesIO(json.dumps(kw).encode("utf8"))))
return self._dataOnly(resp)
# args: files # args: files
def downloadFiles(self, **kw): def downloadFiles(self, **kw):
return self.req("downloadFiles", StringIO(json.dumps(kw))) return self.req("downloadFiles", io.BytesIO(json.dumps(kw).encode("utf8")))
def uploadChanges(self, zip): def uploadChanges(self, zip):
# no compression, as we compress the zip file instead # no compression, as we compress the zip file instead
return self._dataOnly(json.loads( return self._dataOnly(
self.req("uploadChanges", StringIO(zip), comp=0))) self.req("uploadChanges", io.BytesIO(zip), comp=0))
# args: local # args: local
def mediaSanity(self, **kw): def mediaSanity(self, **kw):
return self._dataOnly(json.loads( return self._dataOnly(
self.req("mediaSanity", StringIO(json.dumps(kw))))) self.req("mediaSanity", io.BytesIO(json.dumps(kw).encode("utf8"))))
def _dataOnly(self, resp): def _dataOnly(self, resp):
resp = json.loads(resp.decode("utf8"))
if resp['err']: if resp['err']:
self.col.log("error returned:%s"%resp['err']) self.col.log("error returned:%s"%resp['err'])
raise Exception("SyncError:%s"%resp['err']) raise Exception("SyncError:%s"%resp['err'])
@ -907,6 +908,6 @@ class RemoteMediaServer(HttpSyncer):
self.postVars = dict( self.postVars = dict(
k=self.hkey, k=self.hkey,
) )
return self._dataOnly(json.loads( return self._dataOnly(
self.req("newMediaTest", StringIO( self.req("newMediaTest", io.BytesIO(
json.dumps(dict(cmd=cmd)))))) json.dumps(dict(cmd=cmd)).encode("utf8"))))

View File

@ -576,9 +576,6 @@ title="%s" %s>%s</button>''' % (
if not auto or (self.pm.profile['syncKey'] and if not auto or (self.pm.profile['syncKey'] and
self.pm.profile['autoSync'] and self.pm.profile['autoSync'] and
not self.safeMode): not self.safeMode):
tooltip("Syncing not yet implemented")
return
from aqt.sync import SyncManager from aqt.sync import SyncManager
if not self.unloadCollection(): if not self.unloadCollection():
return return