287 lines
10 KiB
Python
Executable File
287 lines
10 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: iso-8859-15 -*-
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
__doc__ = """Glasnost Appointments Server"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
import sys
|
|
import time
|
|
|
|
glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
|
|
sys.path.insert(0, glasnostPythonDir)
|
|
|
|
import glasnost
|
|
|
|
from glasnost.common.AppointmentsCommon import *
|
|
import glasnost.common.faults as faults
|
|
from glasnost.common.tools import sendMail
|
|
import glasnost.common.tools_new as commonTools
|
|
|
|
from glasnost.server.ObjectsServer import ObjectServerMixin, \
|
|
AdminServerMixin, ObjectsServer
|
|
from glasnost.server.things import register
|
|
from glasnost.server.tools import *
|
|
|
|
from glasnost.proxy.CacheProxy import invalidateValue
|
|
from glasnost.proxy.GroupsProxy import setContains, getSetContainedIds
|
|
|
|
try:
|
|
import vcalsax
|
|
except ImportError:
|
|
pass
|
|
|
|
applicationName = 'AppointmentsServer'
|
|
applicationRole = 'appointments'
|
|
dispatcher = None
|
|
|
|
|
|
class AdminAppointments(AdminServerMixin, AdminAppointmentsCommon):
|
|
pass
|
|
register(AdminAppointments)
|
|
|
|
|
|
class Appointment(ObjectServerMixin, AppointmentCommon):
|
|
pass
|
|
register(Appointment)
|
|
|
|
|
|
class AppointmentsServer(AppointmentsCommonMixin, ObjectsServer):
|
|
useAdminWritersSet = 1
|
|
|
|
def isAskedParticipant(self, appointment, resourceIds):
|
|
people = getSetContainedIds(participantIds)
|
|
for personId in people:
|
|
if setContains(appointment.participantsSet, personId):
|
|
return 1
|
|
return 0
|
|
|
|
def getAppointmentIds(self, start = None, end = None,
|
|
participantIds = None):
|
|
isAdmin = self.isAdmin()
|
|
## if not isAdmin:
|
|
## raise faults.UserAccessDenied()
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
if not participantIds or participantIds == ['']:
|
|
participantIds = []
|
|
|
|
identitiesProxy = getProxyForServerRole('identities')
|
|
result = []
|
|
for objectId, object in virtualServer.objects.items():
|
|
|
|
if not (isAdmin
|
|
or identitiesProxy.setContainsUser(object.readersSet)
|
|
or (object.participantsSet
|
|
and identitiesProxy.setContainsUser(
|
|
object.participantsSet))):
|
|
continue
|
|
|
|
if participantIds and not self.isAskedParticipant(
|
|
object, participantIds):
|
|
continue
|
|
|
|
if (object.end or object.start) < start or object.start > end:
|
|
continue
|
|
|
|
result.append(object.id)
|
|
|
|
return result
|
|
|
|
def addObjectXmlRpc(self, objectImport):
|
|
objectId = ObjectsServer.addObjectXmlRpc(self, objectImport)
|
|
# TODO: send notification mail
|
|
return objectId
|
|
|
|
def modifyObjectXmlRpc(self, objectImport):
|
|
version = ObjectsServer.modifyObjectXmlRpc(self, objectImport)
|
|
# TODO: send notification mail
|
|
return version
|
|
|
|
def updateFromVCalendar(self, vCalendar):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
vCals = ['BEGIN:VCALENDAR' +x
|
|
for x in vCalendar.split('BEGIN:VCALENDAR')[1:]]
|
|
apps = []
|
|
for v in vCals:
|
|
parser = vcalsax.VcfParser()
|
|
reader = vcalsax.Sax2.Reader(0, 0, None,
|
|
vcalsax.Sax2.XmlDomGenerator,
|
|
parser)
|
|
doc = reader.fromString(v)
|
|
apps.append( domToObject(doc) )
|
|
for a in apps:
|
|
invalidate = 0
|
|
object = virtualServer.loadObjectCore(a.id)
|
|
if a.version and object.version != a.version:
|
|
continue
|
|
if object.title != a.title:
|
|
object.title = a.title
|
|
invalidate = 1
|
|
if object.start != a.start and a.start:
|
|
object.start = a.start
|
|
invalidate = 1
|
|
if object.end != a.end and a.end:
|
|
object.end = a.end
|
|
invalidate = 1
|
|
if object.body != a.body:
|
|
object.body = a.body
|
|
invalidate = 1
|
|
|
|
if invalidate:
|
|
object.version += 1
|
|
invalidateValue(object.id)
|
|
|
|
def sendNotification(self, virtualServerId, appointment, subject, body):
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
mailFrom = virtualServer.adminEmailAddress
|
|
personIds = getSetContainedIds(appointment.participantsSet)
|
|
for personId in personIds:
|
|
person = getProxyForServerRole('people').getObject(personId)
|
|
if person.email:
|
|
mailTo = person.email
|
|
try:
|
|
sendMail(mailFrom, mailTo, mailSubject = subject,
|
|
mailMessage = body, mailPerson = person)
|
|
except faults.SmtpError:
|
|
pass
|
|
|
|
def registerPublicMethods(self):
|
|
ObjectsServer.registerPublicMethods(self)
|
|
self.registerPublicMethod('getAppointmentIds')
|
|
self.registerPublicMethod('updateFromVCalendar')
|
|
|
|
def repairVirtualServer(self, virtualServer, version):
|
|
changed = 0
|
|
if version <= 1029000:
|
|
for object in virtualServer.objects.values():
|
|
if not hasattr(object, 'rememberTime'):
|
|
continue
|
|
object.reminderTime = object.rememberTime
|
|
del(object.rememberTime)
|
|
changed = 1
|
|
if version <= 1032000:
|
|
for object in virtualServer.objects.values():
|
|
if not hasattr(object, 'resourcesSet'):
|
|
continue
|
|
object.participantsSet = object.resourcesSet
|
|
del(object.resourcesSet)
|
|
changed = 1
|
|
# several fields where also removed in this version:
|
|
# authorsSet, category, endingDate, lastEditorId,
|
|
# modificationTime, reminderTime, repetition and
|
|
# repetitionDay
|
|
# they are not removed from objects because they might
|
|
# be replaced by alternatives later and it would be bad
|
|
# to lose data.
|
|
if version <= 1039000:
|
|
# beginningDate renamed to start, duration removed,
|
|
# endingDate is back but renamed to end.
|
|
for object in virtualServer.objects.values():
|
|
if hasattr(object, 'beginningDate'):
|
|
object.start = object.beginningDate
|
|
changed = 1
|
|
if hasattr(object, 'endingDate'):
|
|
object.end = object.endingDate
|
|
changed = 1
|
|
if hasattr(object, 'duration') and not object.end:
|
|
object.end = object.start + object.duration
|
|
changed = 1
|
|
if changed:
|
|
virtualServer.markAllAsDirtyFIXME()
|
|
|
|
|
|
appointmentsServer = AppointmentsServer()
|
|
|
|
|
|
def domToObject(domDocument):
|
|
# FIXME: the document could have several VEVENT
|
|
nodes = domDocument._get_childNodes()[1]._get_childNodes()[-1]._get_childNodes()
|
|
attrs = [(x._get_nodeName(),
|
|
x._get_childNodes()[0]._get_nodeValue()) for x in nodes]
|
|
# yep, I hope it will work. Incredible.
|
|
# TODO: something smarter
|
|
# attrs now is a list of pairs (key, value)
|
|
# ex: [('UID', 'glasnost://projects.entrouvert.be.lan/appointments/1'),
|
|
# ('SUMMARY', 'rdv avec Olivier Lattignies et Etienne Saliez'),
|
|
# ('URL', 'http://projects.entrouvert.be.lan/appointments/1'),
|
|
# ('REVISION', '0'),
|
|
# ('DTSTART', '20030210'),
|
|
# ('DTEND', '20030210T235900')]
|
|
d = {}
|
|
for k,v in attrs:
|
|
d[k] = unicode(v.encode('latin-1'), 'utf-8').encode('latin-1')
|
|
appointment = Appointment()
|
|
if d['UID'].startswith('glasnost://'):
|
|
appointment.id = d['UID']
|
|
appointment.title = d['SUMMARY']
|
|
if d.has_key('SEQUENCE'):
|
|
appointment.version = d['SEQUENCE']
|
|
if d.has_key('DTSTART'):
|
|
t = vCalDateToTime(d['DTSTART'])
|
|
if t:
|
|
appointment.start = t
|
|
if d.has_key('DTEND'):
|
|
t = vCalDateToTime(d['DTEND'])
|
|
if t:
|
|
appointment.end = t
|
|
if d.has_key('DESCRIPTION'):
|
|
s = d['DESCRIPTION']
|
|
s = s.replace('\\n', '\n')
|
|
appointment.body = s
|
|
return appointment
|
|
|
|
|
|
def vCalDateToTime(s):
|
|
if len(s) == 8:
|
|
return time.mktime(time.strptime(s, '%Y%m%d'))
|
|
if len(s) == 15:
|
|
return time.mktime(time.strptime(s, '%Y%m%dT%H%M%S'))
|
|
return None
|
|
|
|
|
|
if __name__ == "__main__":
|
|
appointmentsServer.launch(applicationName, applicationRole)
|
|
|