[svn-commits] twilson: trunk r197738 - in /trunk: ./ build_tools/ configs/ include/asterisk...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Thu May 28 14:57:28 CDT 2009


Author: twilson
Date: Thu May 28 14:57:18 2009
New Revision: 197738

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=197738
Log:
Add Calendaring support for Asterisk

This commit add Calendaring support to Asterisk for iCalendar, CalDAV, and MS
Exchange calendars. Exchange support has only been tested on Exchange Server 2k3
and does not support forms-based authentication at this time (patches *very*
welcome). Exchange support is also currently missing the ability to return a
list of a meting's attendees (again, patches are very, very welcome).

Features include:
  Querying a calendar for events over a specific time range
  Checking a calendar's busy status via the dialplan
  Writing calendar events via the dialplan (CalDAV and Exchange only)
  Handling calendar event notifications through the dialplan

(closes issue #14771)
Tested by: lmadsen, twilson, Shivaprakash

Review: https://reviewboard.asterisk.org/r/58

Added:
    trunk/configs/calendar.conf.sample   (with props)
    trunk/include/asterisk/calendar.h   (with props)
    trunk/res/res_calendar.c   (with props)
    trunk/res/res_calendar.exports   (with props)
    trunk/res/res_calendar_caldav.c   (with props)
    trunk/res/res_calendar_exchange.c   (with props)
    trunk/res/res_calendar_icalendar.c   (with props)
Modified:
    trunk/build_tools/menuselect-deps.in
    trunk/configure
    trunk/configure.ac
    trunk/include/asterisk/autoconfig.h.in
    trunk/makeopts.in

Modified: trunk/build_tools/menuselect-deps.in
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/build_tools/menuselect-deps.in?view=diff&rev=197738&r1=197737&r2=197738
==============================================================================
--- trunk/build_tools/menuselect-deps.in (original)
+++ trunk/build_tools/menuselect-deps.in Thu May 28 14:57:18 2009
@@ -11,6 +11,7 @@
 GTK=@PBX_GTK@
 H323=@PBX_H323@
 HOARD=@PBX_HOARD@
+ICAL=@PBX_ICAL@
 ICONV=@PBX_ICONV@
 IKSEMEL=@PBX_IKSEMEL@
 IMAP_TK=@PBX_IMAP_TK@
@@ -19,12 +20,14 @@
 IXJUSER=@PBX_IXJUSER@
 JACK=@PBX_JACK@
 LDAP=@PBX_LDAP@
+LIBXML2=@PBX_LIBXML2@
 LTDL=@PBX_LTDL@
 LUA=@PBX_LUA@
 MISDN=@PBX_MISDN@
 NBS=@PBX_NBS@
 NETSNMP=@PBX_NETSNMP@
 NEWT=@PBX_NEWT@
+NEON=@PBX_NEON@
 OGG=@PBX_OGG@
 OPENH323=@PBX_OPENH323@
 OSPTK=@PBX_OSPTK@

Added: trunk/configs/calendar.conf.sample
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/configs/calendar.conf.sample?view=auto&rev=197738
==============================================================================
--- trunk/configs/calendar.conf.sample (added)
+++ trunk/configs/calendar.conf.sample Thu May 28 14:57:18 2009
@@ -1,0 +1,80 @@
+;[calendar1]
+;type = ical              ;  type of calendar--currently supported: ical, caldav, or exchange
+;url = https://example.com/home/jdoe/Calendar/   ; URL to shared calendar (Zimbra example)
+;user = jdoe              ; web username
+;secret = supersecret     ; web password
+;refresh = 15             ; refresh calendar every n minutes
+;timeframe = 60           ; number of minutes of calendar data to pull for each refresh period
+;                         ; should always be >= refresh
+;
+; You can set up res_icalendar to execute a call upon an upcoming busy status
+; The following fields are available from the ${CALENDAR_EVENT(<field>)} dialplan function:
+;
+; summary     : The VEVENT Summary property or Exchange subject
+; description : The text description of the vent
+; organizer   : The organizer of the event
+; location    : The location field of the event
+; calendar    : The name of the calendar tied to the event
+; uid         : The unique ID for this event
+; start       : Start time of the event
+; end         : The end time of the event
+; busystate   : 0=FREE, 1=TENTATIVE, 2=BUSY
+;
+;autoreminder = 10        ; Override event-defined reminder before each busy status (in mins)
+;
+;channel = SIP/60001      ; Channel to dial
+;context = default        ; Context to connect to on answer
+;extension = 123          ; Extension to connect to on answer
+;
+; or
+;
+;app = Playback          ; Application to execute on answer (instead of context/extension)
+;appdata = tt-weasels    ; Data part of application to execute on answer
+;
+;waittime = 30            ; How long to wait for an answer
+
+;[calendar2]
+;type = exchange          ;  type of calendar--currently supported: ical, caldav, or exchange
+;url = https://example.com/exchange/jdoe   ; URL to MS Exchange OWA for user (usually includes exchange/user)
+;user = jdoe              ; Exchange username
+;secret = mysecret        ; Exchange password
+;refresh = 15             ; refresh calendar every n minutes
+;timeframe = 60           ; number of minutes of calendar data to pull for each refresh period
+;                         ; should always be >= refresh
+;
+; You can set up res_icalendar to execute a call upon an upcoming busy status
+;autoreminder = 10        ; Override event-defined reminder before each busy status (in mins)
+;
+;channel = SIP/1234       ; Channel to dial
+;context = default        ; Context to connect to on answer
+;extension = 1234         ; Extension to connect to on answer
+;
+; or
+;
+;app = Playback          ; Application to execute on answer (instead of context/extension)
+;appdata = tt-weasels    ; Data part of application to execute on answer
+;
+;waittime = 30            ; How long to wait for an answer
+
+;[calendar3]
+;type = caldav            ;  type of calendar--currently supported: ical, caldav, or exchange
+;url = https://www.google.com/calendar/dav/username@gmail.com/events/  ; Main GMail calendar (the trailing slash is significant!)
+;user = jdoe at gmail.com    ; username
+;secret = mysecret        ; password
+;refresh = 15             ; refresh calendar every n minutes
+;timeframe = 60           ; number of minutes of calendar data to pull for each refresh period
+;                         ; should always be >= refresh
+;
+; You can set up res_icalendar to execute a call upon an upcoming busy status
+;autoreminder = 10        ; Override event-defined reminder before each busy status (in mins)
+;
+;channel = SIP/1234       ; Channel to dial
+;context = default        ; Context to connect to on answer
+;extension = 1234         ; Extension to connect to on answer
+;
+; or
+;
+;app = Playback          ; Application to execute on answer (instead of context/extension)
+;appdata = tt-weasels    ; Data part of application to execute on answer
+;
+;waittime = 30            ; How long to wait for an answer

Propchange: trunk/configs/calendar.conf.sample
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: trunk/configs/calendar.conf.sample
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: trunk/configs/calendar.conf.sample
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: trunk/configure.ac
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/configure.ac?view=diff&rev=197738&r1=197737&r2=197738
==============================================================================
--- trunk/configure.ac (original)
+++ trunk/configure.ac Thu May 28 14:57:18 2009
@@ -244,6 +244,7 @@
 AST_EXT_LIB_SETUP([GTK2], [gtk2 libraries], [gtk2])
 AST_EXT_LIB_SETUP([GMIME], [GMime library], [gmime])
 AST_EXT_LIB_SETUP([HOARD], [Hoard Memory Allocator], [hoard])
+AST_EXT_LIB_SETUP([ICAL], [ical libraries], [ical])
 AST_EXT_LIB_SETUP([ICONV], [Iconv Library], [iconv])
 AST_EXT_LIB_SETUP([IKSEMEL], [Iksemel Jabber Library], [iksemel])
 AST_EXT_LIB_SETUP([IMAP_TK], [UW IMAP Toolkit], [imap])
@@ -258,6 +259,7 @@
 AST_EXT_LIB_SETUP([MISDN], [mISDN User Library], [misdn])
 AST_EXT_LIB_SETUP([NBS], [Network Broadcast Sound], [nbs])
 AST_EXT_LIB_SETUP([NCURSES], [ncurses], [ncurses])
+AST_EXT_LIB_SETUP([NEON], [neon], [neon])
 AST_EXT_LIB_SETUP([NETSNMP], [Net-SNMP], [netsnmp])
 AST_EXT_LIB_SETUP([NEWT], [newt], [newt])
 AST_EXT_LIB_SETUP([OGG], [OGG], [ogg])
@@ -778,6 +780,8 @@
 else
   PBX_ICONV=1
 fi
+
+AST_EXT_LIB_CHECK([ICAL], [ical], [icaltimezone_new], [libical/ical.h], [-lpthread])
 
 AST_EXT_LIB_CHECK([IKSEMEL], [iksemel], [iks_start_sasl], [iksemel.h])
 
@@ -1350,6 +1354,8 @@
 
 AST_EXT_LIB_CHECK([NCURSES], [ncurses], [initscr], [curses.h])
 
+AST_EXT_TOOL_CHECK([NEON], [neon])
+
 AST_EXT_TOOL_CHECK([NETSNMP], [net-snmp], , [--agent-libs],
 [#include <net-snmp/net-snmp-config.h>
 #include <net-snmp/net-snmp-includes.h>

Modified: trunk/include/asterisk/autoconfig.h.in
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/include/asterisk/autoconfig.h.in?view=diff&rev=197738&r1=197737&r2=197738
==============================================================================
--- trunk/include/asterisk/autoconfig.h.in (original)
+++ trunk/include/asterisk/autoconfig.h.in Thu May 28 14:57:18 2009
@@ -400,6 +400,12 @@
 /* Define to indicate the ${HOARD_DESCRIP} library version */
 #undef HAVE_HOARD_VERSION
 
+/* Define this to indicate the ${ICAL_DESCRIP} library */
+#undef HAVE_ICAL
+
+/* Define to indicate the ${ICAL_DESCRIP} library version */
+#undef HAVE_ICAL_VERSION
+
 /* Define this to indicate the ${ICONV_DESCRIP} library */
 #undef HAVE_ICONV
 
@@ -592,6 +598,9 @@
 
 /* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
 #undef HAVE_NDIR_H
+
+/* Define if your system has the NEON libraries. */
+#undef HAVE_NEON
 
 /* Define to 1 if you have the <netdb.h> header file. */
 #undef HAVE_NETDB_H

Added: trunk/include/asterisk/calendar.h
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/include/asterisk/calendar.h?view=auto&rev=197738
==============================================================================
--- trunk/include/asterisk/calendar.h (added)
+++ trunk/include/asterisk/calendar.h Thu May 28 14:57:18 2009
@@ -1,0 +1,187 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008 - 2009, Digium, Inc.
+ *
+ * Terry Wilson <twilson at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _ASTERISK_CALENDAR_H
+#define _ASTERISK_CALENDAR_H
+
+#include "asterisk.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/config.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/lock.h"
+
+/*! \file calendar.h
+ * \brief A general API for managing calendar events with Asterisk
+ *
+ * \author Terry Wilson <twilson at digium.com>
+ *
+ * \note This API implements an abstraction for handling different calendaring
+ * technologies in Asterisk. The services provided by the API are a dialplan
+ * function to query whether or not a calendar is busy at the present time, a
+ * adialplan function to query specific information about events in a time range,
+ * a devicestate provider, and notification of calendar events through execution
+ * of dialplan apps or dialplan logic at a specific context and extension.  The
+ * information available through the CALENDAR_EVENT() dialplan function are:
+ *
+ *   SUMMARY, DESCRIPTION, ORGANIZER, LOCATION
+ *   CALENDAR, UID, START, END, and BUSYSTATE
+ *
+ * BUSYSTATE can have the values 0 (free), 1 (tentatively busy), or 2 (busy)
+ *
+ * Usage
+ * All calendaring configuration data is located in calendar.conf and is only read
+ * directly by the Calendaring API. Each calendar technology resource must register
+ * a load_calendar callback which will be passed an ast_calendar_load_data structure.
+ * The load_calendar callback function should then set the values it needs from this
+ * cfg, load the calendar data, and then loop updating the calendar data and events
+ * baesd on the refresh interval in the ast_calendar object.  Each call to
+ * the load_calendar callback will be will run in its own thread.
+ *
+ * Updating events involves creating an astobj2 container of new events and passing
+ * it to the API through ast_calendar_merge_events.
+ *
+ * Calendar technology resource modules must also register an unref_calendar callback
+ * which will only be called when the resource module calls ast_calendar_unregister()
+ * to unregister that module's calendar type (usually done in module_unload())
+ */
+
+extern struct ast_config *calendar_config;
+
+struct ast_calendar;
+struct ast_calendar_event;
+
+/*! \brief Individual calendaring technology data */
+struct ast_calendar_tech {
+	const char *type;
+	const char *description;
+	const char *module;
+	int (* is_busy)(struct ast_calendar *calendar); /*!< Override default busy determination */
+	void *(* load_calendar)(void *data);   /*!< Create private structure, add calendar events, etc. */
+	void *(* unref_calendar)(void *obj);   /*!< Function to be called to free the private structure */
+	int (* write_event)(struct ast_calendar_event *event);  /*!< Function for writing an event to the calendar */
+	AST_LIST_ENTRY(ast_calendar_tech) list;
+};
+
+enum ast_calendar_busy_state {
+	AST_CALENDAR_BS_FREE = 0,
+	AST_CALENDAR_BS_BUSY_TENTATIVE,
+	AST_CALENDAR_BS_BUSY,
+};
+
+struct ast_calendar_attendee {
+	char *data;
+	AST_LIST_ENTRY(ast_calendar_attendee) next;
+};
+
+/* \brief Calendar events */
+struct ast_calendar_event {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(summary);
+		AST_STRING_FIELD(description);
+		AST_STRING_FIELD(organizer);
+		AST_STRING_FIELD(location);
+		AST_STRING_FIELD(uid);
+	);
+	struct ast_calendar *owner;   /*!< The calendar that owns this event */
+	time_t start;        /*!< Start of event (UTC) */
+	time_t end;          /*!< End of event (UTC) */
+	time_t alarm;        /*!< Time for event notification */
+	enum ast_calendar_busy_state busy_state;  /*!< The busy status of the event */
+	int notify_sched;    /*!< The sched for event notification */
+	int bs_start_sched;  /*!< The sched for changing the device state at the start of an event */
+	int bs_end_sched;    /*!< The sched for changing the device state at the end of an event */
+	AST_LIST_HEAD_NOLOCK(attendees, ast_calendar_attendee) attendees;
+};
+
+/*! \brief Asterisk calendar structure */
+struct ast_calendar {
+	const struct ast_calendar_tech *tech;
+	void *tech_pvt;
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);				/*!< Name from config file [name] */
+		AST_STRING_FIELD(notify_channel);	/*!< Channel to use for notification */
+		AST_STRING_FIELD(notify_context);	/*!< Optional context to execute from for notification */
+		AST_STRING_FIELD(notify_extension);	/*!< Optional extension to execute from for notification */
+		AST_STRING_FIELD(notify_app);		/*!< Optional dialplan app to execute for notification */
+		AST_STRING_FIELD(notify_appdata);	/*!< Optional arguments for dialplan app */
+	);
+	int autoreminder;    /*!< If set, override any calendar_tech specific notification times and use this time (in mins) */
+	int notify_waittime; /*!< Maxiumum time to allow for a notification attempt */
+	int refresh;         /*!< When to refresh the calendar events */
+	int timeframe;       /*!< Span (in mins) of calendar data to pull with each request */
+	pthread_t thread;    /*!< The thread that the calendar is loaded/updated in */
+	ast_cond_t unload;
+	int unloading:1;
+	int pending_deletion:1;
+	struct ao2_container *events;  /*!< The events that are known at this time */
+};
+
+/*! \brief Register a new calendar technology
+ *
+ * \param tech calendar technology to register
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_calendar_register(struct ast_calendar_tech *tech);
+
+/*! \brief Unregister a new calendar technology
+ *
+ * \param tech calendar technology to unregister
+ *
+ * \retval 0 success
+ * \retva -1 failure
+ */
+void ast_calendar_unregister(struct ast_calendar_tech *tech);
+
+/*! \brief Allocate an astobj2 ast_calendar_event object
+ *
+ * \param cal calendar to allocate an event for
+ *
+ * \return a new, initialized calendar event
+ */
+struct ast_calendar_event *ast_calendar_event_alloc(struct ast_calendar *cal);
+
+/*! \brief Allocate an astobj2 container for ast_calendar_event objects
+ *
+ * \return a new event container
+ */
+struct ao2_container *ast_calendar_event_container_alloc(void);
+
+/*! \brief Add an event to the list of events for a calendar
+ *
+ * \param cal calendar containing the events to be merged
+ * \param new_events an oa2 container of events to be merged into cal->events
+ */
+void ast_calendar_merge_events(struct ast_calendar *cal, struct ao2_container *new_events);
+
+/*! \brief Unreference an ast_calendar_event 
+ *
+ * \param event event to unref
+ *
+ * \return NULL
+ */
+struct ast_calendar_event *ast_calendar_unref_event(struct ast_calendar_event *event);
+
+/*! \brief Remove all events from calendar 
+ *
+ * \param cal calendar whose events need to be cleared
+ */
+void ast_calendar_clear_events(struct ast_calendar *cal);
+
+#endif /* _ASTERISK_CALENDAR_H */

