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