From a46c9a8b26698d57077627b647fe70b9ae176f42 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 23 Nov 2010 23:28:45 +0900 Subject: [PATCH] sync updates (syncName, etc) - remove text box from deck properties - exiting deck properties after enabling syncing forces a sync - instead of the arduous file>sync, enable checkbox, close dialog, file>sync again process users had to go through before, they can just file>sync now and the deck immediately syncs - when downloading a deck, instead of downloading into a new name, prompt if the user wants to overwrite the deck, and cancel if they don't - simplify deckChooser since we only have to deal with downloading now - only display a clobber warning if the deck already existed on the server - disable syncing if user declines to clobber the deck - skip expensive summary generation if downloading or a conflict --- ankiqt/ui/deckproperties.py | 16 +++++---- ankiqt/ui/main.py | 72 ++++++++++++++++++++++--------------- ankiqt/ui/sync.py | 69 +++++++++++++++++------------------ designer/deckproperties.ui | 37 +------------------ 4 files changed, 86 insertions(+), 108 deletions(-) diff --git a/ankiqt/ui/deckproperties.py b/ankiqt/ui/deckproperties.py index ce81d1215..35edf5b0e 100644 --- a/ankiqt/ui/deckproperties.py +++ b/ankiqt/ui/deckproperties.py @@ -38,13 +38,10 @@ class DeckProperties(QDialog): def readData(self): # syncing - sn = self.d.syncName - if sn: + if self.d.syncName: self.dialog.doSync.setCheckState(Qt.Checked) - self.dialog.syncName.setText(sn) else: self.dialog.doSync.setCheckState(Qt.Unchecked) - self.dialog.syncName.setText(self.d.name()) # priorities self.dialog.highPriority.setText(self.d.highPriority) self.dialog.medPriority.setText(self.d.medPriority) @@ -160,12 +157,15 @@ class DeckProperties(QDialog): n = _("Deck Properties") self.d.startProgress() self.d.setUndoStart(n) + needSync = False # syncing if self.dialog.doSync.checkState() == Qt.Checked: - self.updateField(self.d, 'syncName', - unicode(self.dialog.syncName.text())) + old = self.d.syncName + self.d.enableSyncing() + if self.d.syncName != old: + needSync = True else: - self.updateField(self.d, 'syncName', None) + self.d.disableSyncing() # scheduling minmax = ("Min", "Max") for type in ("hard", "mid", "easy"): @@ -231,3 +231,5 @@ class DeckProperties(QDialog): if self.onFinish: self.onFinish() QDialog.reject(self) + if needSync: + ankiqt.mw.syncDeck(interactive=-1) diff --git a/ankiqt/ui/main.py b/ankiqt/ui/main.py index 48a8aedaa..b4a0b6bfc 100755 --- a/ankiqt/ui/main.py +++ b/ankiqt/ui/main.py @@ -150,6 +150,14 @@ class AnkiQt(QMainWindow): self.mainWin.noticeButton.setFixedHeight(20) addHook("cardAnswered", self.onCardAnsweredHook) addHook("undoEnd", self.maybeEnableUndo) + addHook("notify", self.onNotify) + + def onNotify(self, msg): + if self.mainThread != QThread.currentThread(): + # decks may be opened in a sync thread + sys.stderr.write(msg + "\n") + else: + ui.utils.showInfo(msg) def setNotice(self, str=""): if str: @@ -192,7 +200,11 @@ class AnkiQt(QMainWindow): def getError(self): p = self.pool self.pool = "" - return unicode(p, 'utf8', 'replace') + try: + return unicode(p, 'utf8', 'replace') + except TypeError: + # already unicode + return p self.errorPipe = ErrorPipe(self) sys.stderr = self.errorPipe @@ -212,6 +224,12 @@ class AnkiQt(QMainWindow): ui.utils.showInfo( _("Couldn't play sound. Please install mplayer.")) return + if "ERR-0100" in error: + ui.utils.showInfo(error) + return + if "ERR-0101" in error: + ui.utils.showInfo(error) + return stdText = _("""\ An error occurred. It may have been caused by a harmless bug,
@@ -2099,10 +2117,9 @@ it to your friends. # Syncing ########################################################################## - def syncDeck(self, interactive=True, create=False, onlyMerge=False, - reload=True): + def syncDeck(self, interactive=True, onlyMerge=False, reload=True): "Synchronise a deck with the server." - if not self.inMainWindow() and interactive: return + if not self.inMainWindow() and interactive and interactive!=-1: return self.setNotice() # vet input if interactive: @@ -2117,10 +2134,10 @@ it to your friends. return if self.deck and not self.deck.syncName: if interactive: - self.onDeckProperties() - self.deckProperties.dialog.qtabwidget.setCurrentIndex(1) - self.showToolTip(_("Enable syncing, choose a name, then sync again.")) - return + # enable syncing + self.deck.enableSyncing() + else: + return if self.deck is None and getattr(self, 'deckPath', None) is None: # sync all decks self.loadAfterSync = -1 @@ -2130,13 +2147,12 @@ it to your friends. # sync one deck # hide all deck-associated dialogs self.closeAllDeckWindows() - if self.deck: # save first, so we can rollback on failure self.deck.save() # store data we need before closing the deck self.deckPath = self.deck.path - self.syncName = self.deck.syncName or self.deck.name() + self.syncName = self.deck.name() self.lastSync = self.deck.lastSync self.deck.close() self.deck = None @@ -2146,8 +2162,7 @@ it to your friends. self.state = "nostate" import gc; gc.collect() self.mainWin.welcomeText.setText(u"") - self.syncThread = ui.sync.Sync(self, u, p, interactive, create, - onlyMerge) + self.syncThread = ui.sync.Sync(self, u, p, interactive, onlyMerge) self.connect(self.syncThread, SIGNAL("setStatus"), self.setSyncStatus) self.connect(self.syncThread, SIGNAL("showWarning"), self.showSyncWarning) self.connect(self.syncThread, SIGNAL("noSyncResponse"), self.noSyncResponse) @@ -2204,7 +2219,7 @@ you want to do?""" % deckName), diag = ui.utils.askUserDialog(_("""\ You are about to upload %s to AnkiOnline. This will overwrite -the online version if one exists. +the online copy of this deck. Are you sure?""" % deckName), [_("Upload"), _("Cancel")]) @@ -2231,33 +2246,34 @@ Are you sure?""" % deckName), if self.loadAfterSync == 2: name = re.sub("[<>]", "", self.syncName) p = os.path.join(self.documentDir, name + ".anki") - if os.path.exists(p): - p = os.path.join(self.documentDir, - name + "%d.anki" % time.time()) shutil.copy2(self.deckPath, p) self.deckPath = p self.loadDeck(self.deckPath, sync=False) - self.deck.syncName = self.syncName - self.deck.s.flush() - self.deck.s.commit() + if self.loadAfterSync == 2: + self.deck.enableSyncing() else: self.moveToState("noDeck") self.deckPath = None self.syncFinished = True - def selectSyncDeck(self, decks, create=True): - name = ui.sync.DeckChooser(self, decks, create).getName() + def selectSyncDeck(self, decks): + name = ui.sync.DeckChooser(self, decks).getName() self.syncName = name if name: # name chosen - onlyMerge = self.loadAfterSync == 2 - self.syncDeck(create=True, interactive=False, onlyMerge=onlyMerge) - else: - if not create: - self.syncFinished = True - self.cleanNewDeck() + p = os.path.join(self.documentDir, name + ".anki") + if os.path.exists(p): + d = ui.utils.askUserDialog(_("""\ +This deck already exists on your computer. Overwrite the local copy?"""), + ["Overwrite", "Cancel"]) + d.setDefault(1) + if d.run() == "Overwrite": + self.syncDeck(interactive=False, onlyMerge=True) else: - self.onSyncFinished() + self.syncDeck(interactive=False, onlyMerge=True) + return + self.syncFinished = True + self.cleanNewDeck() def cleanNewDeck(self): "Unload a new deck if an initial sync failed." diff --git a/ankiqt/ui/sync.py b/ankiqt/ui/sync.py index dec09cea6..0ebf3643d 100755 --- a/ankiqt/ui/sync.py +++ b/ankiqt/ui/sync.py @@ -19,14 +19,12 @@ from anki.hooks import addHook, removeHook class Sync(QThread): - def __init__(self, parent, user, pwd, interactive, create, - onlyMerge): + def __init__(self, parent, user, pwd, interactive, onlyMerge): QThread.__init__(self) self.parent = parent self.interactive = interactive self.user = user self.pwd = pwd - self.create = create self.ok = True self.onlyMerge = onlyMerge self.proxy = None @@ -122,6 +120,7 @@ sync was aborted. Please report this error.""") c.close() if not syncName: return + syncName = os.path.splitext(os.path.basename(deck))[0] path = deck else: syncName = self.parent.syncName @@ -147,19 +146,21 @@ sync was aborted. Please report this error.""") else: return # exists on server? + deckCreated = False if not proxy.hasDeck(syncName): + # multi-mode? if deck: return - if self.create: - try: - proxy.createDeck(syncName) - except SyncError, e: - return self.error(e) - else: + if self.onlyMerge: keys = [k for (k,v) in proxy.decks.items() if v[1] != -1] - self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge) + self.emit(SIGNAL("noMatchingDeck"), keys) self.setStatus("") return + try: + proxy.createDeck(syncName) + deckCreated = True + except SyncError, e: + return self.error(e) # check conflicts proxy.deckName = syncName remoteMod = proxy.modified() @@ -188,25 +189,36 @@ sync was aborted. Please report this error.""") if client.prepareSync(): changes = True # summary - self.setStatus(_("Fetching summary from server..."), 0) - sums = client.summaries() - if self.conflictResolution or client.needFullSync(sums): + if not self.conflictResolution and not self.onlyMerge: + self.setStatus(_("Fetching summary from server..."), 0) + sums = client.summaries() + if (self.conflictResolution or + self.onlyMerge or client.needFullSync(sums)): self.setStatus(_("Preparing full sync..."), 0) if self.conflictResolution == "keepLocal": client.remoteTime = 0 - elif self.conflictResolution == "keepRemote": + elif self.conflictResolution == "keepRemote" or self.onlyMerge: client.localTime = 0 lastSync = self.deck.lastSync ret = client.prepareFullSync() if ret[0] == "fromLocal": if not self.conflictResolution: - if lastSync <= 0: + if lastSync <= 0 and not deckCreated: self.clobberChoice = None self.emit(SIGNAL("syncClobber"), syncName) while not self.clobberChoice: time.sleep(0.2) if self.clobberChoice == "cancel": + # deck has already been closed in + # prepareFullSync(), so clean up self.deck.close() + # disable syncing on this deck + c = sqlite.connect(sqlpath) + c.execute( + "update decks set syncName = null, " + "lastSync = 0") + c.commit() + c.close() if not deck: # alert we're finished early self.emit(SIGNAL("syncFinished")) @@ -260,32 +272,22 @@ sync was aborted. Please report this error.""") if not deck: self.error(e) -# Choosing a deck to sync to +# Downloading personal decks ########################################################################## class DeckChooser(QDialog): - def __init__(self, parent, decks, create): + def __init__(self, parent, decks): QDialog.__init__(self, parent, Qt.Window) self.parent = parent self.decks = decks self.dialog = ankiqt.forms.syncdeck.Ui_DeckChooser() self.dialog.setupUi(self) - self.create = create - if self.create: - self.dialog.topLabel.setText(_("