Propchange: trunk/include/asterisk/calendar.h
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: trunk/include/asterisk/calendar.h
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: trunk/include/asterisk/calendar.h
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: trunk/makeopts.in
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/makeopts.in?view=diff&rev=197738&r1=197737&r2=197738
==============================================================================
--- trunk/makeopts.in (original)
+++ trunk/makeopts.in Thu May 28 14:57:18 2009
@@ -105,6 +105,9 @@
 GTK2_INCLUDE=@GTK2_INCLUDE@
 GTK2_LIB=@GTK2_LIB@
 
+ICAL_INCLUDE=@ICAL_INCLUDE@
+ICAL_LIB=@ICAL_LIB@
+
 ICONV_INCLUDE=@ICONV_INCLUDE@
 ICONV_LIB=@ICONV_LIB@
 
@@ -132,6 +135,9 @@
 NCURSES_INCLUDE=@NCURSES_INCLUDE@
 NCURSES_LIB=@NCURSES_LIB@
 NCURSES_DIR=@NCURSES_DIR@
+
+NEON_INCLUDE=@NEON_INCLUDE@
+NEON_LIB=@NEON_LIB@
 
 NETSNMP_INCLUDE=@NETSNMP_INCLUDE@
 NETSNMP_LIB=@NETSNMP_LIB@

