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@446 | 19 | urlencoding = "utf-8" |
paulb@416 | 20 | |
paulb@416 | 21 | def __init__(self): |
paulb@416 | 22 | if not os.path.exists(self.resource_dir): |
paulb@416 | 23 | os.mkdir(self.resource_dir) |
paulb@416 | 24 | |
paulb@22 | 25 | def respond(self, trans): |
paulb@22 | 26 | |
paulb@22 | 27 | """ |
paulb@22 | 28 | Examine the incoming request, either saving a calendar or displaying |
paulb@22 | 29 | one. |
paulb@22 | 30 | """ |
paulb@22 | 31 | |
paulb@380 | 32 | # Remember uploaded calendars using a session. |
paulb@22 | 33 | |
paulb@380 | 34 | session = trans.get_session(create=1) |
paulb@22 | 35 | |
paulb@22 | 36 | # Determine the action to be taken. |
paulb@22 | 37 | |
paulb@22 | 38 | method = trans.get_request_method() |
paulb@22 | 39 | |
paulb@22 | 40 | # NOTE: Some frameworks do not pass in the content type. |
paulb@416 | 41 | # NOTE: We always assume that calendar files are being uploaded. |
paulb@22 | 42 | |
paulb@22 | 43 | content_type = trans.get_content_type() |
paulb@446 | 44 | calendar_name = trans.get_virtual_path_info(self.urlencoding).split("/")[-1] |
paulb@22 | 45 | |
paulb@380 | 46 | # Handle uploads. |
paulb@380 | 47 | |
paulb@22 | 48 | if method == "PUT": |
paulb@380 | 49 | |
paulb@380 | 50 | # Get the last path component as the name of the calendar. |
paulb@380 | 51 | # NOTE: This could be improved to permit hierarchical naming. |
paulb@380 | 52 | |
paulb@22 | 53 | input = trans.get_request_stream() |
paulb@380 | 54 | data = input.read() |
paulb@380 | 55 | |
paulb@416 | 56 | # Store the calendar in the directory. |
paulb@22 | 57 | |
paulb@416 | 58 | f = open(os.path.join(self.resource_dir, calendar_name), "wb") |
paulb@416 | 59 | f.write(data) |
paulb@416 | 60 | f.close() |
paulb@22 | 61 | |
paulb@380 | 62 | # Handle directory browsing. |
paulb@380 | 63 | |
paulb@380 | 64 | elif method == "PROPFIND": |
paulb@380 | 65 | trans.set_response_code(207) |
paulb@22 | 66 | trans.set_content_type(WebStack.Generic.ContentType("text/html")) |
paulb@22 | 67 | out = trans.get_response_stream() |
paulb@380 | 68 | out.write("""<?xml version="1.0"?> |
paulb@380 | 69 | <D:multistatus xmlns:D="DAV:"> |
paulb@380 | 70 | """) |
paulb@380 | 71 | |
paulb@446 | 72 | if trans.get_virtual_path_info(self.urlencoding) == "/": |
paulb@416 | 73 | time_now = time.strftime("%Y-%m-%dT%TZ", time.gmtime(time.time())) |
paulb@380 | 74 | out.write(""" |
paulb@380 | 75 | <D:response> |
paulb@380 | 76 | <D:href>%s</D:href> |
paulb@380 | 77 | <D:propstat> |
paulb@380 | 78 | <D:prop> |
paulb@380 | 79 | <D:creationdate>%s</D:creationdate> |
paulb@380 | 80 | <D:displayname>%s</D:displayname> |
paulb@380 | 81 | <D:resourcetype> |
paulb@380 | 82 | <D:collection/> |
paulb@380 | 83 | </D:resourcetype> |
paulb@380 | 84 | </D:prop> |
paulb@380 | 85 | <D:status>HTTP/1.1 200 OK</D:status> |
paulb@380 | 86 | </D:propstat> |
paulb@380 | 87 | </D:response> |
paulb@416 | 88 | """ % (trans.get_path_without_query(), time_now, trans.get_path_without_query())) |
paulb@380 | 89 | |
paulb@416 | 90 | for filename in os.listdir(self.resource_dir): |
paulb@416 | 91 | pathname = os.path.join(self.resource_dir, filename) |
paulb@416 | 92 | created = time.strftime("%Y-%m-%dT%TZ", time.gmtime(os.path.getctime(pathname))) |
paulb@416 | 93 | size = os.path.getsize(pathname) |
paulb@380 | 94 | out.write(""" |
paulb@380 | 95 | <D:response> |
paulb@380 | 96 | <D:href>%s%s</D:href> |
paulb@380 | 97 | <D:propstat> |
paulb@380 | 98 | <D:prop> |
paulb@380 | 99 | <D:creationdate>%s</D:creationdate> |
paulb@380 | 100 | <D:displayname>%s</D:displayname> |
paulb@380 | 101 | <D:resourcetype/> |
paulb@380 | 102 | <D:getcontenttype>%s</D:getcontenttype> |
paulb@416 | 103 | <D:getcontentlength>%s</D:getcontentlength> |
paulb@380 | 104 | </D:prop> |
paulb@380 | 105 | <D:status>HTTP/1.1 200 OK</D:status> |
paulb@380 | 106 | </D:propstat> |
paulb@380 | 107 | </D:response> |
paulb@416 | 108 | """ % (trans.get_path_without_query(), filename, created, filename, "text/calendar", size)) |
paulb@380 | 109 | |
paulb@380 | 110 | out.write(""" |
paulb@380 | 111 | </D:multistatus> |
paulb@380 | 112 | """) |
paulb@380 | 113 | |
paulb@380 | 114 | # Handle downloads. |
paulb@380 | 115 | |
paulb@380 | 116 | elif method == "GET": |
paulb@380 | 117 | trans.set_content_type(WebStack.Generic.ContentType("text/html")) |
paulb@380 | 118 | out = trans.get_response_stream() |
paulb@416 | 119 | f = open(os.path.join(self.resource_dir, calendar_name)) |
paulb@416 | 120 | out.write(f.read()) |
paulb@416 | 121 | f.close() |
paulb@416 | 122 | |
paulb@416 | 123 | # Handle deletion. |
paulb@416 | 124 | |
paulb@416 | 125 | elif method == "DELETE": |
paulb@416 | 126 | try: |
paulb@416 | 127 | os.remove(os.path.join(self.resource_dir, calendar_name)) |
paulb@416 | 128 | except OSError: |
paulb@416 | 129 | trans.set_response_code(500) |
paulb@416 | 130 | |
paulb@416 | 131 | # Handle renaming. |
paulb@416 | 132 | |
paulb@416 | 133 | elif method in ("MOVE", "COPY"): |
paulb@416 | 134 | destinations = trans.get_header_values("Destination") |
paulb@416 | 135 | if len(destinations) != 1: |
paulb@416 | 136 | trans.set_response_code(500) |
paulb@416 | 137 | else: |
paulb@416 | 138 | try: |
paulb@416 | 139 | # Convert the URL into a filename. |
paulb@416 | 140 | # NOTE: Assume that the URL references the same "directory". |
paulb@416 | 141 | |
paulb@416 | 142 | destination = destinations[0].split("/")[-1] |
paulb@416 | 143 | |
paulb@416 | 144 | if method == "MOVE": |
paulb@416 | 145 | os.rename(os.path.join(self.resource_dir, calendar_name), os.path.join(self.resource_dir, destination)) |
paulb@416 | 146 | elif method == "COPY": |
paulb@416 | 147 | f_old = open(os.path.join(self.resource_dir, calendar_name), "rb") |
paulb@416 | 148 | f_new = open(os.path.join(self.resource_dir, destination), "wb") |
paulb@416 | 149 | f_new.write(f_old.read()) |
paulb@416 | 150 | f_new.close() |
paulb@416 | 151 | f_old.close() |
paulb@416 | 152 | |
paulb@416 | 153 | # NOTE: We do not observe the rules regarding overwriting |
paulb@416 | 154 | # NOTE: and the appropriate status codes. |
paulb@416 | 155 | |
paulb@416 | 156 | trans.set_header_value("Location", destinations[0]) |
paulb@416 | 157 | trans.set_response_code(201) |
paulb@416 | 158 | |
paulb@416 | 159 | except OSError: |
paulb@416 | 160 | trans.set_response_code(500) |
paulb@380 | 161 | |
paulb@380 | 162 | # Disallow other methods. |
paulb@380 | 163 | |
paulb@380 | 164 | else: |
paulb@380 | 165 | trans.set_response_code(405) |
paulb@22 | 166 | |
paulb@22 | 167 | # vim: tabstop=4 expandtab shiftwidth=4 |