diff options
author | Mike Crute <mcrute@gmail.com> | 2010-02-15 22:11:50 -0500 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2010-02-15 22:11:50 -0500 |
commit | df130cd5d132fc5eaf1c88b16c33289c9f31f559 (patch) | |
tree | e6fda7905a2341a75a0b9051c5fe8766fb4748c2 /exchange | |
parent | 6ac707f8ab6ccc551bb0bf1a92aee4ce5329d4f3 (diff) | |
download | calendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.tar.bz2 calendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.tar.xz calendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.zip |
Re-factoring for better abstraction. Moving timezone.
Diffstat (limited to 'exchange')
-rw-r--r-- | exchange/__init__.py | 17 | ||||
-rw-r--r-- | exchange/commands.py | 116 | ||||
-rw-r--r-- | exchange/timezones.py | 21 |
3 files changed, 71 insertions, 83 deletions
diff --git a/exchange/__init__.py b/exchange/__init__.py index a2a78ec..eafb43f 100644 --- a/exchange/__init__.py +++ b/exchange/__init__.py | |||
@@ -7,6 +7,23 @@ Exchange Server Handling Code | |||
7 | @date: April 26, 2009 | 7 | @date: April 26, 2009 |
8 | """ | 8 | """ |
9 | 9 | ||
10 | from datetime import tzinfo, timedelta | ||
11 | |||
10 | 12 | ||
11 | class ExchangeException(Exception): | 13 | class ExchangeException(Exception): |
12 | "Exception that is thrown by all Exchange handling code." | 14 | "Exception that is thrown by all Exchange handling code." |
15 | |||
16 | |||
17 | class AuthenticationException(ExchangeException): | ||
18 | "Exception that is raised when authentication fails." | ||
19 | |||
20 | |||
21 | class EST(tzinfo): | ||
22 | |||
23 | def tzname(self, dt): | ||
24 | return "EST" | ||
25 | |||
26 | def utcoffset(self, dt): | ||
27 | return timedelta(0) | ||
28 | |||
29 | dst = utcoffset | ||
diff --git a/exchange/commands.py b/exchange/commands.py index 8198ca6..a2ab471 100644 --- a/exchange/commands.py +++ b/exchange/commands.py | |||
@@ -11,17 +11,14 @@ development code but it does the trick. Watch out, it doesn't consider | |||
11 | many corner cases. | 11 | many corner cases. |
12 | """ | 12 | """ |
13 | 13 | ||
14 | import xml.etree.cElementTree as etree | 14 | import xml.etree.cElementTree as ElementTree |
15 | import dateutil.parser as date_parser | ||
15 | 16 | ||
16 | from httplib import HTTPSConnection | 17 | from httplib import HTTPSConnection |
17 | from string import Template | 18 | from string import Template |
18 | from datetime import datetime, timedelta | 19 | from datetime import datetime, timedelta |
19 | 20 | from icalendar import Calendar, Event as _Event, Alarm | |
20 | import dateutil.parser | 21 | from exchange import ExchangeException, EST |
21 | from icalendar import Calendar, Event, Alarm | ||
22 | |||
23 | from exchange import ExchangeException | ||
24 | from exchange.timezones import EST | ||
25 | 22 | ||
26 | 23 | ||
27 | class ExchangeCommand(object): | 24 | class ExchangeCommand(object): |
@@ -40,36 +37,32 @@ class ExchangeCommand(object): | |||
40 | "Translate": "f", | 37 | "Translate": "f", |
41 | } | 38 | } |
42 | 39 | ||
43 | def __init__(self, server, authenticator=None): | 40 | def __init__(self, session): |
44 | self.server = server | 41 | self.server = session.server |
45 | self.authenticator = authenticator | 42 | self.session = session |
46 | 43 | ||
47 | def _get_xml(self, **kwargs): | 44 | def _get_xml(self, **kwargs): |
48 | """ | 45 | """ |
49 | Try to get an XML response from the server. | 46 | Try to get an XML response from the server. |
50 | @return: ElementTree response | 47 | @return: ElementTree response |
51 | """ | 48 | """ |
52 | if not self.authenticator.authenticated: | 49 | kwargs["username"] = self.session.username |
53 | raise ExchangeException("Not authenticated. Call authenticate() first.") | ||
54 | |||
55 | # Lets forcibly override the username with the user we're querying as | ||
56 | kwargs["username"] = self.authenticator.username | ||
57 | 50 | ||
58 | xml = self._get_query(**kwargs) | 51 | xml = self._get_query(**kwargs) |
59 | url = self.BASE_URL.substitute({ "username": self.authenticator.username, | 52 | url = self.BASE_URL.substitute({ "username": self.session.username, |
60 | "method": self.exchange_method }) | 53 | "method": self.exchange_method }) |
61 | query = Template(xml).substitute(kwargs) | 54 | query = Template(xml).substitute(kwargs) |
62 | send_headers = self.authenticator.patch_headers(self.BASE_HEADERS) | 55 | send_headers = self.BASE_HEADERS.copy() |
56 | send_headers['Cookie'] = self.session.token | ||
63 | 57 | ||
64 | conn = HTTPSConnection(self.server) | 58 | conn = HTTPSConnection(self.server) |
65 | conn.request(self.dav_method.upper(), url, query, headers=send_headers) | 59 | conn.request(self.dav_method.upper(), url, query, headers=send_headers) |
66 | resp = conn.getresponse() | 60 | resp = conn.getresponse() |
67 | 61 | ||
68 | # TODO: Lets determine authentication errors here and fix them. | ||
69 | if int(resp.status) > 299 or int(resp.status) < 200: | 62 | if int(resp.status) > 299 or int(resp.status) < 200: |
70 | raise ExchangeException("%s %s" % (resp.status, resp.reason)) | 63 | raise ExchangeException("%s %s" % (resp.status, resp.reason)) |
71 | 64 | ||
72 | return etree.fromstring(resp.read()) | 65 | return ElementTree.fromstring(resp.read()) |
73 | 66 | ||
74 | def _get_query(self, **kwargs): | 67 | def _get_query(self, **kwargs): |
75 | """ | 68 | """ |
@@ -77,18 +70,46 @@ class ExchangeCommand(object): | |||
77 | of template substitutions, also does a little bit of elementtree | 70 | of template substitutions, also does a little bit of elementtree |
78 | magic to to build the XML query. | 71 | magic to to build the XML query. |
79 | """ | 72 | """ |
80 | declaration = etree.ProcessingInstruction("xml", 'version="1.0"') | 73 | declaration = ElementTree.ProcessingInstruction("xml", 'version="1.0"') |
81 | 74 | ||
82 | request = etree.Element("g:searchrequest", { "xmlns:g": "DAV:" }) | 75 | request = ElementTree.Element("g:searchrequest", { "xmlns:g": "DAV:" }) |
83 | query = etree.SubElement(request, "g:sql") | 76 | query = ElementTree.SubElement(request, "g:sql") |
84 | query.text = Template(self.sql).substitute(kwargs) | 77 | query.text = Template(self.sql).substitute(kwargs) |
85 | 78 | ||
86 | output = etree.tostring(declaration) | 79 | output = ElementTree.tostring(declaration) |
87 | output += etree.tostring(request) | 80 | output += ElementTree.tostring(request) |
88 | 81 | ||
89 | return output | 82 | return output |
90 | 83 | ||
91 | 84 | ||
85 | class Event(_Event): | ||
86 | |||
87 | def add_alarm(self, alarm_offset): | ||
88 | alarm = Alarm() | ||
89 | alarm.add("action", "DISPLAY") | ||
90 | alarm.add("description", "REMINDER") | ||
91 | alarm.add("trigger", timedelta(minutes=alarm_offset)) | ||
92 | self.add_component(alarm) | ||
93 | |||
94 | def _get_element_text(self, element, key): | ||
95 | value = element.find(key) | ||
96 | |||
97 | if hasattr(value, 'text'): | ||
98 | return value.text | ||
99 | |||
100 | def add_text(self, element, key, add_as=None): | ||
101 | value = self._get_element_text(element, key) | ||
102 | |||
103 | add_as = key if not add_as else add_as | ||
104 | self.add(add_as, value) | ||
105 | |||
106 | def add_date(self, element, key, add_as=None): | ||
107 | value = date_parser.parse(self._get_element_text(element, key)) | ||
108 | |||
109 | add_as = key if not add_as else add_as | ||
110 | self.add(add_as, value) | ||
111 | |||
112 | |||
92 | class FetchCalendar(ExchangeCommand): | 113 | class FetchCalendar(ExchangeCommand): |
93 | exchange_method = "calendar" | 114 | exchange_method = "calendar" |
94 | dav_method = "search" | 115 | dav_method = "search" |
@@ -119,45 +140,16 @@ class FetchCalendar(ExchangeCommand): | |||
119 | 140 | ||
120 | for item in exchange_xml.getchildren(): | 141 | for item in exchange_xml.getchildren(): |
121 | item = item.find("{DAV:}propstat").find("{DAV:}prop") | 142 | item = item.find("{DAV:}propstat").find("{DAV:}prop") |
122 | event = Event() | ||
123 | 143 | ||
124 | # These tests may look funny but the result of item.find | 144 | event = Event() |
125 | # does NOT evaluate to true even though it is not None | 145 | event.add_text(item, 'subject', add_as='summary') |
126 | # so, we have to check the interface of the returned item | 146 | event.add_text(item, 'location') |
127 | # to make sure its usable. | 147 | event.add_text(item, 'description') |
128 | 148 | event.add_date(item, 'start_date', add_as='dtstart') | |
129 | subject = item.find("subject") | 149 | event.add_date(item, 'end_date', add_as='dtend') |
130 | if hasattr(subject, "text"): | 150 | |
131 | event.add("summary", subject.text) | 151 | # TODO: Handle timezone_info |
132 | 152 | # TODO: Handle alarms. Previous implementation didn't work | |
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 | 153 | ||
162 | calendar.add_component(event) | 154 | calendar.add_component(event) |
163 | 155 | ||
diff --git a/exchange/timezones.py b/exchange/timezones.py deleted file mode 100644 index b570e7b..0000000 --- a/exchange/timezones.py +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Timezone Definitions | ||
4 | |||
5 | @author: Mike Crute (mcrute@gmail.com) | ||
6 | @organization: SoftGroup Interactive, Inc. | ||
7 | @date: April 26, 2009 | ||
8 | """ | ||
9 | |||
10 | from datetime import tzinfo, timedelta | ||
11 | |||
12 | |||
13 | class EST(tzinfo): | ||
14 | |||
15 | def tzname(self, dt): | ||
16 | return "EST" | ||
17 | |||
18 | def utcoffset(self, dt): | ||
19 | return timedelta(0) | ||
20 | |||
21 | dst = utcoffset | ||