diff --git a/ankiqt/__init__.py b/ankiqt/__init__.py
index 896b7b916..8fc0502f8 100644
--- a/ankiqt/__init__.py
+++ b/ankiqt/__init__.py
@@ -142,7 +142,7 @@ def run():
import forms
import ui
- ui.splash = SplashScreen(5)
+ ui.splash = SplashScreen(3)
import anki
if anki.version != appVersion:
diff --git a/ankiqt/config.py b/ankiqt/config.py
index 3bb76d476..d3d3ce92f 100644
--- a/ankiqt/config.py
+++ b/ankiqt/config.py
@@ -99,12 +99,18 @@ class Config(dict):
'suppressEstimates': False,
'suppressUpdate': False,
'syncInMsgBox': False,
- 'syncOnClose': True,
- 'syncOnLoad': True,
+ 'syncOnClose': False,
+ 'syncOnLoad': False,
+ 'syncOnProgramClose': True,
+ 'syncOnProgramOpen': True,
'syncPassword': "",
'syncUsername': "",
'typeAnswerFontSize': 20,
}
+ # disable sync on deck load when upgrading
+ if not self.has_key("syncOnProgramOpen"):
+ self['syncOnLoad'] = False
+ self['syncOnClose'] = False
for (k,v) in fields.items():
if not self.has_key(k):
self[k] = v
diff --git a/ankiqt/ui/main.py b/ankiqt/ui/main.py
index 75c3428bc..1e78ba68c 100755
--- a/ankiqt/ui/main.py
+++ b/ankiqt/ui/main.py
@@ -42,6 +42,7 @@ class AnkiQt(QMainWindow):
self.state = "initial"
self.hideWelcome = False
self.views = []
+ signal.signal(signal.SIGINT, self.onSigInt)
self.setLang()
self.setupStyle()
self.setupFonts()
@@ -69,37 +70,40 @@ class AnkiQt(QMainWindow):
self.resize(500, 500)
# load deck
ui.splash.update()
- if (args or self.config['loadLastDeck'] or
- len(self.config['recentDeckPaths']) == 1) and \
- not self.maybeLoadLastDeck(args):
- self.setEnabled(True)
- self.moveToState("auto")
- # check for updates
- ui.splash.update()
self.setupErrorHandler()
self.setupMisc()
# activate & raise is useful when run from the command line on osx
self.activateWindow()
self.raise_()
+ # plugins might be looking at this
+ self.state = "noDeck"
self.loadPlugins()
self.setupAutoUpdate()
self.rebuildPluginsMenu()
- # run after-init hook
+ # plugins loaded, now show interface
+ ui.splash.finish(self)
+ self.show()
+ # program open sync
+ if self.config['syncOnProgramOpen']:
+ self.syncDeck(interactive=False)
+ if (args or self.config['loadLastDeck'] or
+ len(self.config['recentDeckPaths']) == 1):
+ # open the last deck
+ self.maybeLoadLastDeck(args)
+ if self.deck:
+ # deck open sync?
+ if self.config['syncOnLoad'] and self.deck.syncName:
+ self.syncDeck(interactive=False)
+ elif not self.config['syncOnProgramOpen'] or not self.browserDecks:
+ # sync disabled or no user/pass, so draw deck browser manually
+ self.moveToState("noDeck")
+ # all setup is done, run after-init hook
try:
runHook('init')
except:
ui.utils.showWarning(
_("Broken plugin:\n\n%s") %
unicode(traceback.format_exc(), "utf-8", "replace"))
- ui.splash.update()
- ui.splash.finish(self)
- # ensure actions are updated after plugins loaded
- self.moveToState("auto")
- self.show()
- if (self.deck and self.config['syncOnLoad'] and
- self.deck.syncName):
- self.syncDeck(interactive=False)
- signal.signal(signal.SIGINT, self.onSigInt)
except:
ui.utils.showInfo("Error during startup:\n%s" %
traceback.format_exc())
@@ -1117,8 +1121,8 @@ your deck."""))
if not self.config['recentDeckPaths']:
return
toRemove = []
- if ui.splash.finished:
- self.startProgress(max=len(self.config['recentDeckPaths']))
+ self.startProgress(max=len(self.config['recentDeckPaths']),
+ immediate=True)
for c, d in enumerate(self.config['recentDeckPaths']):
if ui.splash.finished:
self.updateProgress(_("Checking deck %(x)d of %(y)d...") % {
@@ -1378,6 +1382,8 @@ later by using File>Close.
if not self.saveAndClose(hideWelcome=True):
event.ignore()
else:
+ if self.config['syncOnProgramClose']:
+ self.syncDeck(interactive=False)
self.prepareForExit()
event.accept()
self.app.quit()
@@ -2095,7 +2101,8 @@ it to your friends.
if not self.inMainWindow() and interactive: return
self.setNotice()
# vet input
- self.ensureSyncParams()
+ if interactive:
+ self.ensureSyncParams()
u=self.config['syncUsername']
p=self.config['syncPassword']
if not u or not p:
@@ -2110,28 +2117,34 @@ it to your friends.
self.deckProperties.dialog.qtabwidget.setCurrentIndex(1)
self.showToolTip(_("Enable syncing, choose a name, then sync again."))
return
- if self.deck is None and self.deckPath is None:
- # qt on linux incorrectly accepts shortcuts for disabled actions
- return
- # 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.lastSync = self.deck.lastSync
- if checkSources:
- self.sourcesToCheck = self.deck.s.column0(
- "select id from sources where syncPeriod != -1 "
- "and syncPeriod = 0 or :t - lastSync > syncPeriod",
- t=time.time())
- else:
- self.sourcesToCheck = []
- self.deck.close()
- self.deck = None
- self.loadAfterSync = reload
+ if self.deck is None and getattr(self, 'deckPath', None) is None:
+ # sync all decks
+ self.loadAfterSync = -1
+ self.syncName = None
+ self.sourcesToCheck = []
+ self.syncDecks = self.decksToSync()
+ else:
+ # 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.lastSync = self.deck.lastSync
+ if checkSources:
+ self.sourcesToCheck = self.deck.s.column0(
+ "select id from sources where syncPeriod != -1 "
+ "and syncPeriod = 0 or :t - lastSync > syncPeriod",
+ t=time.time())
+ else:
+ self.sourcesToCheck = []
+ self.deck.close()
+ self.deck = None
+ self.loadAfterSync = reload
# bug triggered by preferences dialog - underlying c++ widgets are not
# garbage collected until the middle of the child thread
self.state = "nostate"
@@ -2146,7 +2159,7 @@ it to your friends.
self.connect(self.syncThread, SIGNAL("noMatchingDeck"), self.selectSyncDeck)
self.connect(self.syncThread, SIGNAL("syncClockOff"), self.syncClockOff)
self.connect(self.syncThread, SIGNAL("cleanNewDeck"), self.cleanNewDeck)
- self.connect(self.syncThread, SIGNAL("syncFinished"), self.syncFinished)
+ self.connect(self.syncThread, SIGNAL("syncFinished"), self.onSyncFinished)
self.connect(self.syncThread, SIGNAL("openSyncProgress"), self.openSyncProgress)
self.connect(self.syncThread, SIGNAL("closeSyncProgress"), self.closeSyncProgress)
self.connect(self.syncThread, SIGNAL("updateSyncProgress"), self.updateSyncProgress)
@@ -2158,16 +2171,27 @@ it to your friends.
self.syncThread.start()
self.switchToWelcomeScreen()
self.setEnabled(False)
- while not self.syncThread.isFinished():
+ self.syncFinished = False
+ while not self.syncFinished:
self.app.processEvents()
self.syncThread.wait(100)
self.setEnabled(True)
return self.syncThread.ok
- def syncFinished(self):
+ def decksToSync(self):
+ ok = []
+ for d in self.config['recentDeckPaths']:
+ if os.path.exists(d):
+ ok.append(d)
+ return ok
+
+ def onSyncFinished(self):
"Reopen after sync finished."
self.mainWin.buttonStack.show()
- if self.loadAfterSync:
+ if self.loadAfterSync == -1:
+ # after sync all, so refresh browser list
+ self.moveToState("noDeck")
+ elif self.loadAfterSync:
if self.loadAfterSync == 2:
name = re.sub("[<>]", "", self.syncName)
p = os.path.join(self.documentDir, name + ".anki")
@@ -2183,6 +2207,7 @@ it to your friends.
elif not self.hideWelcome:
self.moveToState("noDeck")
self.deckPath = None
+ self.syncFinished = True
def selectSyncDeck(self, decks, create=True):
name = ui.sync.DeckChooser(self, decks, create).getName()
@@ -2195,7 +2220,7 @@ it to your friends.
if not create:
self.cleanNewDeck()
else:
- self.syncFinished()
+ self.onSyncFinished()
def cleanNewDeck(self):
"Unload a new deck if an initial sync failed."
@@ -2213,7 +2238,7 @@ it to your friends.
_("Since this can cause many problems with syncing,\n"
"syncing is disabled until you fix the problem.")
)
- self.syncFinished()
+ self.onSyncFinished()
def showSyncWarning(self, text):
ui.utils.showWarning(text, self)
@@ -2259,6 +2284,8 @@ it to your friends.
def fullSyncFinished(self):
self.finishProgress()
+ # need to deactivate interface again
+ self.setEnabled(False)
def fullSyncProgress(self, type, val):
if type == "fromLocal":
@@ -2277,7 +2304,6 @@ it to your friends.
"Close",
"Addcards",
"Editdeck",
- "Syncdeck",
"DisplayProperties",
"DeckProperties",
"Undo",
@@ -2654,13 +2680,13 @@ it to your friends.
def setProgressParent(self, parent):
self.progressParent = parent
- def startProgress(self, max=0, min=0, title=None):
+ def startProgress(self, max=0, min=0, title=None, immediate=False):
if self.mainThread != QThread.currentThread():
return
self.setBusy()
if not self.progressWins:
parent = self.progressParent or self.app.activeWindow() or self
- p = ui.utils.ProgressWin(parent, max, min, title)
+ p = ui.utils.ProgressWin(parent, max, min, title, immediate)
else:
p = None
self.progressWins.append(p)
diff --git a/ankiqt/ui/preferences.py b/ankiqt/ui/preferences.py
index 4b0695b45..a1f962af0 100644
--- a/ankiqt/ui/preferences.py
+++ b/ankiqt/ui/preferences.py
@@ -101,6 +101,8 @@ class Preferences(QDialog):
def setupNetwork(self):
self.dialog.syncOnOpen.setChecked(self.config['syncOnLoad'])
self.dialog.syncOnClose.setChecked(self.config['syncOnClose'])
+ self.dialog.syncOnProgramOpen.setChecked(self.config['syncOnProgramOpen'])
+ self.dialog.syncOnProgramClose.setChecked(self.config['syncOnProgramClose'])
self.dialog.syncUser.setText(self.config['syncUsername'])
self.dialog.syncPass.setText(self.config['syncPassword'])
self.dialog.proxyHost.setText(self.config['proxyHost'])
@@ -113,6 +115,8 @@ class Preferences(QDialog):
def updateNetwork(self):
self.config['syncOnLoad'] = self.dialog.syncOnOpen.isChecked()
self.config['syncOnClose'] = self.dialog.syncOnClose.isChecked()
+ self.config['syncOnProgramOpen'] = self.dialog.syncOnProgramOpen.isChecked()
+ self.config['syncOnProgramClose'] = self.dialog.syncOnProgramClose.isChecked()
self.config['syncUsername'] = unicode(self.dialog.syncUser.text())
self.config['syncPassword'] = unicode(self.dialog.syncPass.text())
self.config['proxyHost'] = unicode(self.dialog.proxyHost.text())
diff --git a/ankiqt/ui/sync.py b/ankiqt/ui/sync.py
index 4c2093713..1711d63e2 100755
--- a/ankiqt/ui/sync.py
+++ b/ankiqt/ui/sync.py
@@ -10,6 +10,7 @@ from anki.sync import SyncClient, HttpSyncServerProxy, copyLocalMedia
from anki.sync import SYNC_HOST, SYNC_PORT
from anki.errors import *
from anki import DeckStorage
+from anki.db import sqlite
import ankiqt.forms
from anki.hooks import addHook, removeHook
@@ -29,6 +30,7 @@ class Sync(QThread):
self.ok = True
self.onlyMerge = onlyMerge
self.sourcesToCheck = sourcesToCheck
+ self.proxy = None
addHook('fullSyncStarted', self.fullSyncStarted)
addHook('fullSyncFinished', self.fullSyncFinished)
addHook('fullSyncProgress', self.fullSyncProgress)
@@ -37,7 +39,10 @@ class Sync(QThread):
self.emit(SIGNAL("setStatus"), msg, timeout)
def run(self):
- self.syncDeck()
+ if self.parent.syncName:
+ self.syncDeck()
+ else:
+ self.syncAllDecks()
removeHook('fullSyncStarted', self.fullSyncStarted)
removeHook('fullSyncFinished', self.fullSyncFinished)
removeHook('fullSyncProgress', self.fullSyncProgress)
@@ -77,22 +82,44 @@ class Sync(QThread):
def connect(self, *args):
# connect, check auth
- proxy = HttpSyncServerProxy(self.user, self.pwd)
- proxy.sourcesToCheck = self.sourcesToCheck
- proxy.connect("ankiqt-" + ankiqt.appVersion)
- return proxy
+ if not self.proxy:
+ self.setStatus(_("Connecting..."), 0)
+ proxy = HttpSyncServerProxy(self.user, self.pwd)
+ proxy.sourcesToCheck = self.sourcesToCheck
+ proxy.connect("ankiqt-" + ankiqt.appVersion)
+ self.proxy = proxy
+ return self.proxy
- def syncDeck(self):
- self.setStatus(_("Connecting..."), 0)
+ def syncAllDecks(self):
+ decks = self.parent.syncDecks
+ for d in decks:
+ self.syncDeck(deck=d)
+ self.emit(SIGNAL("syncFinished"))
+
+ def syncDeck(self, deck=None):
+ # multi-mode setup
+ if deck:
+ c = sqlite.connect(deck)
+ syncName = c.execute("select syncName from decks").fetchone()[0]
+ c.close()
+ if not syncName:
+ return
+ path = deck
+ else:
+ syncName = self.parent.syncName
+ path = self.parent.deckPath
+ # ensure deck mods cached
try:
proxy = self.connect()
except SyncError, e:
return self.error(e)
# exists on server?
- if not proxy.hasDeck(self.parent.syncName):
+ if not proxy.hasDeck(syncName):
+ if deck:
+ return
if self.create:
try:
- proxy.createDeck(self.parent.syncName)
+ proxy.createDeck(syncName)
except SyncError, e:
return self.error(e)
else:
@@ -100,17 +127,18 @@ class Sync(QThread):
self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge)
self.setStatus("")
return
+ self.setStatus(_("Syncing %s...") % syncName, 0)
timediff = abs(proxy.timestamp - time.time())
if timediff > 300:
self.emit(SIGNAL("syncClockOff"), timediff)
return
- # reconnect
+ # reopen
self.deck = None
try:
- self.deck = DeckStorage.Deck(self.parent.deckPath)
+ self.deck = DeckStorage.Deck(path)
client = SyncClient(self.deck)
client.setServer(proxy)
- proxy.deckName = self.parent.syncName
+ proxy.deckName = syncName
# need to do anything?
start = time.time()
if client.prepareSync():
@@ -129,15 +157,16 @@ class Sync(QThread):
client.fullSyncFromServer(ret[1], ret[2])
self.setStatus(_("Sync complete."), 0)
# reopen the deck in case we have sources
- self.deck = DeckStorage.Deck(self.parent.deckPath)
+ self.deck = DeckStorage.Deck(path)
client.deck = self.deck
else:
# diff
self.setStatus(_("Determining differences..."), 0)
payload = client.genPayload(sums)
# send payload
- pr = client.payloadChangeReport(payload)
- self.setStatus("
" + pr + "
", 0)
+ if not deck:
+ pr = client.payloadChangeReport(payload)
+ self.setStatus("
" + pr + "
", 0)
self.setStatus(_("Transferring payload..."), 0)
res = client.server.applyPayload(payload)
# apply reply
@@ -150,7 +179,8 @@ class Sync(QThread):
self.deck.s.commit()
else:
changes = False
- self.setStatus(_("No changes found."))
+ if not deck:
+ self.setStatus(_("No changes found."))
# check sources
srcChanged = False
if self.sourcesToCheck:
@@ -177,12 +207,13 @@ class Sync(QThread):
self.deck.s.commit()
# close and send signal to main thread
self.deck.close()
- taken = time.time() - start
- if (changes or srcChanged) and taken < 2.5:
- time.sleep(2.5 - taken)
- else:
- time.sleep(0.25)
- self.emit(SIGNAL("syncFinished"))
+ if not deck:
+ taken = time.time() - start
+ if (changes or srcChanged) and taken < 2.5:
+ time.sleep(2.5 - taken)
+ else:
+ time.sleep(0.25)
+ self.emit(SIGNAL("syncFinished"))
except Exception, e:
self.ok = False
#traceback.print_exc()
@@ -192,7 +223,8 @@ class Sync(QThread):
err = `getattr(e, 'data', None) or e`
self.setStatus(_("Syncing failed: %(a)s") % {
'a': err})
- self.error(e)
+ if not deck:
+ self.error(e)
# Choosing a deck to sync to
##########################################################################
diff --git a/ankiqt/ui/utils.py b/ankiqt/ui/utils.py
index aba453256..1650e2522 100644
--- a/ankiqt/ui/utils.py
+++ b/ankiqt/ui/utils.py
@@ -235,7 +235,7 @@ def getBase(deck, card):
class ProgressWin(object):
- def __init__(self, parent, max=0, min=0, title=None):
+ def __init__(self, parent, max=0, min=0, title=None, immediate=False):
if not title:
title = "Anki"
self.diag = QProgressDialog("", "", min, max, parent)
@@ -245,7 +245,10 @@ class ProgressWin(object):
self.diag.setAutoReset(False)
# qt doesn't seem to honour this consistently, and it's not triggered
# by the db progress handler, so we set it high and use maybeShow() below
- self.diag.setMinimumDuration(100000)
+ if immediate:
+ self.diag.show()
+ else:
+ self.diag.setMinimumDuration(100000)
self.counter = min
self.min = min
self.max = max
diff --git a/designer/main.ui b/designer/main.ui
index 2ff1a9a0f..ce117390b 100644
--- a/designer/main.ui
+++ b/designer/main.ui
@@ -2972,7 +2972,7 @@
S&ync
- Synchronize this deck with Anki Online
+ Synchronize with Anki Online
Ctrl+Shift+Y
diff --git a/designer/preferences.ui b/designer/preferences.ui
index 5fc1acbf8..5e0792eb0 100644
--- a/designer/preferences.ui
+++ b/designer/preferences.ui
@@ -204,7 +204,7 @@
-
- Sync on close
+ Sync on deck close
true
@@ -221,13 +221,27 @@
-
- Sync on open
+ Sync on deck open
true
+ -
+
+
+ Sync on program open
+
+
+
+ -
+
+
+ Sync on program close
+
+
+
@@ -689,6 +703,8 @@
syncPass
syncOnOpen
syncOnClose
+ syncOnProgramOpen
+ syncOnProgramClose
proxyHost
proxyPort
proxyUser
@@ -720,8 +736,8 @@
accept()
- 270
- 412
+ 279
+ 457
157
@@ -736,8 +752,8 @@
reject()
- 317
- 412
+ 326
+ 457
286