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