#!/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)

