Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ Matthew Rademaker - matthew.rademaker [at] gmail [dot] com
Valentin Iovene - val [at] too [dot] gy
Julian Wollrath
Mattori Birnbaum - me [at] mattori [dot] com - https://mattori.com
Piotr Wojciech Dabrowski - piotr.dabrowski [at] htw-berlin [dot] de
1 change: 1 addition & 0 deletions khal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def build_collection(conf, selection):
'priority': cal['priority'],
'ctype': cal['type'],
'addresses': cal['addresses'],
'address_adapter': cal['address_adapter']
}
collection = khalendar.CalendarCollection(
calendars=props,
Expand Down
37 changes: 31 additions & 6 deletions khal/khalendar/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import datetime as dt
import logging
import os
import re
from typing import Dict, List, Optional, Tuple, Type, Union

import icalendar
Expand All @@ -43,6 +44,29 @@
logger = logging.getLogger('khal')


class Attendee:
def __init__(self, defline):
m = re.match(r"(?P<name>.*)\<(?P<mail>.*)\>", defline)
if m is not None and m.group("name") is not None and m.group("mail") is not None:
self.cn = m.group("name").strip()
self.mail = m.group("mail").strip().lower()
else:
self.cn = None
self.mail = defline.strip().lower()

@staticmethod
def format_vcard(vcard):
data = str(vcard).split(":")
if len(data) > 1:
mail = data[1]
else:
mail = str(vcard)
cn = mail
if "CN" in vcard.params:
cn = vcard.params["CN"]
return f"{cn} <{mail}>"


class Event:
"""base Event class for representing a *recurring instance* of an Event

Expand Down Expand Up @@ -493,13 +517,12 @@ def update_location(self, location: str) -> None:
def attendees(self) -> str:
addresses = self._vevents[self.ref].get('ATTENDEE', [])
if not isinstance(addresses, list):
addresses = [addresses, ]
return ", ".join([address.split(':')[-1]
for address in addresses])
return addresses
return ", ".join([Attendee.format_vcard(address) for address in addresses])

def update_attendees(self, attendees: List[str]):
assert isinstance(attendees, list)
attendees = [a.strip().lower() for a in attendees if a != ""]
attendees = [Attendee(a) for a in attendees if a != ""]
if len(attendees) > 0:
# first check for overlaps in existing attendees.
# Existing vCalAddress objects will be copied, non-existing
Expand All @@ -510,11 +533,13 @@ def update_attendees(self, attendees: List[str]):
for attendee in attendees:
for old_attendee in old_attendees:
old_email = old_attendee.lstrip("MAILTO:").lower()
if attendee == old_email:
if attendee.mail == old_email:
vCalAddresses.append(old_attendee)
unchanged_attendees.append(attendee)
for attendee in [a for a in attendees if a not in unchanged_attendees]:
item = icalendar.prop.vCalAddress(f'MAILTO:{attendee}')
item = icalendar.prop.vCalAddress(f'MAILTO:{attendee.mail}')
if attendee.cn is not None:
item.params['CN'] = attendee.cn
item.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT')
item.params['PARTSTAT'] = icalendar.prop.vText('NEEDS-ACTION')
item.params['CUTYPE'] = icalendar.prop.vText('INDIVIDUAL')
Expand Down
17 changes: 17 additions & 0 deletions khal/khalendar/khalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import logging
import os
import os.path
import re
import subprocess
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union # noqa

from ..custom_types import CalendarConfiguration, EventCreationTypes, LocaleConfiguration
Expand Down Expand Up @@ -104,9 +106,12 @@ def __init__(self,
self.priority = priority
self.highlight_event_days = highlight_event_days
self._locale = locale
self._contacts: Dict[str, List[str]] = {} # List of mail addresses of contacts
self._backend = backend.SQLiteDb(self.names, dbpath, self._locale)
self._last_ctags: Dict[str, str] = {}
self.update_db()
for calendar in self._calendars.keys():
self._contacts_update(calendar)

@property
def writable_names(self) -> List[str]:
Expand Down Expand Up @@ -361,12 +366,24 @@ def _needs_update(self, calendar: str, remember: bool=False) -> bool:
self._last_ctags[calendar] = local_ctag
return local_ctag != self._backend.get_ctag(calendar)

def _contacts_update(self, calendar: str) -> None:
adaptercommand = self._calendars[calendar].get('address_adapter')
if adaptercommand is None:
self._contacts[calendar] = []
else:
res = subprocess.check_output(["bash", "-c", adaptercommand])
maildata = [re.split(r"\s{2,}", x) for x in res.decode("utf-8").split("\n")]
mails = [f"{x[0]} <{x[2]}>" for x in maildata if len(x) > 1]
self._contacts[calendar] = mails


def _db_update(self, calendar: str) -> None:
"""implements the actual db update on a per calendar base"""
local_ctag = self._local_ctag(calendar)
db_hrefs = {href for href, etag in self._backend.list(calendar)}
storage_hrefs: Set[str] = set()
bdays = self._calendars[calendar].get('ctype') == 'birthdays'
self._contacts_update(calendar)

with self._backend.at_once():
for href, etag in self._storages[calendar].list():
Expand Down
9 changes: 8 additions & 1 deletion khal/settings/khal.spec
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ type = option('calendar', 'birthdays', 'discover', default='calendar')
# belongs to the user.
addresses = force_list(default='')

# The address adapter is a command that will be run using "bash -c" to get
# a list of addresses for autocompletion in the attendee field of a new event
# in interactive mode. This expects the same output as "khard email | tail -n +2"
# since it has only been developed with khard in mind - but other providers
# should be configurable to create the same output, or the parser could
# be extended (see _contacts_update in CalenderCollection in khalendar.py)
address_adapter = string(default=None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What behaviour does None cause and why not default to khard email | tail -n +2?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None causes no autocompletion to be available (no address book). The idea behind not setting the default to khard email was that not everyone might have khard installed.

However, I agree that this is counterintuitive. I have committed a change that:

  • Adds an address_adapter field to the [default] section that
    • Is, by default, khard email | tail -n +2
    • Can be None, meaning "empty address book"
  • Adds the possible value "default" (which is also the default) to the address_adapter in the account section. That value causes the address_adapter from the [default] section to be used for that calendar
  • Tests whether the command from address_adapter was run successfully. If that is not the case, the effect is the same as setting address_adapter to None, i.e. the address book is assumed to be empty.

Does that make more sense?


[sqlite]
# khal stores its internal caching database here, by default this will be in the *$XDG_DATA_HOME/khal/khal.db* (this will most likely be *~/.local/share/khal/khal.db*).
path = expand_db_path(default=None)
Expand Down Expand Up @@ -231,7 +239,6 @@ default_dayevent_alarm = timedelta(default='')
# 'ikhal' only)
enable_mouse = boolean(default=True)


# The view section contains configuration options that effect the visual appearance
# when using khal and ikhal.

Expand Down
Loading