mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 01:06:35 -04:00
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
This commit is contained in:
parent
3be42fbbfe
commit
a46c9a8b26
4 changed files with 86 additions and 108 deletions
|
@ -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)
|
||||
|
|
|
@ -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, <br>
|
||||
|
@ -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 <b>%s</b>
|
||||
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."
|
||||
|
|
|
@ -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(_("<h1>Synchronize</h1>"))
|
||||
else:
|
||||
self.dialog.topLabel.setText(_("<h1>Open Online Deck</h1>"))
|
||||
if self.create:
|
||||
self.dialog.decks.addItem(QListWidgetItem(
|
||||
_("Create '%s' on server") % self.parent.syncName))
|
||||
self.dialog.topLabel.setText(_("<h1>Dowload Personal Deck</h1>"))
|
||||
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()
|
||||
|
|
|
@ -195,25 +195,7 @@
|
|||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Name on server: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="syncName">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>option</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
@ -561,7 +543,6 @@
|
|||
<tabstop>medPriority</tabstop>
|
||||
<tabstop>lowPriority</tabstop>
|
||||
<tabstop>doSync</tabstop>
|
||||
<tabstop>syncName</tabstop>
|
||||
<tabstop>delay0</tabstop>
|
||||
<tabstop>delay1</tabstop>
|
||||
<tabstop>delay2</tabstop>
|
||||
|
@ -612,21 +593,5 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>doSync</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>syncName</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>58</x>
|
||||
<y>445</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>327</x>
|
||||
<y>477</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
|
Loading…
Reference in a new issue