From 14dde084755a8b15004d59bb6be5cc7a3726a8bf Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 14 Jun 2010 13:01:41 +0000 Subject: 28038: improved handling of recurring events in calendar system --- Functions/Calendar/calendar_add | 178 +++++++++++++++++++++++++++++----------- 1 file changed, 132 insertions(+), 46 deletions(-) (limited to 'Functions/Calendar/calendar_add') diff --git a/Functions/Calendar/calendar_add b/Functions/Calendar/calendar_add index eded25b2a..c06deda3a 100644 --- a/Functions/Calendar/calendar_add +++ b/Functions/Calendar/calendar_add @@ -7,14 +7,19 @@ # entry before the first existing entry with a later date and time. emulate -L zsh -setopt extendedglob +setopt extendedglob # xtrace local context=":datetime:calendar_add:" +local vdatefmt="%Y%m%dT%H%M%S" +local d='[[:digit:]]' -local calendar newfile REPLY lastline opt -local -a calendar_entries lockfiles reply -integer my_date done rstat nolock nobackup new_recurring old_recurring -local -A reply parse_new parse_old recurring_uids +local calendar newfile REPLY lastline opt text occur +local -a calendar_entries lockfiles reply occurrences +integer my_date done rstat nolock nobackup new_recurring +integer keep_my_uid +local -A reply parse_new parse_old +local -a match mbegin mend +local my_uid their_uid autoload -U calendar_{parse,read,lockfiles} @@ -47,7 +52,6 @@ if ! calendar_parse $addline; then fi parse_new=("${(@kv)reply}") (( my_date = $parse_new[time] )) -[[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 )) if zstyle -t $context reformat-date; then local datefmt zstyle -s $context date-format datefmt || @@ -55,12 +59,24 @@ if zstyle -t $context reformat-date; then strftime -s REPLY $datefmt $parse_new[time] addline="$REPLY $parse_new[text1]" fi +if [[ -n $parse_new[rptstr] ]]; then + (( new_recurring = 1 )) + if [[ $parse_new[rptstr] = CANCELLED ]]; then + (( done = 1 )) + elif [[ $addline = (#b)(*[[:space:]\#]RECURRENCE[[:space:]]##)([^[:space:]]##)([[:space:]]*|) ]]; then + # Use the updated recurrence time + strftime -s REPLY $vdatefmt ${parse_new[schedrpttime]} + addline="${match[1]}$REPLY${match[3]}" + else + # Add a recurrence time + [[ $addline = *$'\n' ]] || addline+=$'\n' + strftime -s REPLY $vdatefmt ${parse_new[schedrpttime]} + addline+=" # RECURRENCE $REPLY" + fi +fi # $calendar doesn't necessarily exist yet. -local -a match mbegin mend -local my_uid their_uid - # Match a UID, a unique identifier for the entry inherited from # text/calendar format. local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)' @@ -87,14 +103,112 @@ fi calendar_read $calendar if [[ -n $my_uid ]]; then - # Pre-scan to find recurring events with a UID + # Pre-scan to events with the same UID for line in $calendar_entries; do calendar_parse $line || continue + parse_old=("${(@kv)reply}") # Recurring with a UID? - if [[ -n $reply[rpttime] && $line = ${~uidpat} ]]; then - # Yes, so record this as a recurring event. + if [[ $line = ${~uidpat} ]]; then their_uid=${(U)match[1]} - recurring_uids[$their_uid]=$reply[time] + if [[ $their_uid = $my_uid ]]; then + # Deal with recurrences and also some add some + # extra magic for cancellation. + + # See if we have found a recurrent version + if [[ -z $parse_old[rpttime] ]]; then + # No, so assume this is a straightforward replacement + # of a non-recurring event. + + # Is this a cancellation of a non-recurring event? + # Look for an OCCURRENCE in the form + # OCCURRENCE 20100513T110000 CANCELLED + # although we don't bother looking at the date/time--- + # it's one-off, so this should already be unique. + if [[ $new_recurring -eq 0 && \ + $parse_new[text1] = (|*[[:space:]\#])"OCCURRENCE"[[:space:]]##([^[:space:]]##[[:space:]]##CANCELLED)(|[[:space:]]*) ]]; then + # Yes, so skip both the old and new events. + (( done = 1 )) + fi + # We'll skip this UID when we encounter it again. + continue + fi + if (( new_recurring )); then + # Replacing a recurrence; there can be only one. + # TBD: do we replace existing occurrences of the + # replaced recurrent event? I'm guessing not, but + # if we keep the UID then maybe we should. + # + # TBD: ick, suppose we're cancelling an even that + # we added to a recurring sequence but didn't replace + # the recurrence. We might get RPT CANCELLED for this. + # That would be bad. Should invent better way of + # cancelling non-recurring event. + continue + else + # The recorded event is recurring, but the new one is a + # one-off event. If there are embedded OCCURRENCE declarations, + # use those. + # + # TBD: We could be clever about text associated + # with the occurrence. Copying the entire text + # of the meeting seems like overkill but people often + # add specific remarks about why this occurrence was + # moved/cancelled. + # + # TBD: one case we don't yet handle is cancelling + # something that isn't replacing a recurrence, i.e. + # something we added as OCCURRENCE XXXXXXXXTXXXXXX . + # If we're adding a CANCELLED occurrence we should + # look to see if it matches and if so simply + # delete that occurrence. + # + # TBD: one nasty case is if the new occurrence + # occurs before the current scheduled time. As we + # never look backwards we'll miss it. + text=$addline + occurrences=() + while [[ $text = (#b)(|*[[:space:]\#])"OCCURRENCE"[[:space:]]##([^[:space:]]##[[:space:]]##[^[:space:]]##)(|[[:space:]]*) ]]; do + occurrences+=($match[2]) + text="$match[1] $match[3]" + done + if (( ! ${#occurrences} )); then + # No embedded occurrences. We'll manufacture one + # that doesn't refer to an original recurrence. + strftime -s REPLY $vdatefmt $my_date + occurrences=("XXXXXXXXTXXXXXX $REPLY") + fi + # Add these occurrences, checking if they replace + # an existing one. + for occur in ${(o)occurrences}; do + REPLY=${occur%%[[:space:]]*} + # Only update occurrences that refer to genuine + # recurrences. + if [[ $REPLY = [[:digit:]](#c8)T[[:digit:]](#c6) && $line = (#b)(|*[[:space:]\#])(OCCURRENCE[[:space:]]##)${REPLY}[[:space:]]##[^[:space:]]##(|[[:space:]]*) ]]; then + # Yes, update in situ + line="${match[1]}${match[2]}$occur${match[3]}" + else + # No, append. + [[ $line = *$'\n' ]] || line+=$'\n' + line+=" # OCCURRENCE $occur" + fi + done + # The line we're adding now corresponds to the + # original event. We'll skip the matching UID + # in the file below, however. + addline=$line + # We need to work out which event is next, so + # reparse. + if calendar_parse $addline; then + parse_new=("${(@kv)reply}") + (( my_date = ${parse_new[time]} )) + if zstyle -t $context reformat-date; then + zstyle -s $context date-format datefmt + strftime -s REPLY $datefmt $parse_new[time] + addline="$REPLY $parse_new[text1]" + fi + fi + fi + fi fi done fi @@ -107,39 +221,11 @@ fi print -r -- $addline (( done = 1 )) fi - if [[ -n $parse_old[rpttime] ]]; then - (( old_recurring = 1 )) - else - (( old_recurring = 0 )) - fi - if [[ -n $my_uid && $line = ${~uidpat} ]]; then + # We've already merged any information on the same UID + # with our new text, probably. + if [[ $keep_my_uid -eq 0 && -n $my_uid && $line = ${~uidpat} ]]; then their_uid=${(U)match[1]} - if [[ $my_uid = $their_uid ]]; then - # Deal with recurrences, being careful in case there - # are one-off variants that don't replace recurrences. - # - # Bug 1: "calendar" still doesn't know about one-off variants. - # Bug 2: neither do I; how do we know which occurrence - # it replaces? - # Bug 3: the code for calculating recurrences is awful anyway. - - if (( old_recurring && new_recurring )); then - # Replacing a recurrence; there can be only one. - continue - elif (( ! new_recurring )); then - # Not recurring. See if we have previously found - # a recurrent version - [[ -n $recurring_uids[$their_uid] ]] && (( old_recurring = 1 )) - # No, so assume this is a straightforward replacement - # of a non-recurring event. - (( ! old_recurring )) && continue - # It's recurring, but if this is a one-off at the - # same time as the previous one, replace anyway. - [[ -z $parse_old[$rpttime] ]] && - (( ${parse_new[time]} == ${parse_old[time]} )) && - continue - fi - fi + [[ $my_uid = $their_uid ]] && continue fi if [[ $parse_old[time] -eq $my_date && $line = $addline ]]; then (( done )) && continue # paranoia: shouldn't happen @@ -157,7 +243,7 @@ New calendar left in $newfile." >&2 fi fi else - print -r -- $line >$newfile + (( done )) || print -r -- $addline >$newfile fi if (( !rstat )) && ! mv $newfile $calendar; then -- cgit 1.4.1