summaryrefslogtreecommitdiff
path: root/exchange/commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'exchange/commands.py')
-rw-r--r--exchange/commands.py164
1 files changed, 164 insertions, 0 deletions
diff --git a/exchange/commands.py b/exchange/commands.py
new file mode 100644
index 0000000..855e1b5
--- /dev/null
+++ b/exchange/commands.py
@@ -0,0 +1,164 @@
1# -*- coding: utf-8 -*-
2"""
3Exchange Commands
4
5@author: Mike Crute (mcrute@gmail.com)
6@date: November 10, 2008
7@version: $Revision$
8
9This is a set of classes that starts to define a set of classes for
10fetching data using Exchange's WebDAV API. This is still pretty
11development code but it does the trick. Watch out, it doesn't consider
12many corner cases.
13
14$Id$
15"""
16import xml.etree.cElementTree as etree
17
18from httplib import HTTPSConnection
19from string import Template
20from datetime import datetime, timedelta
21
22import dateutil.parser
23from icalendar import Calendar, Event, Alarm
24
25from exchange import ExchangeException
26
27
28class ExchangeCommand(object):
29 """
30 Base class for Exchange commands. This really shouldn't be constructed
31 directly but should be subclassed to do useful things.
32 """
33
34 #: Base URL for Exchange commands.
35 BASE_URL = Template("/exchange/${username}/${method}")
36
37 #: Basic headers that are required for all requests
38 BASE_HEADERS = {
39 "Content-Type": 'text/xml; charset="UTF-8"',
40 "Depth": "0",
41 "Translate": "f",
42 }
43
44 def __init__(self, server, authenticator=None):
45 self.server = server
46 self.authenticator = authenticator
47
48 def _get_xml(self, **kwargs):
49 """
50 Try to get an XML response from the server.
51 @return: ElementTree response
52 """
53 if not self.authenticator.authenticated:
54 raise ExchangeException("Not authenticated. Call authenticate() first.")
55
56 # Lets forcibly override the username with the user we're querying as
57 kwargs["username"] = self.authenticator.username
58
59 xml = self._get_query(**kwargs)
60 url = self.BASE_URL.substitute({ "username": self.authenticator.username,
61 "method": self.exchange_method })
62 query = Template(xml).substitute(kwargs)
63 send_headers = self.authenticator.patch_headers(self.BASE_HEADERS)
64
65 conn = HTTPSConnection(self.server)
66 conn.request(self.dav_method.upper(), url, query, headers=send_headers)
67 resp = conn.getresponse()
68
69 # TODO: Lets determine authentication errors here and fix them.
70 if int(resp.status) > 299 or int(resp.status) < 200:
71 raise ExchangeException("%s %s" % (resp.status, resp.reason))
72
73 return etree.fromstring(resp.read())
74
75 def _get_query(self, **kwargs):
76 """
77 Build up the XML query for the server. Mostly just does a lot
78 of template substitutions, also does a little bit of elementtree
79 magic to to build the XML query.
80 """
81 declaration = etree.ProcessingInstruction("xml", 'version="1.0"')
82
83 request = etree.Element("g:searchrequest", { "xmlns:g": "DAV:" })
84 query = etree.SubElement(request, "g:sql")
85 query.text = Template(self.sql).substitute(kwargs)
86
87 output = etree.tostring(declaration)
88 output += etree.tostring(request)
89
90 return output
91
92
93class FetchCalendar(ExchangeCommand):
94 exchange_method = "calendar"
95 dav_method = "search"
96
97 sql = """
98 SELECT
99 "urn:schemas:calendar:location" AS location,
100 "urn:schemas:httpmail:normalizedsubject" AS subject,
101 "urn:schemas:calendar:dtstart" AS start_date,
102 "urn:schemas:calendar:dtend" AS end_date,
103 "urn:schemas:calendar:busystatus" AS busy_status,
104 "urn:schemas:calendar:instancetype" AS instance_type,
105 "urn:schemas:calendar:timezone" AS timezone_info,
106 "urn:schemas:httpmail:textdescription" AS description
107 FROM
108 Scope('SHALLOW TRAVERSAL OF "/exchange/${username}/calendar/"')
109 WHERE
110 NOT "urn:schemas:calendar:instancetype" = 1
111 AND "DAV:contentclass" = 'urn:content-classes:appointment'
112 ORDER BY
113 "urn:schemas:calendar:dtstart" ASC
114 """
115
116 def execute(self, alarms=True, alarm_offset=15, **kwargs):
117 exchange_xml = self._get_xml(**kwargs)
118 calendar = Calendar()
119
120 for item in exchange_xml.getchildren():
121 item = item.find("{DAV:}propstat").find("{DAV:}prop")
122 event = Event()
123
124 # These tests may look funny but the result of item.find
125 # does NOT evaluate to true even though it is not None
126 # so, we have to check the interface of the returned item
127 # to make sure its usable.
128
129 subject = item.find("subject")
130 if hasattr(subject, "text"):
131 event.add("summary", subject.text)
132
133 location = item.find("location")
134 if hasattr(location, "text"):
135 event.add("location", location.text)
136
137 description = item.find("description")
138 if hasattr(description, "text"):
139 event.add("description", description.text)
140
141 # Dates should always exist
142 start_date = dateutil.parser.parse(item.find("start_date").text)
143 event.add("dtstart", start_date)
144
145 end_date = dateutil.parser.parse(item.find("end_date").text)
146 event.add("dtend", end_date)
147
148 if item.get("timezone_info"):
149 """This comes back from Exchange as already formatted
150 ical data. We probably need to parse and re-construct
151 it unless the icalendar api lets us just dump it out.
152 """
153 pass
154
155 if alarms and start_date > datetime.now(tz=EST()):
156 alarm = Alarm()
157 alarm.add("action", "DISPLAY")
158 alarm.add("description", "REMINDER")
159 alarm.add("trigger", timedelta(minutes=alarm_offset))
160 event.add_component(alarm)
161
162 calendar.add_component(event)
163
164 return calendar