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