Synchronize

")) - else: - self.dialog.topLabel.setText(_("

Open Online Deck

")) - if self.create: - self.dialog.decks.addItem(QListWidgetItem( - _("Create '%s' on server") % self.parent.syncName)) + self.dialog.topLabel.setText(_("

Dowload Personal Deck

")) self.decks.sort() for name in decks: name = os.path.splitext(name)[0] - if self.create: - msg = _("Overwrite '%s' on server") % name - else: - msg = name + msg = name item = QListWidgetItem(msg) self.dialog.decks.addItem(item) self.dialog.decks.setCurrentRow(0) @@ -300,12 +302,5 @@ class DeckChooser(QDialog): def accept(self): idx = self.dialog.decks.currentRow() - if self.create: - offset = 1 - else: - offset = 0 - if idx == 0 and self.create: - self.name = self.parent.syncName - else: - self.name = self.decks[self.dialog.decks.currentRow() - offset] + self.name = self.decks[self.dialog.decks.currentRow()] self.close() diff --git a/designer/deckproperties.ui b/designer/deckproperties.ui index e175e7499..c02f788da 100644 --- a/designer/deckproperties.ui +++ b/designer/deckproperties.ui @@ -195,25 +195,7 @@ - - - - - Name on server: - - - - - - - false - - - option - - - - + @@ -561,7 +543,6 @@ medPriority lowPriority doSync - syncName delay0 delay1 delay2 @@ -612,21 +593,5 @@ - - doSync - toggled(bool) - syncName - setEnabled(bool) - - - 58 - 445 - - - 327 - 477 - - -