From 02975d43adf4a9dda251011a32f3c520acb08a68 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 17 Jan 2017 17:15:50 +1000 Subject: [PATCH] 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 --- aqt/progress.py | 22 +++++++++++++++-- aqt/sync.py | 65 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/aqt/progress.py b/aqt/progress.py index e627c3215..4f8ed3512 100644 --- a/aqt/progress.py +++ b/aqt/progress.py @@ -84,14 +84,31 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""") if evt.key() == Qt.Key_Escape: 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 if self._levels > 1: return # setup window parent = parent or self.app.activeWindow() or self.mw 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.setCancelButton(None) 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._lastUpdate = time.time() self._disabled = False + return self._win def update(self, label=None, value=None, process=True, maybeShow=True): #print self._min, self._counter, self._max, label, time.time() - self._lastTime diff --git a/aqt/sync.py b/aqt/sync.py index 48311a8e3..ed7b81ba4 100644 --- a/aqt/sync.py +++ b/aqt/sync.py @@ -1,7 +1,6 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import socket import time import traceback import gc @@ -47,11 +46,17 @@ class SyncManager(QObject): auth=auth, media=self.pm.profile['syncMedia']) t.event.connect(self.onEvent) 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._updateLabel() self.thread.start() 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.thread.wait(100) self.mw.progress.finish() @@ -289,6 +294,10 @@ class SyncThread(QThread): self.hkey = hkey self.auth = auth self.media = media + self._abort = 0 # 1=flagged, 2=aborting + + def flagAbort(self): + self._abort = 1 def run(self): # init this first so an early crash doesn't cause an error @@ -309,11 +318,19 @@ class SyncThread(QThread): def syncMsg(msg): self.fireEvent("syncMsg", msg) def sendEvent(bytes): - self.sentTotal += bytes - self.fireEvent("send", str(self.sentTotal)) + if not self._abort: + self.sentTotal += bytes + self.fireEvent("send", str(self.sentTotal)) + elif self._abort == 1: + self._abort = 2 + raise Exception("sync cancelled") def recvEvent(bytes): - self.recvTotal += bytes - self.fireEvent("recv", str(self.recvTotal)) + if not self._abort: + self.recvTotal += bytes + self.fireEvent("recv", str(self.recvTotal)) + elif self._abort == 1: + self._abort = 2 + raise Exception("sync cancelled") addHook("sync", syncEvent) addHook("syncMsg", syncMsg) addHook("httpSend", sendEvent) @@ -332,6 +349,16 @@ class SyncThread(QThread): remHook("httpSend", sendEvent) 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): if self.auth: # need to authenticate and obtain host key @@ -344,13 +371,15 @@ class SyncThread(QThread): self.fireEvent("newKey", self.hkey) # run sync and check state try: - ret = self.client.sync() + ret = self._abortingSync() except Exception as e: log = traceback.format_exc() err = repr(str(e)) if ("Unable to find the server" in err or "Errno 2" in err): self.fireEvent("offline") + elif "sync cancelled" in err: + pass else: if not err: err = log @@ -391,11 +420,16 @@ class SyncThread(QThread): if f == "cancel": return self.client = FullSyncer(self.col, self.hkey, self.server.client) - if f == "upload": - if not self.client.upload(): - self.fireEvent("upbad") - else: - self.client.download() + try: + if f == "upload": + if not self.client.upload(): + self.fireEvent("upbad") + else: + self.client.download() + except Exception as e: + if "sync cancelled" in str(e): + return + raise # reopen db and move on to media sync self.col.reopen() self._syncMedia() @@ -405,7 +439,12 @@ class SyncThread(QThread): return self.server = RemoteMediaServer(self.col, self.hkey, self.server.client) 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": self.fireEvent("noMediaChanges") elif ret == "sanityCheckFailed":