7 commits - conf/kolab.conf pykolab/imap pykolab/xml wallace/module_optout.py wallace/module_resources.py
Jeroen van Meeuwen
vanmeeuwen at kolabsys.com
Tue May 22 04:47:24 CEST 2012
conf/kolab.conf | 11 +
pykolab/imap/__init__.py | 36 +++-
pykolab/xml/attendee.py | 12 +
pykolab/xml/contact_reference.py | 8
pykolab/xml/event.py | 317 +++++++++++++++++++++++++++++++++------
wallace/module_optout.py | 10 -
wallace/module_resources.py | 200 +++++++++++++++++++++---
7 files changed, 518 insertions(+), 76 deletions(-)
New commits:
commit 3e893314d7c0a6caf007c98b9089d596669416e0
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue May 22 03:46:42 2012 +0100
Update Wallace resource module to send iTip responses to the organizer
Create directory tree needed
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index 54cf85f..c9be1f1 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -17,6 +17,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
+import datetime
import icalendar
import json
import os
@@ -50,15 +51,27 @@ auth = None
imap = None
def __init__():
- if not os.path.isdir(mybasepath):
- os.makedirs(mybasepath)
-
modules.register('resources', execute, description=description())
+def accept(filepath):
+ new_filepath = os.path.join(mybasepath, 'ACCEPT', os.path.basename(filepath))
+ os.rename(filepath, new_filepath)
+ filepath = new_filepath
+ exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources',filepath))
+
def description():
return """Resource management module."""
def execute(*args, **kw):
+ if not os.path.isdir(mybasepath):
+ os.makedirs(mybasepath)
+
+ for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER' ]:
+ if not os.path.isdir(os.path.join(mybasepath, stage)):
+ os.makedirs(os.path.join(mybasepath, stage))
+
+ log.debug(_("Resource Management called for %r, %r") % (args, kw), level=9)
+
auth = Auth()
auth.connect()
@@ -92,8 +105,11 @@ def execute(*args, **kw):
)
return
-
- log.debug(_("Resource Management called for %r, %r") % (args, kw), level=8)
+ else:
+ # Move to incoming
+ new_filepath = os.path.join(mybasepath, 'incoming', os.path.basename(filepath))
+ os.rename(filepath, new_filepath)
+ filepath = new_filepath
message = message_from_file(open(filepath, 'r'))
@@ -107,7 +123,7 @@ def execute(*args, **kw):
"iTip.")
)
- exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources',filepath))
+ accept(filepath)
return
else:
@@ -120,12 +136,14 @@ def execute(*args, **kw):
# See if a resource is actually being allocated
if len([x['resources'] for x in itip_events if x.has_key('resources')]) == 0:
if len([x['attendees'] for x in itip_events if x.has_key('attendees')]) == 0:
- exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources',filepath))
+ accept(filepath)
return
- # A simple list of merely resource entry IDs
+ # A simple list of merely resource entry IDs that hold any relevance to the
+ # iTip events
resource_records = resource_records_from_itip_events(itip_events)
+ # Get the resource details, which includes details on the IMAP folder
resources = {}
for resource_record in list(set(resource_records)):
# Get the attributes for the record
@@ -135,13 +153,21 @@ def execute(*args, **kw):
resource_attrs = auth.get_entry_attributes(None, resource_record, ['*'])
if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
if resource_attrs.has_key('uniquemember'):
+ resources[resource_record] = resource_attrs
for uniquemember in resource_attrs['uniquemember']:
resource_attrs = auth.get_entry_attributes(None, uniquemember, ['*'])
if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
- resources[resource_record] = resource_attrs
+ resources[uniquemember] = resource_attrs
+ resources[uniquemember]['memberof'] = resource_record
else:
resources[resource_record] = resource_attrs
+ log.debug(_("Resources: %r") % (resources), level=8)
+
+ # For each resource, determine if any of the events in question is in
+ # conflict.
+ #
+ # Store the (first) conflicting event(s) alongside the resource information.
for resource in resources.keys():
if not resources[resource].has_key('kolabtargetfolder'):
continue
@@ -162,7 +188,7 @@ def execute(*args, **kw):
typ, data = imap.imap.m.search(None, 'ALL')
for num in data[0].split():
- # For efficiency, non-deterministic
+ # For efficiency, makes the routine non-deterministic
if resources[resource]['conflict']:
continue
@@ -182,9 +208,35 @@ def execute(*args, **kw):
log.debug(_(" event %r start: %r") % (event.get_uid(),event.get_start()), level=9)
log.debug(_(" event %r end: %r") % (event.get_uid(),event.get_end()), level=9)
- if event.get_start() < itip['start']:
- if event.get_start() <= itip['end']:
- if event.get_end() <= itip['start']:
+ _es = event.get_start()
+ _is = itip['start']
+
+ if type(_es) == 'datetime.date':
+ log.debug(_("_es is datetime.date"))
+ if type(_is) == 'datetime.datetime':
+ _is = datetime.date(_is.year, _is.month, _is.day)
+ else:
+ pass
+ else:
+ log.debug(_("_es is datetime.datetime"))
+ if type(_is) == 'datetime.date':
+ log.debug(_("_is is datetime.date"))
+ _es = datetime.date(_es.year, _es.month, _es.day)
+
+ _ee = event.get_end()
+ _ie = itip['end']
+ if type(_ee) == 'datetime.date':
+ if type(_ie) == 'datetime.datetime':
+ _ie = datetime.date(_ie.year, _ie.month, _ie.day)
+ else:
+ pass
+ else:
+ if type(_ie) == 'datetime.date':
+ _ee = datetime.date(_ee.year, _ee.month, _ee.day)
+
+ if _es < _is:
+ if _es <= _ie:
+ if _ie <= _is:
conflict = False
else:
log.debug(_("Event %r ends later than invitation") % (event.get_uid()), level=9)
@@ -192,11 +244,11 @@ def execute(*args, **kw):
else:
log.debug(_("Event %r starts before invitation ends") % (event.get_uid()), level=9)
conflict = True
- elif event.get_start() == itip['start']:
+ elif _es == _is:
log.debug(_("Event %r and invitation start at the same time") % (event.get_uid()), level=9)
conflict = True
- else: # event.get_start() > itip['start']
- if event.get_start() <= itip['end']:
+ else: # _es > _is
+ if _es <= _ie:
log.debug(_("Event %r starts before invitation ends") % (event.get_uid()), level=9)
conflict = True
else:
@@ -209,6 +261,91 @@ def execute(*args, **kw):
log.debug(_("Resource status information: %r") % (resources[resource]), level=8)
+
+ for resource in resources.keys():
+ log.debug(_("Polling for resource %r") % (resource), level=9)
+
+ if not resources.has_key(resource):
+ log.debug(_("Resource %r has been popped from the list") % (resource), level=9)
+ continue
+
+ if not resources[resource].has_key('conflicting_events'):
+ log.debug(_("Resource is a collection"), level=9)
+ continue
+
+ if len(resources[resource]['conflicting_events']) > 0:
+ # This is the event being conflicted with!
+ for itip_event in itip_events:
+ # Now we have the event that was conflicting
+ if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+ itip_event['xml'].set_attendee_participant_status([a for a in itip_event['xml'].get_attendees() if a.get_email() == resources[resource]['mail']][0], "DECLINED")
+
+ send_response(resources[resource]['mail'], itip_events)
+ else:
+ # This must have been a resource collection originally.
+ # We have inserted the reference to the original resource
+ # record in 'memberof'.
+ if resources[resource].has_key('memberof'):
+ original_resource = resources[resources[resource]['memberof']]
+
+ _target_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
+
+ # unset all
+ for _r in original_resource['uniquemember']:
+ del resources[_r]
+
+ if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+ itip_event['xml'].set_attendee_participant_status(
+ [a for a in itip_event['xml'].get_attendees() if a.get_email() == original_resource['mail']][0],
+ "DECLINED"
+ )
+
+ send_response(original_resource['mail'], itip_events)
+
+ else:
+ # No conflicts, go accept
+ for itip_event in itip_events:
+ if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+ itip_event['xml'].set_attendee_participant_status(
+ [a for a in itip_event['xml'].get_attendees() if a.get_email() == resources[resource]['mail']][0],
+ "ACCEPTED"
+ )
+
+ log.debug(_("Adding event to %r") % (resources[resource]['kolabtargetfolder']), level=9)
+ imap.imap.m.append(resources[resource]['kolabtargetfolder'], None, None, itip_event['xml'].to_message().as_string())
+
+ send_response(resources[resource]['mail'], itip_events)
+
+ else:
+ # This must have been a resource collection originally.
+ # We have inserted the reference to the original resource
+ # record in 'memberof'.
+ if resources[resource].has_key('memberof'):
+ original_resource = resources[resources[resource]['memberof']]
+
+ _target_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
+
+ # unset all
+ for _r in original_resource['uniquemember']:
+ del resources[_r]
+
+ if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+ itip_event['xml'].set_attendee_participant_status(
+ [a for a in itip_event['xml'].get_attendees() if a.get_email() == original_resource['mail']][0],
+ "ACCEPTED"
+ )
+
+ log.debug(_("Adding event to %r") % (_target_resource['kolabtargetfolder']), level=9)
+ imap.imap.m.append(_target_resource['kolabtargetfolder'], None, None, itip_event['xml'].to_message().as_string())
+
+ send_response(original_resource['mail'], itip_events)
+
+ # Disconnect IMAP or we lock the mailbox almost constantly
+ imap.disconnect()
+ del imap
+
+ os.unlink(filepath)
+
def itip_events_from_message(message):
"""
Obtain the iTip payload from email.message <message>
@@ -229,19 +366,14 @@ def itip_events_from_message(message):
if part.get_param('method') == "REQUEST":
# Python iCalendar prior to 3.0 uses "from_string".
- itip_payload = part.get_payload()
+ itip_payload = part.get_payload(decode=True)
if hasattr(icalendar.Calendar, 'from_ical'):
cal = icalendar.Calendar.from_ical(itip_payload)
elif hasattr(icalendar.Calendar, 'from_string'):
cal = icalendar.Calendar.from_string(itip_payload)
else:
log.error(_("Could not read iTip from message."))
- exec(
- 'modules.cb_action_ACCEPT(%r, %r)' % (
- 'resources',
- filepath
- )
- )
+ accept(filepath)
return
@@ -294,10 +426,14 @@ def resource_records_from_itip_events(itip_events):
resource_records = []
+ log.debug(_("Raw itip_events: %r") % (itip_events), level=9)
attendees_raw = []
- for list_attendees_raw in [x for x in [y['attendees'] for y in itip_events if y.has_key('attendees')]]:
+ for list_attendees_raw in [x for x in [y['attendees'] for y in itip_events if y.has_key('attendees') and isinstance(y['attendees'], list)]]:
attendees_raw.extend(list_attendees_raw)
+ for list_attendees_raw in [y['attendees'] for y in itip_events if y.has_key('attendees') and isinstance(y['attendees'], basestring)]:
+ attendees_raw.append(list_attendees_raw)
+
log.debug(_("Raw set of attendees: %r") % (attendees_raw), level=9)
# TODO: Resources are actually not implemented in the format. We reset this
@@ -364,4 +500,18 @@ def resource_records_from_itip_events(itip_events):
log.debug(_("The following resources are being referred to in the iTip: %r") % (resource_records), level=8)
- return resource_records
\ No newline at end of file
+ return resource_records
+
+def send_response(from_address, itip_events):
+ import smtplib
+ smtp = smtplib.SMTP("localhost", 10027)
+
+ if conf.debuglevel > 8:
+ smtp.set_debuglevel(True)
+
+ for itip_event in itip_events:
+ attendee = [a for a in itip_event['xml'].get_attendees() if a.get_email() == from_address][0]
+ participant_status = itip_event['xml'].get_attendee_participant_status(attendee)
+ message = itip_event['xml'].to_message_itip(from_address, method="REPLY", participant_status=participant_status)
+ smtp.sendmail(message['From'], message['To'], message.as_string())
+ smtp.quit()
commit 7184d5f07958887e500907d28a38659595ea62c6
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue May 22 03:46:06 2012 +0100
Create the necessary directories way after init
diff --git a/wallace/module_optout.py b/wallace/module_optout.py
index 1aedc7e..5540d80 100644
--- a/wallace/module_optout.py
+++ b/wallace/module_optout.py
@@ -41,15 +41,19 @@ conf = pykolab.getConf()
mybasepath = '/var/spool/pykolab/wallace/optout/'
def __init__():
- if not os.path.isdir(mybasepath):
- os.makedirs(mybasepath)
-
modules.register('optout', execute, description=description())
def description():
return """Consult the opt-out service."""
def execute(*args, **kw):
+ if not os.path.isdir(mybasepath):
+ os.makedirs(mybasepath)
+
+ for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER' ]:
+ if not os.path.isdir(os.path.join(mybasepath, stage)):
+ os.makedirs(os.path.join(mybasepath, stage))
+
# TODO: Test for correct call.
filepath = args[0]
commit fd2cee5019d765fa28e1f2d9c33bd5c4af08c367
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue May 22 03:45:34 2012 +0100
Update event to spec of replying with sensible iTip messages
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index a23b785..f516ead 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -3,11 +3,16 @@ import icalendar
import kolabformat
import time
+import pykolab
from pykolab import constants
+from pykolab import utils
+from pykolab.translate import _
from attendee import Attendee
from contact_reference import ContactReference
+log = pykolab.getLogger('pykolab.xml_event')
+
def event_from_ical(string):
return Event(from_ical=string)
@@ -15,7 +20,12 @@ def event_from_string(string):
return Event(from_string=string)
class Event(object):
- StatusTentative = kolabformat.StatusTentative
+ status_map = {
+ "TENTATIVE": kolabformat.StatusTentative,
+ "CONFIRMED": kolabformat.StatusConfirmed,
+ "CANCELLED": kolabformat.StatusCancelled,
+ }
+
def __init__(self, from_ical="", from_string=""):
self._attendees = []
@@ -27,12 +37,13 @@ class Event(object):
else:
self.from_ical(from_ical)
- def add_attendee(self, email, name=None, rsvp=False, role=None):
- attendee = Attendee(email, name, rsvp, role)
+ def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None):
+ log.debug(_("adding attendee with email address %r") % (email), level=9)
+ attendee = Attendee(email, name, rsvp, role, participant_status)
self._attendees.append(attendee)
self.event.setAttendees(self._attendees)
- def as_string_itip(self):
+ def as_string_itip(self, method="REQUEST"):
cal = icalendar.Calendar()
cal.add(
'prodid',
@@ -46,7 +57,7 @@ class Event(object):
# TODO: Really?
cal.add('calscale', 'GREGORIAN')
# TODO: Not always a request...
- cal.add('method', 'REQUEST')
+ cal.add('method', method)
# TODO: Add timezone information using icalendar.?()
# Not sure if there is a class for it.
@@ -117,6 +128,8 @@ class Event(object):
elif hasattr(icalendar.Event, 'from_string'):
ical_event = icalendar.Event.from_string(ical)
+ # TODO: Clause the timestamps for zulu suffix causing datetime.datetime
+ # to fail substitution.
for attr in list(set(ical_event.required)):
if ical_event.has_key(attr):
if hasattr(self, 'set_ical_%s' % (attr.lower())):
@@ -140,8 +153,11 @@ class Event(object):
else:
print attr, "exists but no function exists"
+ def get_attendee_participant_status(self, attendee):
+ return attendee.get_participant_status()
+
def get_attendees(self):
- return self.event.attendees()
+ return self._attendees
def get_created(self):
_datetime = self.event.created()
@@ -199,6 +215,7 @@ class Event(object):
contact = attendee.contact()
rsvp = attendee.rsvp()
role = attendee.role()
+ partstat = attendee.partStat()
if rsvp:
_rsvp = "TRUE"
@@ -211,6 +228,7 @@ class Event(object):
#NonParticipant = _kolabformat.NonParticipant
# TODO: Check the role strings for validity
+ # TODO^2: Use map
if role == kolabformat.Required:
_role = "REQ-PARTICIPANT"
elif role == kolabformat.Chair:
@@ -219,8 +237,22 @@ class Event(object):
_role = "OPTIONAL"
elif role == kolabformat.NonParticipant:
_role = "NON-PARTICIPANT"
+ else:
+ _role = "OPTIONAL"
+
+ if partstat == kolabformat.PartNeedsAction:
+ _partstat = "NEEDS-ACTION"
+ elif partstat == kolabformat.PartAccepted:
+ _partstat = "ACCEPTED"
+ elif partstat == kolabformat.PartDeclined:
+ _partstat = "DECLINED"
+ elif partstat == kolabformat.PartTentative:
+ _partstat = "TENTATIVE"
+ elif partstat == kolabformat.PartDelegated:
+ _partstat = "DELEGATED"
_attendee = "RSVP=%s" % _rsvp
+ _attendee += ";PARTSTAT=%s" % _partstat
_attendee += ";ROLE=%s" % _role
_attendee += ";MAILTO:%s" % contact.email()
@@ -249,6 +281,7 @@ class Event(object):
def get_ical_organizer(self):
organizer = self.get_organizer()
name = organizer.name()
+
if not name:
return "mailto:%s" % (organizer.email())
else:
@@ -257,32 +290,14 @@ class Event(object):
def get_ical_status(self):
status = self.event.status()
- # TODO: See which ones are actually valid for iTip
- if status == kolabformat.StatusUndefined:
- _status = "UNDEFINED"
- elif status == kolabformat.StatusNeedsAction:
- _status = "NEEDS-ACTION"
- elif status == kolabformat.StatusCompleted:
- _status = "COMPLETED"
- elif status == kolabformat.StatusInProcess:
- _status = "INPROCESS"
- elif status == kolabformat.StatusCancelled:
- _status = "CANCELLED"
- elif status == kolabformat.StatusTentative:
- _status = "TENTATIVE"
- elif status == kolabformat.StatusConfirmed:
- _status = "CONFIRMED"
- elif status == kolabformat.StatusDraft:
- _status = "DRAFT"
- elif status == kolabformat.StatusFinal:
- _status = "FINAL"
- else:
- _status = "UNDEFINED"
-
- return _status
+ for key in self.status_map.keys():
+ if status == self.status_map[key]:
+ return key
def get_organizer(self):
- return self.event.organizer()
+ organizer = self.event.organizer()
+ #print organizer
+ return organizer
def get_priority(self):
return self.event.priority()
@@ -308,6 +323,12 @@ class Event(object):
return datetime.datetime(year, month, day, hour, minute, second)
+ def get_status(self):
+ status = self.event.status()
+ for key in self.status_map.keys():
+ if self.status_map[key] == status:
+ return key
+
def get_summary(self):
return self.event.summary()
@@ -319,6 +340,10 @@ class Event(object):
self.__str__()
return kolabformat.getSerializedUID()
+ def set_attendee_participant_status(self, attendee, status):
+ attendee.set_participant_status(status)
+ self.event.setAttendees(self._attendees)
+
def set_created(self, _datetime=None):
if _datetime == None:
_datetime = datetime.datetime.now()
@@ -343,7 +368,7 @@ class Event(object):
kolabformat.cDateTime(year, month, day, hour, minute, second)
)
- def set_end(self, _datetime):
+ def set_dtstamp(self, _datetime):
(
year,
month,
@@ -360,23 +385,73 @@ class Event(object):
_datetime.second
)
+ self.event.setLastModified(
+ kolabformat.cDateTime(year, month, day, hour, minute, second)
+ )
+
+ def set_end(self, _datetime):
+ (
+ year,
+ month,
+ day,
+ ) = (
+ _datetime.year,
+ _datetime.month,
+ _datetime.day,
+ )
+ if hasattr(_datetime, 'hour'):
+ (
+ hour,
+ minute,
+ second
+ ) = (
+ _datetime.hour,
+ _datetime.minute,
+ _datetime.second
+ )
+ else:
+ (hour, minute, second) = (0,0,0)
+
self.event.setEnd(
kolabformat.cDateTime(year, month, day, hour, minute, second)
)
def set_ical_attendee(self, _attendee):
+ log.debug(_("set attendees from ical: %r") % (_attendee), level=9)
+
if isinstance(_attendee, list):
for attendee in _attendee:
+ #print attendee
rsvp = False
role = None
cn = None
+ partstat = None
address = None
for param in attendee.split(';'):
if (len(param.split('=')) > 1):
exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
- if (len(param.split(':')) > 1):
- address = param.split(':')[1]
- self.add_attendee(address, name=cn, rsvp=rsvp, role=role)
+ #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
+ if (len(attendee.split(':')) > 1):
+ address = attendee.split(':')[-1]
+ #print address
+
+ self.add_attendee(address, name=cn, rsvp=rsvp, role=role, participant_status=partstat)
+ else:
+ #print attendee
+ rsvp = False
+ role = None
+ cn = None
+ partstat = None
+ address = None
+ for param in _attendee.split(';'):
+ if (len(param.split('=')) > 1):
+ exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
+ #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
+ if (len(_attendee.split(':')) > 1):
+ address = _attendee.split(':')[-1]
+ #print address
+
+ self.add_attendee(address, name=cn, rsvp=rsvp, role=role, participant_status=partstat)
def set_ical_dtend(self, dtend):
self.set_end(dtend)
@@ -388,7 +463,18 @@ class Event(object):
self.set_start(dtstart)
def set_ical_organizer(self, organizer):
- self.set_organizer(organizer)
+ cn = None
+ address = None
+ #print organizer
+ for param in organizer.split(':'):
+ if (len(param.split('=')) > 1):
+ exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
+ #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
+ if (len(organizer.split(':')) > 1):
+ address = organizer.split(':')[-1]
+ #print address
+
+ self.set_organizer(address, name=cn)
def set_ical_priority(self, priority):
self.set_priority(priority)
@@ -439,22 +525,33 @@ class Event(object):
year,
month,
day,
- hour,
- minute,
- second
) = (
_datetime.year,
_datetime.month,
_datetime.day,
- _datetime.hour,
- _datetime.minute,
- _datetime.second
)
+ if hasattr(_datetime, 'hour'):
+ (
+ hour,
+ minute,
+ second
+ ) = (
+ _datetime.hour,
+ _datetime.minute,
+ _datetime.second
+ )
+ else:
+ (hour, minute, second) = (0,0,0)
self.event.setStart(kolabformat.cDateTime(year, month, day, hour, minute, second))
def set_status(self, status):
- self.event.setStatus(status)
+ if status in self.status_map.keys():
+ self.event.setStatus(self.status_map[status])
+ elif status in self.status_map.values():
+ self.event.setStatus(status)
+ else:
+ raise InvalidEventStatusError, _("Invalid status set: %r") % (status)
def set_summary(self, summary):
self.event.setSummary(summary)
@@ -465,6 +562,142 @@ class Event(object):
def __str__(self):
return kolabformat.writeEvent(self.event)
+ def to_message(self):
+ from email.MIMEMultipart import MIMEMultipart
+ from email.MIMEBase import MIMEBase
+ from email.MIMEText import MIMEText
+ from email.Utils import COMMASPACE, formatdate
+ from email import Encoders
+
+ msg = MIMEMultipart()
+ organizer = self.get_organizer()
+ email = organizer.email()
+ name = organizer.name()
+
+ if not name:
+ msg['From'] = email
+ else:
+ msg['From'] = '"%s" <%s>' % (name, email)
+
+ msg['To'] = ', '.join([x.__str__() for x in self.get_attendees()])
+ msg['Date'] = formatdate(localtime=True)
+
+ msg.add_header('X-Kolab-Type', 'application/x-vnd.kolab.event')
+
+ text = utils.multiline_message("""
+ This is a Kolab Groupware object. To view this object you
+ will need an email client that understands the Kolab
+ Groupware format. For a list of such email clients please
+ visit http://www.kolab.org/
+ """)
+
+ msg.attach( MIMEText(text) )
+
+ part = MIMEBase('application', "calendar+xml")
+ part.set_charset('UTF-8')
+
+ msg["Subject"] = self.get_uid()
+
+ part.set_payload(str(self))
+
+ part.add_header('Content-Disposition', 'attachment; filename="kolab.xml"')
+ part.replace_header('Content-Transfer-Encoding', '8bit')
+
+ msg.attach(part)
+
+ return msg
+
+ def to_message_itip(self, from_address, method="REQUEST", participant_status="ACCEPTED"):
+ from email.MIMEMultipart import MIMEMultipart
+ from email.MIMEBase import MIMEBase
+ from email.MIMEText import MIMEText
+ from email.Utils import COMMASPACE, formatdate
+ from email import Encoders
+
+ msg = MIMEMultipart()
+
+ msg_from = None
+
+ log.debug(_("MESSAGE ITIP method %r") % (method), level=9)
+
+ if method == "REPLY":
+ msg['To'] = self.get_organizer().email()
+
+ log.debug(_("IN ITIP MESSAGE REPLY: %r") % (msg['To']), level=9)
+
+ attendees = self.get_attendees()
+
+ for attendee in attendees:
+ if attendee.get_email() == from_address:
+ # Only the attendee is supposed to be listed in a reply
+ attendee.set_participant_status(participant_status)
+
+ self._attendees = [attendee]
+ self.event.setAttendees(self._attendees)
+
+ name = attendee.get_name()
+ email = attendee.get_email()
+
+ if not name:
+ msg_from = email
+ else:
+ msg_from = '"%s" <%s>' % (name, email)
+
+ if msg_from == None:
+ organizer = self.get_organizer()
+ email = organizer.get_email()
+ name = organizer.get_name()
+ if email == from_address:
+ if not name:
+ msg_from = email
+ else:
+ msg_from = '"%s" <%s>' % (name, email)
+
+ elif method == "REQUEST":
+ organizer = self.get_organizer()
+ email = organizer.get_email()
+ name = organizer.get_name()
+ if not name:
+ msg_from = email
+ else:
+ msg_from = '"%s" <%s>' % (name, email)
+
+
+ log.debug(_("Message sender: %r") % (msg_from), level=9)
+
+ if msg_from == None:
+ log.error(_("No sender specified"))
+
+ msg['From'] = msg_from
+
+ msg['Date'] = formatdate(localtime=True)
+
+ text = utils.multiline_message("""
+ This is a response to one of your event requests.
+ """)
+
+ msg.attach( MIMEText(text) )
+
+ part = MIMEBase('text', "calendar")
+ part.set_charset('UTF-8')
+
+ msg["Subject"] = "Response to invitation"
+
+ part.set_payload(self.as_string_itip(method=method))
+
+ part.add_header('Content-Disposition', 'attachment; filename="event.ics"')
+ part.replace_header('Content-Transfer-Encoding', '8bit')
+
+ msg.attach(part)
+
+ print msg.as_string()
+
+ return msg
+
class EventIntegrityError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
+
+class InvalidEventStatusError(Exception):
+ def __init__(self, message):
+ Exception.__init__(self, message)
commit 17cab1bb4e5271ff7330c229f65161b4cca29b89
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue May 22 03:44:55 2012 +0100
Add additional helper functions to contact references
diff --git a/pykolab/xml/contact_reference.py b/pykolab/xml/contact_reference.py
index ff41480..e3fbcb2 100644
--- a/pykolab/xml/contact_reference.py
+++ b/pykolab/xml/contact_reference.py
@@ -17,8 +17,14 @@ class ContactReference(kolabformat.ContactReference):
else:
kolabformat.ContactReference.__init__(self, email)
+ def get_email(self):
+ return self.email()
+
+ def get_name(self):
+ return self.name()
+
def set_email(self, email):
- self.email = email
+ kolabformat.ContactReference.__init__(self, email)
def set_name(self, name):
self.setName(name)
commit 45310fe30d302d30359c119d2a783e3223d2fb54
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue May 22 03:44:25 2012 +0100
Update participant_status_map
Add additional helper functions
diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 692524d..7beb123 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -3,7 +3,7 @@ import kolabformat
from contact_reference import ContactReference
class Attendee(kolabformat.Attendee):
- partstat_map = {
+ participant_status_map = {
"NEEDS-ACTION": kolabformat.PartNeedsAction,
"ACCEPTED": kolabformat.PartAccepted,
"DECLINED": kolabformat.PartDeclined,
@@ -48,8 +48,18 @@ class Attendee(kolabformat.Attendee):
if not participant_status == None:
self.set_participant_status(participant_status)
+ def get_email(self):
+ return self.email
+
+ def get_name(self):
+ return self.contact().name()
+
+ def get_participant_status(self):
+ return self.partStat()
+
def set_participant_status(self, participant_status):
if self.participant_status_map.has_key(participant_status):
+ #print "Setting participant status for %r to %r" %(str(self), participant_status)
self.setPartStat(self.participant_status_map[participant_status])
def set_role(self, role):
commit 33e3eb39fee78bc917ade346530ab9859603ee85
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Mon May 21 14:46:49 2012 +0100
Be a little more robust about the shared folder strategy.
The kolabTargetFolder attribute is not yet being value-checked, and even
though the objectClass kolabSharedFolder is required, the value for the
attribute may or may not have been prefixed with 'shared/' or 'shared.'
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index f544beb..9735312 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -218,17 +218,34 @@ class IMAP(object):
"""
folder_name = "shared%s%s" % (self.imap.separator, folder_path)
- log.info(_("Creating new shared folder %s") %(folder_path))
+
+ # Correct folder_path being supplied with "shared/shared/" for example
+ if folder_name.startswith("shared%s" % (self.imap.separator) * 2):
+ folder_name = folder_name[7:]
+
+ log.info(_("Creating new shared folder %s") %(folder_name))
self.create_folder(folder_name, server)
def shared_folder_exists(self, folder_path):
"""
Check if a shared mailbox exists.
"""
- return self.has_folder('shared%s%s' % (self.imap.separator, folder_path))
+ folder_name = 'shared%s%s' % (self.imap.separator, folder_path)
+
+ # Correct folder_path being supplied with "shared/shared/" for example
+ if folder_name.startswith("shared%s" % (self.imap.separator) * 2):
+ folder_name = folder_name[7:]
+
+ return self.has_folder(folder_name)
def shared_folder_set_type(self, folder_path, folder_type):
- self.imap._setannotation('shared%s%s' % (self.imap.separator, folder_path), '/vendor/kolab/folder-type', folder_type)
+ folder_name = 'shared%s%s' % (self.imap.separator, folder_path)
+
+ # Correct folder_path being supplied with "shared/shared/" for example
+ if folder_name.startswith("shared%s" % (self.imap.separator) * 2):
+ folder_name = folder_name[7:]
+
+ self.imap._setannotation(folder_name, '/vendor/kolab/folder-type', folder_type)
def shared_mailbox_create(self, mailbox_base_name, server=None):
"""
@@ -236,6 +253,11 @@ class IMAP(object):
"""
folder_name = "shared%s%s" % (self.imap.separator, mailbox_base_name)
+
+ # Correct folder_path being supplied with "shared/shared/" for example
+ if folder_name.startswith("shared%s" % (self.imap.separator) * 2):
+ folder_name = folder_name[7:]
+
log.info(_("Creating new shared folder %s") %(mailbox_base_name))
self.create_folder(folder_name, server)
@@ -243,7 +265,13 @@ class IMAP(object):
"""
Check if a shared mailbox exists.
"""
- return self.has_folder('shared%s%s' %(self.imap.separator, mailbox_base_name))
+ folder_name = "shared%s%s" % (self.imap.separator, mailbox_base_name)
+
+ # Correct folder_path being supplied with "shared/shared/" for example
+ if folder_name.startswith("shared%s" % (self.imap.separator) * 2):
+ folder_name = folder_name[7:]
+
+ return self.has_folder(folder_name)
def user_mailbox_create(self, mailbox_base_name, server=None):
"""
commit 726fcdf9a002f706d048a8dd135be328d0849010
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Mon May 21 14:44:55 2012 +0100
Add the new settings to the default kolab.conf
diff --git a/conf/kolab.conf b/conf/kolab.conf
index 03bfd06..0e9d606 100644
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -77,6 +77,17 @@ group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls))
group_scope = sub
kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls))
+; Same again
+sharedfolder_base_dn = ou=Shared Folders,%(base_dn)s
+
+; Same again. Resources live in a different OU structure or;
+;
+; - They would appear in the address book(s) as distribution lists or individual contacts,
+; - Groups or individual users would appear to be Resources.
+;
+resource_base_dn = ou=Resources,%(base_dn)s
+resource_filter = (|%(group_filter)s(objectclass=kolabsharedfolder))
+
; The base DN, scope and filter to use when searching for additional domain
; name spaces in this environment.
domain_base_dn = cn=kolab,cn=config
More information about the commits
mailing list