summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2010-02-15 22:11:50 -0500
committerMike Crute <mcrute@gmail.com>2010-02-15 22:11:50 -0500
commitdf130cd5d132fc5eaf1c88b16c33289c9f31f559 (patch)
treee6fda7905a2341a75a0b9051c5fe8766fb4748c2
parent6ac707f8ab6ccc551bb0bf1a92aee4ce5329d4f3 (diff)
downloadcalendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.tar.bz2
calendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.tar.xz
calendar_proxy-df130cd5d132fc5eaf1c88b16c33289c9f31f559.zip
Re-factoring for better abstraction. Moving timezone.
-rw-r--r--exchange/__init__.py17
-rw-r--r--exchange/commands.py116
-rw-r--r--exchange/timezones.py21
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
10from datetime import tzinfo, timedelta
11
10 12
11class ExchangeException(Exception): 13class ExchangeException(Exception):
12 "Exception that is thrown by all Exchange handling code." 14 "Exception that is thrown by all Exchange handling code."
15
16
17class AuthenticationException(ExchangeException):
18 "Exception that is raised when authentication fails."
19
20
21class 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
11many corner cases. 11many corner cases.
12""" 12"""
13 13
14import xml.etree.cElementTree as etree 14import xml.etree.cElementTree as ElementTree
15import dateutil.parser as date_parser
15 16
16from httplib import HTTPSConnection 17from httplib import HTTPSConnection
17from string import Template 18from string import Template
18from datetime import datetime, timedelta 19from datetime import datetime, timedelta
19 20from icalendar import Calendar, Event as _Event, Alarm
20import dateutil.parser 21from exchange import ExchangeException, EST
21from icalendar import Calendar, Event, Alarm
22
23from exchange import ExchangeException
24from exchange.timezones import EST
25 22
26 23
27class ExchangeCommand(object): 24class 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
85class 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
92class FetchCalendar(ExchangeCommand): 113class 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"""
3Timezone Definitions
4
5@author: Mike Crute (mcrute@gmail.com)
6@organization: SoftGroup Interactive, Inc.
7@date: April 26, 2009
8"""
9
10from datetime import tzinfo, timedelta
11
12
13class 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