mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
detect sync conflicts and offer choice of direction
This commit is contained in:
parent
9ca3430f3d
commit
e3f50540d5
3 changed files with 80 additions and 3 deletions
|
@ -2168,6 +2168,7 @@ it to your friends.
|
||||||
self.connect(self.syncThread, SIGNAL("fullSyncFinished"), self.fullSyncFinished)
|
self.connect(self.syncThread, SIGNAL("fullSyncFinished"), self.fullSyncFinished)
|
||||||
self.connect(self.syncThread, SIGNAL("fullSyncProgress"), self.fullSyncProgress)
|
self.connect(self.syncThread, SIGNAL("fullSyncProgress"), self.fullSyncProgress)
|
||||||
self.connect(self.syncThread, SIGNAL("badUserPass"), self.badUserPass)
|
self.connect(self.syncThread, SIGNAL("badUserPass"), self.badUserPass)
|
||||||
|
self.connect(self.syncThread, SIGNAL("syncConflicts"), self.onConflict)
|
||||||
self.syncThread.start()
|
self.syncThread.start()
|
||||||
self.switchToWelcomeScreen()
|
self.switchToWelcomeScreen()
|
||||||
self.setEnabled(False)
|
self.setEnabled(False)
|
||||||
|
@ -2185,6 +2186,22 @@ it to your friends.
|
||||||
ok.append(d)
|
ok.append(d)
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
def onConflict(self, deckName):
|
||||||
|
diag = ui.utils.askUserDialog(_("""\
|
||||||
|
<b>%s</b> has been changed on both the local<br>
|
||||||
|
and remote side. What do you want to do?""" % deckName),
|
||||||
|
[_("Keep Local"),
|
||||||
|
_("Keep Remote"),
|
||||||
|
_("Cancel")])
|
||||||
|
diag.setDefault(2)
|
||||||
|
ret = diag.run()
|
||||||
|
if ret == _("Keep Local"):
|
||||||
|
self.syncThread.conflictResolution = "keepLocal"
|
||||||
|
elif ret == _("Keep Remote"):
|
||||||
|
self.syncThread.conflictResolution = "keepRemote"
|
||||||
|
else:
|
||||||
|
self.syncThread.conflictResolution = "cancel"
|
||||||
|
|
||||||
def onSyncFinished(self):
|
def onSyncFinished(self):
|
||||||
"Reopen after sync finished."
|
"Reopen after sync finished."
|
||||||
self.mainWin.buttonStack.show()
|
self.mainWin.buttonStack.show()
|
||||||
|
|
|
@ -100,7 +100,8 @@ class Sync(QThread):
|
||||||
# multi-mode setup
|
# multi-mode setup
|
||||||
if deck:
|
if deck:
|
||||||
c = sqlite.connect(deck)
|
c = sqlite.connect(deck)
|
||||||
syncName = c.execute("select syncName from decks").fetchone()[0]
|
(syncName, localChanged) = c.execute(
|
||||||
|
"select syncName, modified > lastSync from decks").fetchone()
|
||||||
c.close()
|
c.close()
|
||||||
if not syncName:
|
if not syncName:
|
||||||
return
|
return
|
||||||
|
@ -108,6 +109,10 @@ class Sync(QThread):
|
||||||
else:
|
else:
|
||||||
syncName = self.parent.syncName
|
syncName = self.parent.syncName
|
||||||
path = self.parent.deckPath
|
path = self.parent.deckPath
|
||||||
|
c = sqlite.connect(path)
|
||||||
|
localChanged = c.execute(
|
||||||
|
"select modified > lastSync from decks").fetchone()[0]
|
||||||
|
c.close()
|
||||||
# ensure deck mods cached
|
# ensure deck mods cached
|
||||||
try:
|
try:
|
||||||
proxy = self.connect()
|
proxy = self.connect()
|
||||||
|
@ -127,12 +132,25 @@ class Sync(QThread):
|
||||||
self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge)
|
self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge)
|
||||||
self.setStatus("")
|
self.setStatus("")
|
||||||
return
|
return
|
||||||
self.setStatus(_("Syncing <b>%s</b>...") % syncName, 0)
|
# check clock
|
||||||
timediff = abs(proxy.timestamp - time.time())
|
timediff = abs(proxy.timestamp - time.time())
|
||||||
if timediff > 300:
|
if timediff > 300:
|
||||||
self.emit(SIGNAL("syncClockOff"), timediff)
|
self.emit(SIGNAL("syncClockOff"), timediff)
|
||||||
return
|
return
|
||||||
|
# check conflicts
|
||||||
|
remoteChanged = proxy.hasChanged(syncName)
|
||||||
|
self.conflictResolution = None
|
||||||
|
if localChanged and remoteChanged:
|
||||||
|
self.emit(SIGNAL("syncConflicts"), syncName)
|
||||||
|
while not self.conflictResolution:
|
||||||
|
time.sleep(0.2)
|
||||||
|
if self.conflictResolution == "cancel":
|
||||||
|
if not deck:
|
||||||
|
# alert we're finished early
|
||||||
|
self.emit(SIGNAL("syncFinished"))
|
||||||
|
return
|
||||||
# reopen
|
# reopen
|
||||||
|
self.setStatus(_("Syncing <b>%s</b>...") % syncName, 0)
|
||||||
self.deck = None
|
self.deck = None
|
||||||
try:
|
try:
|
||||||
self.deck = DeckStorage.Deck(path)
|
self.deck = DeckStorage.Deck(path)
|
||||||
|
@ -146,8 +164,12 @@ class Sync(QThread):
|
||||||
# summary
|
# summary
|
||||||
self.setStatus(_("Fetching summary from server..."), 0)
|
self.setStatus(_("Fetching summary from server..."), 0)
|
||||||
sums = client.summaries()
|
sums = client.summaries()
|
||||||
if client.needFullSync(sums):
|
if self.conflictResolution or client.needFullSync(sums):
|
||||||
self.setStatus(_("Preparing full sync..."), 0)
|
self.setStatus(_("Preparing full sync..."), 0)
|
||||||
|
if self.conflictResolution == "keepLocal":
|
||||||
|
client.remoteTime = 0
|
||||||
|
elif self.conflictResolution == "keepRemote":
|
||||||
|
client.localTime = 0
|
||||||
ret = client.prepareFullSync()
|
ret = client.prepareFullSync()
|
||||||
if ret[0] == "fromLocal":
|
if ret[0] == "fromLocal":
|
||||||
self.setStatus(_("Uploading..."), 0)
|
self.setStatus(_("Uploading..."), 0)
|
||||||
|
|
|
@ -75,6 +75,44 @@ def askUser(text, parent=None, help=""):
|
||||||
break
|
break
|
||||||
return r == QMessageBox.Yes
|
return r == QMessageBox.Yes
|
||||||
|
|
||||||
|
class ButtonedDialog(QMessageBox):
|
||||||
|
|
||||||
|
def __init__(self, text, buttons, parent=None, help=""):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.buttons = []
|
||||||
|
self.setWindowTitle("Anki")
|
||||||
|
self.help = help
|
||||||
|
self.setIcon(QMessageBox.Warning)
|
||||||
|
self.setText(text)
|
||||||
|
# v = QVBoxLayout()
|
||||||
|
# v.addWidget(QLabel(text))
|
||||||
|
# box = QDialogButtonBox()
|
||||||
|
# v.addWidget(box)
|
||||||
|
for b in buttons:
|
||||||
|
self.buttons.append(
|
||||||
|
self.addButton(b, QMessageBox.AcceptRole))
|
||||||
|
if help:
|
||||||
|
self.addButton(_("Help"), QMessageBox.HelpRole)
|
||||||
|
buttons.append(_("Help"))
|
||||||
|
#self.setLayout(v)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.exec_()
|
||||||
|
but = self.clickedButton().text()
|
||||||
|
if but == "Help":
|
||||||
|
# FIXME stop dialog closing?
|
||||||
|
openWikiLink(self.help)
|
||||||
|
return self.clickedButton().text()
|
||||||
|
|
||||||
|
def setDefault(self, idx):
|
||||||
|
self.setDefaultButton(self.buttons[idx])
|
||||||
|
|
||||||
|
def askUserDialog(text, buttons, parent=None, help=""):
|
||||||
|
if not parent:
|
||||||
|
parent = ankiqt.mw
|
||||||
|
diag = ButtonedDialog(text, buttons, parent, help)
|
||||||
|
return diag
|
||||||
|
|
||||||
class GetTextDialog(QDialog):
|
class GetTextDialog(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, question, help=None, edit=None):
|
def __init__(self, parent, question, help=None, edit=None):
|
||||||
|
|
Loading…
Reference in a new issue