paulb@22 | 1 | #!/usr/bin/env python |
paulb@22 | 2 | |
paulb@22 | 3 | "A calendar application." |
paulb@22 | 4 | |
paulb@22 | 5 | import WebStack.Generic |
paulb@380 | 6 | import time |
paulb@416 | 7 | import os |
paulb@22 | 8 | |
paulb@416 | 9 | class CalendarResource: |
paulb@22 | 10 | |
paulb@22 | 11 | """ |
paulb@22 | 12 | A resource which handles incoming calendars and viewing requests. |
paulb@22 | 13 | An arbitrary set of rules can be applied to determine what is to be done |
paulb@22 | 14 | with a request, and in this case, the application appears as a directory of |
paulb@22 | 15 | calendars, yet also accepts incoming calendars. |
paulb@22 | 16 | """ |
paulb@22 | 17 | |
paulb@416 | 18 | resource_dir = os.path.join(os.path.split(__file__)[0], "calendars") |
paulb@456 | 19 | encoding = "utf-8" |
paulb@416 | 20 | |
paulb@459 | 21 | def __init__(self): |
paulb@416 | 22 | if not os.path.exists(self.resource_dir): |
paulb@416 | 23 | os.mkdir(self.resource_dir) |
paulb@459 | 24 | |
paulb@459 | 25 | if os.path.supports_unicode_filenames: |
paulb@459 | 26 | self.fsencoding = None |
paulb@459 | 27 | else: |
paulb@459 | 28 | import locale |
paulb@459 | 29 | self.fsencoding = locale.getdefaultlocale()[1] |
paulb@456 | 30 | |
paulb@456 | 31 | def _convert_name(self, name): |
paulb@456 | 32 | if self.fsencoding: |
paulb@456 | 33 | return name.encode(self.fsencoding) |
paulb@456 | 34 | else: |
paulb@456 | 35 | return name |
paulb@456 | 36 | |
paulb@456 | 37 | def _convert_fsname(self, name): |
paulb@456 | 38 | if self.fsencoding: |
paulb@456 | 39 | return unicode(name, self.fsencoding) |
paulb@456 | 40 | else: |
paulb@456 | 41 | return name |
paulb@416 | 42 | |
paulb@22 | 43 | def respond(self, trans): |
paulb@22 | 44 | |
paulb@22 | 45 | """ |
paulb@22 | 46 | Examine the incoming request, either saving a calendar or displaying |
paulb@22 | 47 | one. |
paulb@22 | 48 | """ |
paulb@22 | 49 | |
paulb@22 | 50 | # Determine the action to be taken. |
paulb@22 | 51 | |
paulb@22 | 52 | method = trans.get_request_method() |
paulb@22 | 53 | |
paulb@22 | 54 | # NOTE: Some frameworks do not pass in the content type. |
paulb@416 | 55 | # NOTE: We always assume that calendar files are being uploaded. |
paulb@22 | 56 | |
paulb@456 | 57 | calendar_name = trans.get_virtual_path_info(self.encoding).split("/")[-1] |
paulb@22 | 58 | |
paulb@380 | 59 | # Handle uploads. |
paulb@380 | 60 | |
paulb@22 | 61 | if method == "PUT": |
paulb@380 | 62 | |
paulb@380 | 63 | # Get the last path component as the name of the calendar. |
paulb@380 | 64 | # NOTE: This could be improved to permit hierarchical naming. |
paulb@380 | 65 | |
paulb@22 | 66 | input = trans.get_request_stream() |
paulb@380 | 67 | data = input.read() |
paulb@380 | 68 | |
paulb@416 | 69 | # Store the calendar in the directory. |
paulb@22 | 70 | |
paulb@456 | 71 | f = open(self._convert_name(os.path.join(self.resource_dir, calendar_name)), "wb") |
paulb@416 | 72 | f.write(data) |
paulb@416 | 73 | f.close() |
paulb@22 | 74 | |
paulb@380 | 75 | # Handle directory browsing. |
paulb@380 | 76 | |
paulb@380 | 77 | elif method == "PROPFIND": |
paulb@380 | 78 | trans.set_response_code(207) |
paulb@456 | 79 | trans.set_content_type(WebStack.Generic.ContentType("text/xml", self.encoding)) |
paulb@22 | 80 | out = trans.get_response_stream() |
paulb@380 | 81 | out.write("""<?xml version="1.0"?> |
paulb@380 | 82 | <D:multistatus xmlns:D="DAV:"> |
paulb@380 | 83 | """) |
paulb@380 | 84 | |
paulb@456 | 85 | if trans.get_virtual_path_info(self.encoding) == "/": |
paulb@416 | 86 | time_now = time.strftime("%Y-%m-%dT%TZ", time.gmtime(time.time())) |
paulb@380 | 87 | out.write(""" |
paulb@380 | 88 | <D:response> |
paulb@380 | 89 | <D:href>%s</D:href> |
paulb@380 | 90 | <D:propstat> |
paulb@380 | 91 | <D:prop> |
paulb@380 | 92 | <D:creationdate>%s</D:creationdate> |
paulb@380 | 93 | <D:displayname>%s</D:displayname> |
paulb@380 | 94 | <D:resourcetype> |
paulb@380 | 95 | <D:collection/> |
paulb@380 | 96 | </D:resourcetype> |
paulb@380 | 97 | </D:prop> |
paulb@380 | 98 | <D:status>HTTP/1.1 200 OK</D:status> |
paulb@380 | 99 | </D:propstat> |
paulb@380 | 100 | </D:response> |
paulb@456 | 101 | """ % ( |
paulb@456 | 102 | trans.get_path_without_query(self.encoding), |
paulb@456 | 103 | time_now, |
paulb@456 | 104 | trans.get_path_without_query(self.encoding))) |
paulb@380 | 105 | |
paulb@416 | 106 | for filename in os.listdir(self.resource_dir): |
paulb@416 | 107 | pathname = os.path.join(self.resource_dir, filename) |
paulb@416 | 108 | created = time.strftime("%Y-%m-%dT%TZ", time.gmtime(os.path.getctime(pathname))) |
paulb@416 | 109 | size = os.path.getsize(pathname) |
paulb@380 | 110 | out.write(""" |
paulb@380 | 111 | <D:response> |
paulb@380 | 112 | <D:href>%s%s</D:href> |
paulb@380 | 113 | <D:propstat> |
paulb@380 | 114 | <D:prop> |
paulb@380 | 115 | <D:creationdate>%s</D:creationdate> |
paulb@380 | 116 | <D:displayname>%s</D:displayname> |
paulb@380 | 117 | <D:resourcetype/> |
paulb@380 | 118 | <D:getcontenttype>%s</D:getcontenttype> |
paulb@416 | 119 | <D:getcontentlength>%s</D:getcontentlength> |
paulb@380 | 120 | </D:prop> |
paulb@380 | 121 | <D:status>HTTP/1.1 200 OK</D:status> |
paulb@380 | 122 | </D:propstat> |
paulb@380 | 123 | </D:response> |
paulb@456 | 124 | """ % ( |
paulb@456 | 125 | trans.get_path_without_query(self.encoding), |
paulb@456 | 126 | self._convert_fsname(filename), |
paulb@456 | 127 | created, |
paulb@456 | 128 | self._convert_fsname(filename), |
paulb@456 | 129 | "text/calendar", |
paulb@456 | 130 | size)) |
paulb@380 | 131 | |
paulb@380 | 132 | out.write(""" |
paulb@380 | 133 | </D:multistatus> |
paulb@380 | 134 | """) |
paulb@380 | 135 | |
paulb@380 | 136 | # Handle downloads. |
paulb@380 | 137 | |
paulb@380 | 138 | elif method == "GET": |
paulb@456 | 139 | trans.set_content_type(WebStack.Generic.ContentType("text/calendar")) |
paulb@380 | 140 | out = trans.get_response_stream() |
paulb@456 | 141 | f = open(self._convert_name(os.path.join(self.resource_dir, calendar_name))) |
paulb@416 | 142 | out.write(f.read()) |
paulb@416 | 143 | f.close() |
paulb@416 | 144 | |
paulb@416 | 145 | # Handle deletion. |
paulb@416 | 146 | |
paulb@416 | 147 | elif method == "DELETE": |
paulb@416 | 148 | try: |
paulb@416 | 149 | os.remove(os.path.join(self.resource_dir, calendar_name)) |
paulb@416 | 150 | except OSError: |
paulb@416 | 151 | trans.set_response_code(500) |
paulb@416 | 152 | |
paulb@416 | 153 | # Handle renaming. |
paulb@416 | 154 | |
paulb@416 | 155 | elif method in ("MOVE", "COPY"): |
paulb@416 | 156 | destinations = trans.get_header_values("Destination") |
paulb@416 | 157 | if len(destinations) != 1: |
paulb@416 | 158 | trans.set_response_code(500) |
paulb@416 | 159 | else: |
paulb@416 | 160 | try: |
paulb@416 | 161 | # Convert the URL into a filename. |
paulb@416 | 162 | # NOTE: Assume that the URL references the same "directory". |
paulb@416 | 163 | |
paulb@416 | 164 | destination = destinations[0].split("/")[-1] |
paulb@456 | 165 | destination = trans.decode_path(destination, self.encoding) |
paulb@416 | 166 | |
paulb@416 | 167 | if method == "MOVE": |
paulb@456 | 168 | os.rename( |
paulb@456 | 169 | self._convert_name(os.path.join(self.resource_dir, calendar_name)), |
paulb@456 | 170 | self._convert_name(os.path.join(self.resource_dir, destination)) |
paulb@456 | 171 | ) |
paulb@416 | 172 | elif method == "COPY": |
paulb@456 | 173 | f_old = open(self._convert_name(os.path.join(self.resource_dir, calendar_name)), "rb") |
paulb@456 | 174 | f_new = open(self._convert_name(os.path.join(self.resource_dir, destination)), "wb") |
paulb@416 | 175 | f_new.write(f_old.read()) |
paulb@416 | 176 | f_new.close() |
paulb@416 | 177 | f_old.close() |
paulb@416 | 178 | |
paulb@416 | 179 | # NOTE: We do not observe the rules regarding overwriting |
paulb@416 | 180 | # NOTE: and the appropriate status codes. |
paulb@416 | 181 | |
paulb@416 | 182 | trans.set_header_value("Location", destinations[0]) |
paulb@416 | 183 | trans.set_response_code(201) |
paulb@416 | 184 | |
paulb@416 | 185 | except OSError: |
paulb@416 | 186 | trans.set_response_code(500) |
paulb@380 | 187 | |
paulb@380 | 188 | # Disallow other methods. |
paulb@380 | 189 | |
paulb@380 | 190 | else: |
paulb@380 | 191 | trans.set_response_code(405) |
paulb@22 | 192 | |
paulb@22 | 193 | # vim: tabstop=4 expandtab shiftwidth=4 |