Added: trunk/res/res_calendar.c
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/res/res_calendar.c?view=auto&rev=197738
==============================================================================
--- trunk/res/res_calendar.c (added)
+++ trunk/res/res_calendar.c Thu May 28 14:57:18 2009
@@ -1,0 +1,1604 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008 - 2009, Digium, Inc.
+ *
+ * Terry Wilson <twilson at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Calendaring API
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/calendar.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/sched.h"
+#include "asterisk/dial.h"
+#include "asterisk/cli.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+	<function name="CALENDAR_BUSY" language="en_US">
+		<synopsis>
+			Determine if the calendar is marked busy at this time.
+		</synopsis>
+		<syntax>
+			<parameter name="calendar" required="true" />
+		</syntax>
+    	<description>
+			<para>Check the specified calendar's current busy status.</para>
+		</description>
+	</function>
+	<function name="CALENDAR_EVENT" language="en_US">
+		<synopsis>
+			Get calendar event notification data from a notification call.
+		</synopsis>
+		<syntax>
+			<parameter name="field" required="true">
+				<enumlist>
+					<enum name="summary"><para>The VEVENT SUMMARY property or Exchange event 'subject'</para></enum>
+					<enum name="description"><para>The text description of the event</para></enum>
+					<enum name="organizer"><para>The organizer of the event</para></enum>
+					<enum name="location"><para>The location of the eventt</para></enum>
+					<enum name="calendar"><para>The name of the calendar associated with the event</para></enum>
+					<enum name="uid"><para>The unique identifier for this event</para></enum>
+					<enum name="start"><para>The start time of the event</para></enum>
+					<enum name="end"><para>The end time of the event</para></enum>
+					<enum name="busystate"><para>The busy state of the event 0=FREE, 1=TENTATIVE, 2=BUSY</para></enum>
+				</enumlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Whenever a calendar event notification call is made, the event data
+			may be accessed with this function.</para>
+		</description>
+	</function>
+	<function name="CALENDAR_QUERY" language="en_US">
+		<synopsis>Query a calendar server and store the data on a channel
+		</synopsis>
+		<syntax>
+			<parameter name="calendar" required="true">
+				<para>The calendar that should be queried</para>
+			</parameter>
+			<parameter name="start" required="false">
+				<para>The start time of the query (in seconds since epoch)</para>
+			</parameter>
+			<parameter name="end" required="false">
+				<para>The end time of the query (in seconds since epoch)</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Get a list of events in the currently accessible timeframe of the <replaceable>calendar</replaceable>
+			The function returns the id for accessing the result with CALENDAR_QUERY_RESULT()</para>
+		</description>
+	</function>
+	<function name="CALENDAR_QUERY_RESULT" language="en_US">
+		<synopsis>
+			Retrieve data from a previously run CALENDAR_QUERY() call
+		</synopsis>
+		<syntax>
+			<parameter name="id" required="true">
+				<para>The query ID returned by <literal>CALENDAR_QUERY</literal></para>
+			</parameter>
+			<parameter name="field" required="true">
+				<enumlist>
+					<enum name="getnum"><para>number of events occurring during time range</para></enum>
+					<enum name="summary"><para>A summary of the event</para></enum>
+					<enum name="description"><para>The full event description</para></enum>
+					<enum name="organizer"><para>The event organizer</para></enum>
+					<enum name="location"><para>The event location</para></enum>
+					<enum name="calendar"><para>The name of the calendar associted with the event</para></enum>
+					<enum name="uid"><para>The unique identifier for the event</para></enum>
+					<enum name="start"><para>The start time of the event (in seconds since epoch)</para></enum>
+					<enum name="end"><para>The end time of the event (in seconds since epoch)</para></enum>
+					<enum name="busystate"><para>The busy status of the event 0=FREE, 1=TENTATIVE, 2=BUSY</para></enum>
+				</enumlist>
+			</parameter>
+			<parameter name="entry" required="false" default="1">
+				<para>Return data from a specific event returned by the query</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>After running CALENDAR_QUERY and getting a result <replaceable>id</replaceable>, calling
+			<literal>CALENDAR_QUERY</literal> with that <replaceable>id</replaceable> and a <replaceable>field</replaceable>
+			will return the data for that field. If multiple events matched the query, and <replaceable>entry</replaceable>
+			is provided, information from that event will be returned.</para>
+		</description>
+	</function>
+	<function name="CALENDAR_WRITE" language="en_US">
+		<synopsis>Write an event to a calendar</synopsis>
+		<syntax>
+			<parameter name="calendar" required="true">
+				<para>The calendar to write to</para>
+			</parameter>
+			<parameter name="field" multiple="true" required="true">
+				<enumlist>
+					<enum name="summary"><para>A summary of the event</para></enum>
+					<enum name="description"><para>The full event description</para></enum>
+					<enum name="organizer"><para>The event organizer</para></enum>
+					<enum name="location"><para>The event location</para></enum>
+					<enum name="uid"><para>The unique identifier for the event</para></enum>
+					<enum name="start"><para>The start time of the event (in seconds since epoch)</para></enum>
+					<enum name="end"><para>The end time of the event (in seconds since epoch)</para></enum>
+					<enum name="busystate"><para>The busy status of the event 0=FREE, 1=TENTATIVE, 2=BUSY</para></enum>
+				</enumlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Example: CALENDAR_WRITE(calendar,field1,field2,field3)=val1,val2,val3</para>
+			<para>The field and value arguments can easily be set/passed using the HASHKEYS() and HASH() functions</para>
+		</description>
+	</function>
+
+***/
+#define CALENDAR_BUCKETS 19
+
+static struct ao2_container *calendars;
+static struct sched_context *sched;
+static pthread_t refresh_thread = AST_PTHREADT_NULL;
+static ast_mutex_t refreshlock;
+static ast_cond_t refresh_condition;
+static ast_mutex_t reloadlock;
+
+static void event_notification_destroy(void *data);
+static void *event_notification_duplicate(void *data);
+static void eventlist_destroy(void *data);
+static void *eventlist_duplicate(void *data);
+
+static const struct ast_datastore_info event_notification_datastore = {
+	.type = "EventNotification",
+	.destroy = event_notification_destroy,
+	.duplicate = event_notification_duplicate,
+};
+
+static const struct ast_datastore_info eventlist_datastore_info = {
+	.type = "CalendarEventList",
+	.destroy = eventlist_destroy,
+	.duplicate = eventlist_duplicate,
+};
+
+struct evententry {
+	struct ast_calendar_event *event;
+	AST_LIST_ENTRY(evententry) list;
+};
+
+AST_LIST_HEAD_STATIC(techs, ast_calendar_tech);
+AST_LIST_HEAD_NOLOCK(eventlist, evententry); /* define the type */
+
+struct ast_config *calendar_config = NULL;
+
+static struct ast_calendar *unref_calendar(struct ast_calendar *cal)
+{
+	ao2_ref(cal, -1);
+	return NULL;
+}
+
+static int calendar_hash_fn(const void *obj, const int flags)
+{
+	const struct ast_calendar *cal = obj;
+	return ast_str_case_hash(cal->name);
+}
+
+static int calendar_cmp_fn(void *obj, void *arg, int flags)
+{
+	const struct ast_calendar *one = obj, *two = arg;
+	return !strcasecmp(one->name, two->name) ? CMP_MATCH | CMP_STOP: 0;
+}
+
+static struct ast_calendar *find_calendar(const char *name)
+{
+	struct ast_calendar tmp = {
+		.name = name,
+	};
+	return ao2_find(calendars, &tmp, OBJ_POINTER);
+}
+
+static int event_hash_fn(const void *obj, const int flags)
+{
+	const struct ast_calendar_event *event = obj;
+	return ast_str_hash(event->uid);
+}
+
+static int event_cmp_fn(void *obj, void *arg, int flags)
+{
+	const struct ast_calendar_event *one = obj, *two = arg;
+	return !strcmp(one->uid, two->uid) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct ast_calendar_event *find_event(struct ao2_container *events, const char *uid)
+{
+	struct ast_calendar_event tmp = {
+		.uid = uid,
+	};
+	return ao2_find(events, &tmp, OBJ_POINTER);
+}
+
+struct ast_calendar_event *ast_calendar_unref_event(struct ast_calendar_event *event)
+{
+	ao2_ref(event, -1);
+	return NULL;
+}
+
+static void calendar_destructor(void *obj)
+{
+	struct ast_calendar *cal = obj;
+
+	ast_debug(3, "Destroying calendar %s\n", cal->name);
+
+	ao2_lock(cal);
+	cal->unloading = 1;
+	ast_cond_signal(&cal->unload);
+	pthread_join(cal->thread, NULL);
+	if (cal->tech_pvt) {
+		cal->tech_pvt = cal->tech->unref_calendar(cal->tech_pvt);
+	}
+	ast_calendar_clear_events(cal);
+	ast_string_field_free_memory(cal);
+	ao2_ref(cal->events, -1);
+	ao2_unlock(cal);
+}
+
+static void eventlist_destructor(void *obj)
+{
+	struct eventlist *events = obj;
+	struct evententry *entry;
+
+	while ((entry = AST_LIST_REMOVE_HEAD(events, list))) {
+		ao2_ref(entry->event, -1);
+		ast_free(entry);
+	}
+}
+
+static int calendar_busy_callback(void *obj, void *arg, int flags)
+{
+	struct ast_calendar_event *event = obj;
+	int *is_busy = arg;
+	struct timeval tv = ast_tvnow();
+
+	if (tv.tv_sec >= event->start && tv.tv_sec <= event->end && event->busy_state > AST_CALENDAR_BS_FREE) {
+		*is_busy = 1;
+		return CMP_STOP;
+	}
+
+	return 0;
+}
+
+static int calendar_is_busy(struct ast_calendar *cal)
+{
+	int is_busy = 0;
+
+	ao2_callback(cal->events, OBJ_NODATA, calendar_busy_callback, &is_busy);
+
+	return is_busy;
+}
+
+static enum ast_device_state calendarstate(const char *data)
+{
+	struct ast_calendar *cal;
+
+	if (ast_strlen_zero(data) || (!(cal = find_calendar(data)))) {
+		return AST_DEVICE_INVALID;
+	}
+
+	if (cal->tech->is_busy) {
+		return cal->tech->is_busy(cal) ? AST_DEVICE_INUSE : AST_DEVICE_NOT_INUSE;
+	}
+
+	return calendar_is_busy(cal) ? AST_DEVICE_INUSE : AST_DEVICE_NOT_INUSE;
+}
+
+static struct ast_calendar *build_calendar(struct ast_config *cfg, const char *cat, const struct ast_calendar_tech *tech)
+{
+	struct ast_calendar *cal;
+	struct ast_variable *v;
+	int new_calendar = 0;
+
+	if (!(cal = find_calendar(cat))) {
+		new_calendar = 1;
+		if (!(cal = ao2_alloc(sizeof(*cal), calendar_destructor))) {
+			ast_log(LOG_ERROR, "Could not allocate calendar structure. Stopping.\n");
+			return NULL;
+		}
+
+		if (!(cal->events = ao2_container_alloc(CALENDAR_BUCKETS, event_hash_fn, event_cmp_fn))) {
+			ast_log(LOG_ERROR, "Could not allocate events container for %s\n", cat);
+			cal = unref_calendar(cal);
+			return NULL;
+		}
+
+		if (ast_string_field_init(cal, 32)) {
+			ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", cat);
+			cal = unref_calendar(cal);
+			return NULL;
+		}
+	} else {
+		cal->pending_deletion = 0;
+	}
+
+	ast_string_field_set(cal, name, cat);
+	cal->tech = tech;
+
+	cal->refresh = 3600;
+	cal->timeframe = 60;
+
+	for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
+		if (!strcasecmp(v->name, "autoreminder")) {
+			cal->autoreminder = atoi(v->value);
+		} else if (!strcasecmp(v->name, "channel")) {
+			ast_string_field_set(cal, notify_channel, v->value);
+		} else if (!strcasecmp(v->name, "context")) {
+			ast_string_field_set(cal, notify_context, v->value);
+		} else if (!strcasecmp(v->name, "extension")) {
+			ast_string_field_set(cal, notify_extension, v->value);
+		} else if (!strcasecmp(v->name, "waittime")) {
+			cal->notify_waittime = atoi(v->value);
+		} else if (!strcasecmp(v->name, "app")) {
+			ast_string_field_set(cal, notify_app, v->value);
+		} else if (!strcasecmp(v->name, "appdata")) {
+			ast_string_field_set(cal, notify_appdata, v->value);
+		} else if (!strcasecmp(v->name, "refresh")) {
+			cal->refresh = atoi(v->value);
+		} else if (!strcasecmp(v->name, "timeframe")) {
+			cal->timeframe = atoi(v->value);
+		}
+	}
+
+	if (new_calendar) {
+		cal->thread = AST_PTHREADT_NULL;
+		ast_cond_init(&cal->unload, NULL);
+		ao2_link(calendars, cal);
+		if (ast_pthread_create(&cal->thread, NULL, cal->tech->load_calendar, cal)) {
+			/* If we start failing to create threads, go ahead and return NULL
+			 * and the tech module will be unregistered
+			 */ 
+			ao2_unlink(calendars, cal);
+			cal = unref_calendar(cal);
+		}
+	}
+
+	return cal;
+}
+
+static int load_tech_calendars(struct ast_calendar_tech *tech)
+{
+	struct ast_calendar *cal;
+	const char *cat = NULL;
+	const char *val;
+
+	if (!calendar_config) {
+		ast_log(LOG_WARNING, "Calendar support disabled, not loading %s calendar module\n", tech->type);
+		return -1;
+	}
+
+	while ((cat = ast_category_browse(calendar_config, cat))) {
+		if (!strcasecmp(cat, "general")) {
+			continue;
+		}
+
+		if (!(val = ast_variable_retrieve(calendar_config, cat, "type")) || strcasecmp(val, tech->type)) {
+			continue;
+		}
+
+		/* A serious error occurred loading calendars from this tech and it should be disabled */
+		if (!(cal = build_calendar(calendar_config, cat, tech))) {
+			ast_calendar_unregister(tech);
+			return -1;
+		}
+
+		cal = unref_calendar(cal);
+	}
+
+	return 0;
+}
+
+int ast_calendar_register(struct ast_calendar_tech *tech)
+{
+	struct ast_calendar_tech *iter;
+
+	AST_LIST_LOCK(&techs);
+	AST_LIST_TRAVERSE(&techs, iter, list) {
+		if(!strcasecmp(tech->type, iter->type)) {
+			ast_log(LOG_WARNING, "Already have a handler for calendar type '%s'\n", tech->type);
+			AST_LIST_UNLOCK(&techs);
+			return -1;
+		}
+	}
+	AST_LIST_INSERT_HEAD(&techs, tech, list);
+	AST_LIST_UNLOCK(&techs);
+
+	ast_verb(2, "Registered calendar type '%s' (%s)\n", tech->type, tech->description);
+
+	return load_tech_calendars(tech);
+}
+
+static int match_caltech_cb(void *user_data, void *arg, int flags)
+{
+	struct ast_calendar *cal = user_data;
+	struct ast_calendar_tech *tech = arg;
+
+	if (cal->tech == tech) {
+		return CMP_MATCH;
+	}
+
+	return 0;
+}
+
+void ast_calendar_unregister(struct ast_calendar_tech *tech)
+{
+	struct ast_calendar_tech *iter;
+
+	AST_LIST_LOCK(&techs);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&techs, iter, list) {
+		if (iter != tech) {
+			continue;
+		}
+
+		ao2_callback(calendars, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, match_caltech_cb, tech);
+
+		AST_LIST_REMOVE_CURRENT(list);
+		ast_verb(2, "Unregistered calendar type '%s'\n", tech->type);
+		break;
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&techs);
+
+}
+
+static void calendar_event_destructor(void *obj)
+{
+	struct ast_calendar_event *event = obj;
+	struct ast_calendar_attendee *attendee;
+
+	ast_debug(3, "Destroying event for calendar '%s'\n", event->owner->name);
+	ast_string_field_free_memory(event);
+	while ((attendee = AST_LIST_REMOVE_HEAD(&event->attendees, next))) {
+		if (attendee->data) {
+			ast_free(attendee->data);
+		}
+		ast_free(attendee);
+	}
+}
+
+/* This is only called from ao2_callbacks that are going to unref the event for us,
+ * so we don't unref the event here.  */
+static struct ast_calendar_event *destroy_event(struct ast_calendar_event *event)
+{
+	if (event->notify_sched > -1 && ast_sched_del(sched, event->notify_sched)) {
+		ast_debug(3, "Notification running, can't delete sched entry\n");
+	}
+	if (event->bs_start_sched > -1 && ast_sched_del(sched, event->bs_start_sched)) {
+		ast_debug(3, "Devicestate update (start) running, can't delete sched entry\n");
+	}
+	if (event->bs_end_sched > -1 && ast_sched_del(sched, event->bs_end_sched)) {
+		ast_debug(3, "Devicestate update (end) running, can't delete sched entry\n");
+	}
+
+	/* If an event is being deleted and we've fired an event changing the status at the beginning,
+	 * but haven't hit the end event yet, go ahead and set the devicestate to the current busy status */
+	if (event->bs_start_sched < 0 && event->bs_end_sched >= 0) {
+		if (!calendar_is_busy(event->owner)) {
+			ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Calendar/%s", event->owner->name);
+		} else {
+			ast_devstate_changed(AST_DEVICE_BUSY, "Calendar/%s", event->owner->name);
+		}
+	}
+
+	return NULL;
+}
+
+static int clear_events_cb(void *user_data, void *arg, int flags)
+{
+	struct ast_calendar_event *event = user_data;
+
+	event = destroy_event(event);
+
+	return CMP_MATCH;
+}
+
+void ast_calendar_clear_events(struct ast_calendar *cal)
+{
+	ast_debug(3, "Clearing all events for calendar %s\n", cal->name);
+
+	ao2_callback(cal->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, clear_events_cb, NULL);
+}
+
+struct ast_calendar_event *ast_calendar_event_alloc(struct ast_calendar *cal)
+{
+	struct ast_calendar_event *event;
+	if (!(event = ao2_alloc(sizeof(*event), calendar_event_destructor))) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(event, 32)) {
+		event = ast_calendar_unref_event(event);
+		return NULL;
+	}
+
+	event->owner = cal;
+	event->notify_sched = -1;
+	event->bs_start_sched = -1;
+	event->bs_end_sched = -1;
+
+	AST_LIST_HEAD_INIT_NOLOCK(&event->attendees);
+
+	return event;
+}
+
+struct ao2_container *ast_calendar_event_container_alloc(void)
+{
+	return ao2_container_alloc(CALENDAR_BUCKETS, event_hash_fn, event_cmp_fn);
+}
+
+static void event_notification_destroy(void *data)
+{
+	struct ast_calendar_event *event = data;
+
+	event = ast_calendar_unref_event(event);
+
+}
+
+static void *event_notification_duplicate(void *data)
+{
+	struct ast_calendar_event *event = data;
+
+	if (!event) {
+		return NULL;
+	}
+
+	ao2_ref(event, +1);
+
+	return event;
+}
+
+/*! \brief Generate 32 byte random string (stolen from chan_sip.c)*/
+static char *generate_random_string(char *buf, size_t size)
+{
+	long val[4];
+	int x;
+
+	for (x = 0; x < 4; x++) {
+		val[x] = ast_random();
+	}
+	snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);
+
+	return buf;
+}
+
+static int calendar_event_notify(const void *data)
+{
+	struct ast_calendar_event *event = (void *)data;
+	char tech[256], dest[256], buf[8], *tmp;
+	struct ast_dial *dial = NULL;
+	struct ast_channel *chan = NULL;
+	struct ast_str *apptext = NULL;
+	int res = -1;
+	char start[12], end[12], busystate[2];
+	struct ast_datastore *datastore;
+
+	if (!(event && event->owner)) {
+		ast_log(LOG_ERROR, "Extremely low-cal...in fact cal is NULL!\n");
+		goto notify_cleanup;
+	}
+
+	ao2_ref(event, +1);
+	event->notify_sched = -1;
+
+	ast_copy_string(tech, event->owner->notify_channel, sizeof(tech));
+
+	if ((tmp = strchr(tech, '/'))) {
+		*tmp = '\0';
+		tmp++;
+		ast_copy_string(dest, tmp, sizeof(dest));
+	} else {
+		ast_log(LOG_WARNING, "Channel should be in form Tech/Dest\n");
+		goto notify_cleanup;
+	}
+
+	if (!(dial = ast_dial_create())) {
+		ast_log(LOG_ERROR, "Could not create dial structure\n");
+		goto notify_cleanup;
+	}
+
+	if (ast_dial_append(dial, tech, dest) < 0) {
+		ast_log(LOG_ERROR, "Could not append channel\n");
+		goto notify_cleanup;
+	}
+
+	ast_dial_set_global_timeout(dial, event->owner->notify_waittime);
+	generate_random_string(buf, sizeof(buf));
+	if (!(chan = ast_channel_alloc(1, AST_STATE_DOWN, 0, 0, 0, 0, 0, 0, "Calendar/%s-%s", event->owner->name, buf))) {
+		ast_log(LOG_ERROR, "Could not allocate notification channel\n");
+		goto notify_cleanup;
+	}
+
+	snprintf(busystate, sizeof(busystate), "%d", event->busy_state);
+	snprintf(start, sizeof(start), "%lu", event->start);
+	snprintf(end, sizeof(end), "%lu", event->end);
+
+	chan->nativeformats = AST_FORMAT_SLINEAR;
+
+	if (!(datastore = ast_datastore_alloc(&event_notification_datastore, NULL))) {
+		ast_log(LOG_ERROR, "Could not allocate datastore, notification not being sent!\n");
+		goto notify_cleanup;
+	}
+
+	datastore->data = event;
+	datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+
+	ao2_ref(event, +1);
+	res = ast_channel_datastore_add(chan, datastore);
+
+	if (!(apptext = ast_str_create(32))) {
+		goto notify_cleanup;
+	}
+
+	if (!ast_strlen_zero(event->owner->notify_app)) {
+		ast_str_set(&apptext, 0, "%s,%s", event->owner->notify_app, event->owner->notify_appdata);
+	} else {
+		ast_str_set(&apptext, 0, "Dial,Local/%s@%s", event->owner->notify_extension, event->owner->notify_context);
+	}
+	ast_dial_option_global_enable(dial, AST_DIAL_OPTION_ANSWER_EXEC, ast_str_buffer(apptext));
+
+	ast_dial_run(dial, chan, 1);
+	res = 0;
+
+notify_cleanup:
+	event = ast_calendar_unref_event(event);
+	if (res == -1 && dial) {
+		ast_dial_destroy(dial);
+	}
+	if (apptext) {
+		ast_free(apptext);
+	}
+	if (chan) {
+		ast_channel_release(chan);
+	}
+
+	return res;
+}
+
+static int calendar_devstate_change(const void *data)
+{
+	struct ast_calendar_event *event = (struct ast_calendar_event *)data;
+	struct timeval now = ast_tvnow();
+	int is_end_event;
+
+	if (!event) {

[... 2914 lines stripped ...]



More information about the svn-commits mailing list