vdirsyncer icon indicating copy to clipboard operation
vdirsyncer copied to clipboard

add conflict_resolution possibility to "add two events" depending on the resolution script

Open bernhardreiter opened this issue 3 years ago • 5 comments

Users using calendar application frontends only, want to see if there was a conflict within their applications by seeing both versions of the event.

vdirsyncer version 0.18.0a2.dev16+g81895c2.d20210520 only offers the conflict resolution options null, a wins, b wins and ["command..].

An option take both would save both events (by using a difference uid for the second event), this way users can resolve their conflicts within their calendar applications and vdirsyncer can run non-interactively, periodically in the background.

bernhardreiter avatar May 21 '21 13:05 bernhardreiter

This seems a bit trickier that it might sound.

Say we have an event ABC which is in conflict. We'd then create events "ABC1" and "ABC2", right?

How does the user figure out that a duplication has happened? I can image a conflict for some event in 30 days, and then they day before that, I notice duplicate events. How do I figure out where each copy came from?

WhyNotHugo avatar May 21 '21 13:05 WhyNotHugo

I sound that you could use conflict resolution command, and just write a script that returns two events... Though I guess vdirsyncer would also need to be able to handle that :thinking:

WhyNotHugo avatar May 21 '21 13:05 WhyNotHugo

Thanks for thinking along. We had a feature like this in the Kolab groupware, but the default was to shown an interactive conflict resolution dialog. The idea is that you see both events next to each other, because they are at the same time (and they have the same title). Then you open both and manually look at them and in case that you cannot decide, you'll have to externally ask for the details again. However, this is a problem if the time and title has changed. So maybe you are right that it is not a good idea. 

(I've also been thinking about using command, but if it cannot get some gui stuff up, it will stay a problem. And from the description, it seemed that vdirsyncer did not take both if they are different.)

bernhardreiter avatar May 21 '21 13:05 bernhardreiter

vdirsyncer expects both to be identical. Them being different is an indicator of "resolution command failed".

I guess the command logic could be extended to have some fancier form of communication (e.g.: reading the stdout of the command to determine more complex outcomes).

As an alternate approach, I think you could just use command and use write a short script to send yourself an email or show a desktop notification saying "conflict in vdirsyncer". The alert should suffice, and you can then re-run vdirsyncer manually on a terminal and do the conflict resolution yourself.

WhyNotHugo avatar May 21 '21 13:05 WhyNotHugo

Writing a script for interactive use is probably a good idea to solve my use case. Still I believe the idea to be able to return two events that are both taken is a good one. (Or even to be able to delete both events). It would make the command more flexible. Here is my script (v1), tested on Debian Buster with current master:

#!/usr/bin/env python3
"""Ask to resolve a vdirsyncer sync conflict interatively.

Currently kdialog is hardcoded, but can be easily be replaced.

Needs at least python3.5.

  conflict_resolution = ["command", "/home/bern/vdirsyncer/conflictscript.py"]

This file is Free Software under the following license:
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 Intevation GmbH <https://intevation.de>
Author: <[email protected]>
"""

from pathlib import Path
import re
import subprocess
import sys

KDIALOG = "/usr/bin/kdialog"

SUMMARY_PATTERN = re.compile("^(SUMMARY:.*)$", re.MULTILINE)

def get_summary(icalendar_text:str):
    """Get the first SUMMARY: line from an iCalendar text."""
    match = re.search(SUMMARY_PATTERN, icalendar_text)
    return match[1]

def main(ical1_filename, ical2_filename):
    ical1 = ical1_filename.read_text()
    ical2 = ical2_filename.read_text()

    additional_args = ["--yes-label", "take first"] # return code == 0
    additional_args += ["--no-label", "take second"] # return code == 1
    additional_args += ["--cancel-label", "do not resolve"] # return code == 2

    r = subprocess.run(args = [
        KDIALOG,
        "--warningyesnocancel",
        "The was a sync conflict, do you prefer the first entry: \n" +
        get_summary(ical1) + "...\n(full contents: " + str(ical1_filename) +
        " )\n\nor the second entry: \n" +
        get_summary(ical2) + "...\n(full contents: " + str(ical2_filename) + 
        " )"
    ] + additional_args)

    if r.returncode == 2:
        # cancel was pressed
        return # shall lead to items not changed, because not copied

    if r.returncode == 0:
        # we want to take the first item, so overwrite the second
        ical2_filename.write_text(ical1)
    else: # r.returncode == 1, we want the second item, so overwrite the first
        ical1_filename.write_text(ical2)

main(Path(sys.argv[1]), Path(sys.argv[2]))

bernhardreiter avatar May 25 '21 15:05 bernhardreiter