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