detect sync conflicts and offer choice of direction

This commit is contained in:
Damien Elmes 2010-07-21 18:35:55 +09:00
parent 9ca3430f3d
commit e3f50540d5
3 changed files with 80 additions and 3 deletions

View file

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

View file

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

View file

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