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:
Damien Elmes 2010-11-23 23:28:45 +09:00
parent 3be42fbbfe
commit a46c9a8b26
4 changed files with 86 additions and 108 deletions

View file

@ -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)

View file

@ -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 = ""
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,9 +2134,9 @@ 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."))
# enable syncing
self.deck.enableSyncing()
else:
return
if self.deck is None and getattr(self, 'deckPath', None) is None:
# sync all decks
@ -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)
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:
if not create:
self.syncDeck(interactive=False, onlyMerge=True)
return
self.syncFinished = True
self.cleanNewDeck()
else:
self.onSyncFinished()
def cleanNewDeck(self):
"Unload a new deck if an initial sync failed."

View file

@ -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
if not self.conflictResolution and not self.onlyMerge:
self.setStatus(_("Fetching summary from server..."), 0)
sums = client.summaries()
if self.conflictResolution or client.needFullSync(sums):
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,31 +272,21 @@ 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
item = QListWidgetItem(msg)
self.dialog.decks.addItem(item)
@ -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()

View file

@ -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>