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