partial sync cancellation

each time we send or receive a chunk of data we check to see if the
user wants to cancel sync

in the case of a hung connection, it will still take a minute to time
out
This commit is contained in:
Damien Elmes 2017-01-17 17:15:50 +10:00
parent 4380705992
commit 02975d43ad
2 changed files with 72 additions and 15 deletions

View File

@ -84,14 +84,31 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""")
if evt.key() == Qt.Key_Escape: if evt.key() == Qt.Key_Escape:
evt.ignore() evt.ignore()
def start(self, max=0, min=0, label=None, parent=None, immediate=False): class ProgressCancellable(QProgressDialog):
def __init__(self, *args, **kwargs):
QProgressDialog.__init__(self, *args, **kwargs)
self.ankiCancel = False
def closeEvent(self, evt):
# avoid standard Qt flag as we don't want to close until we're ready
self.ankiCancel = True
evt.ignore()
def keyPressEvent(self, evt):
if evt.key() == Qt.Key_Escape:
evt.ignore()
self.ankiCancel = True
def start(self, max=0, min=0, label=None, parent=None, immediate=False, cancellable=False):
self._levels += 1 self._levels += 1
if self._levels > 1: if self._levels > 1:
return return
# setup window # setup window
parent = parent or self.app.activeWindow() or self.mw parent = parent or self.app.activeWindow() or self.mw
label = label or _("Processing...") label = label or _("Processing...")
self._win = self.ProgressNoCancel(label, "", min, max, parent) if cancellable:
klass = self.ProgressCancellable
else:
klass = self.ProgressNoCancel
self._win = klass(label, "", min, max, parent)
self._win.setWindowTitle("Anki") self._win.setWindowTitle("Anki")
self._win.setCancelButton(None) self._win.setCancelButton(None)
self._win.setAutoClose(False) self._win.setAutoClose(False)
@ -112,6 +129,7 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""")
self._firstTime = time.time() self._firstTime = time.time()
self._lastUpdate = time.time() self._lastUpdate = time.time()
self._disabled = False self._disabled = False
return self._win
def update(self, label=None, value=None, process=True, maybeShow=True): def update(self, label=None, value=None, process=True, maybeShow=True):
#print self._min, self._counter, self._max, label, time.time() - self._lastTime #print self._min, self._counter, self._max, label, time.time() - self._lastTime

View File

@ -1,7 +1,6 @@
# 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 socket
import time import time
import traceback import traceback
import gc import gc
@ -47,11 +46,17 @@ class SyncManager(QObject):
auth=auth, media=self.pm.profile['syncMedia']) auth=auth, media=self.pm.profile['syncMedia'])
t.event.connect(self.onEvent) t.event.connect(self.onEvent)
self.label = _("Connecting...") self.label = _("Connecting...")
self.mw.progress.start(immediate=True, label=self.label) prog = self.mw.progress.start(immediate=True, label=self.label, cancellable=True)
self.sentBytes = self.recvBytes = 0 self.sentBytes = self.recvBytes = 0
self._updateLabel() self._updateLabel()
self.thread.start() self.thread.start()
while not self.thread.isFinished(): while not self.thread.isFinished():
if prog.ankiCancel:
self.thread.flagAbort()
# make sure we don't display 'upload success' msg
self._didFullUp = False
# abort may take a while
self.mw.progress.update(_("Stopping..."))
self.mw.app.processEvents() self.mw.app.processEvents()
self.thread.wait(100) self.thread.wait(100)
self.mw.progress.finish() self.mw.progress.finish()
@ -289,6 +294,10 @@ class SyncThread(QThread):
self.hkey = hkey self.hkey = hkey
self.auth = auth self.auth = auth
self.media = media self.media = media
self._abort = 0 # 1=flagged, 2=aborting
def flagAbort(self):
self._abort = 1
def run(self): def run(self):
# init this first so an early crash doesn't cause an error # init this first so an early crash doesn't cause an error
@ -309,11 +318,19 @@ class SyncThread(QThread):
def syncMsg(msg): def syncMsg(msg):
self.fireEvent("syncMsg", msg) self.fireEvent("syncMsg", msg)
def sendEvent(bytes): def sendEvent(bytes):
self.sentTotal += bytes if not self._abort:
self.fireEvent("send", str(self.sentTotal)) self.sentTotal += bytes
self.fireEvent("send", str(self.sentTotal))
elif self._abort == 1:
self._abort = 2
raise Exception("sync cancelled")
def recvEvent(bytes): def recvEvent(bytes):
self.recvTotal += bytes if not self._abort:
self.fireEvent("recv", str(self.recvTotal)) self.recvTotal += bytes
self.fireEvent("recv", str(self.recvTotal))
elif self._abort == 1:
self._abort = 2
raise Exception("sync cancelled")
addHook("sync", syncEvent) addHook("sync", syncEvent)
addHook("syncMsg", syncMsg) addHook("syncMsg", syncMsg)
addHook("httpSend", sendEvent) addHook("httpSend", sendEvent)
@ -332,6 +349,16 @@ class SyncThread(QThread):
remHook("httpSend", sendEvent) remHook("httpSend", sendEvent)
remHook("httpRecv", recvEvent) remHook("httpRecv", recvEvent)
def _abortingSync(self):
try:
return self.client.sync()
except Exception as e:
if "sync cancelled" in str(e):
self.server.abort()
raise
else:
raise
def _sync(self): def _sync(self):
if self.auth: if self.auth:
# need to authenticate and obtain host key # need to authenticate and obtain host key
@ -344,13 +371,15 @@ class SyncThread(QThread):
self.fireEvent("newKey", self.hkey) self.fireEvent("newKey", self.hkey)
# run sync and check state # run sync and check state
try: try:
ret = self.client.sync() ret = self._abortingSync()
except Exception as e: except Exception as e:
log = traceback.format_exc() log = traceback.format_exc()
err = repr(str(e)) err = repr(str(e))
if ("Unable to find the server" in err or if ("Unable to find the server" in err or
"Errno 2" in err): "Errno 2" in err):
self.fireEvent("offline") self.fireEvent("offline")
elif "sync cancelled" in err:
pass
else: else:
if not err: if not err:
err = log err = log
@ -391,11 +420,16 @@ class SyncThread(QThread):
if f == "cancel": if f == "cancel":
return return
self.client = FullSyncer(self.col, self.hkey, self.server.client) self.client = FullSyncer(self.col, self.hkey, self.server.client)
if f == "upload": try:
if not self.client.upload(): if f == "upload":
self.fireEvent("upbad") if not self.client.upload():
else: self.fireEvent("upbad")
self.client.download() else:
self.client.download()
except Exception as e:
if "sync cancelled" in str(e):
return
raise
# reopen db and move on to media sync # reopen db and move on to media sync
self.col.reopen() self.col.reopen()
self._syncMedia() self._syncMedia()
@ -405,7 +439,12 @@ class SyncThread(QThread):
return return
self.server = RemoteMediaServer(self.col, self.hkey, self.server.client) self.server = RemoteMediaServer(self.col, self.hkey, self.server.client)
self.client = MediaSyncer(self.col, self.server) self.client = MediaSyncer(self.col, self.server)
ret = self.client.sync() try:
ret = self.client.sync()
except Exception as e:
if "sync cancelled" in str(e):
return
raise
if ret == "noChanges": if ret == "noChanges":
self.fireEvent("noMediaChanges") self.fireEvent("noMediaChanges")
elif ret == "sanityCheckFailed": elif ret == "sanityCheckFailed":