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(_("