comparison dogdish/dispatcher.py @ 15:fc5b842de4e9

be a little less aggressive when caching
author Jeff Hammel <jhammel@mozilla.com>
date Wed, 17 Oct 2012 14:59:49 -0700
parents cffb6f681b59
children 31898fb3b7d9
comparison
equal deleted inserted replaced
14:cffb6f681b59 15:fc5b842de4e9
14 from webob import Response, exc 14 from webob import Response, exc
15 from ConfigParser import RawConfigParser as ConfigParser 15 from ConfigParser import RawConfigParser as ConfigParser
16 16
17 here = os.path.dirname(os.path.abspath(__file__)) 17 here = os.path.dirname(os.path.abspath(__file__))
18 18
19 ### models
19 20
20 class Application(object): 21 class Application(object):
21 """class for storing application.ini data""" 22 """class for storing application.ini data"""
22 23
23 def __init__(self, filename): 24 def __init__(self, filename):
26 """ 27 """
27 config = ConfigParser() 28 config = ConfigParser()
28 config.read(filename) 29 config.read(filename)
29 self.build_id = config.get('App', 'BuildID') 30 self.build_id = config.get('App', 'BuildID')
30 self.version = config.get('App', 'Version') 31 self.version = config.get('App', 'Version')
32
33
34 class Update(object):
35 """class representing a .mar update file"""
36
37 prefix = 'b2g_update_'
38 suffix = '.mar'
39
40 @classmethod
41 def updates(cls, directory):
42 """returns the updates in a directory"""
43
44 contents = [i for i in os.listdir(directory)
45 if i.startswith(cls.prefix) and i.endswith(cls.suffix)]
46 contents = set(contents)
47 return contents
48
49 def __init__(self, directory, filename):
50 self.directory = directory
51 self.filename = filename
52 self.path = os.path.join(directory, filename)
53 self.stamp = filename[len(self.prefix):-len(self.suffix)]
54 self.size = os.path.getsize(self.path)
55
56 # cached properties
57 self._application = None
58 self._hash = None
59
60 def application(self):
61 """
62 returns the path to the application.ini
63 associated with this update
64 """
65
66 if not self._application:
67 application_ini = 'application_%s.ini' % self.stamp
68 application_ini = os.path.join(self.directory, application_ini)
69 assert os.path.exists(application_ini)
70 self._application = Application(application_ini)
71 return self._application
72
73 def hash(self):
74 if not self._hash:
75 # compute the hash
76 with file(self.path) as f:
77 self._hash = hashlib.sha512(f.read()).hexdigest()
78 return self._hash
31 79
32 ### request handlers 80 ### request handlers
33 81
34 class Handler(object): 82 class Handler(object):
35 """abstract handler object for a request""" 83 """abstract handler object for a request"""
70 def match(cls, request): 118 def match(cls, request):
71 return request.method == 'GET' 119 return request.method == 'GET'
72 120
73 def __call__(self): 121 def __call__(self):
74 122
75 update_path = os.path.join(self.app.directory, self.app.current_update)
76 body = self.body 123 body = self.body
77 query = {} 124 query = {}
78 dogfood_id = self.request.GET.get('dogfood_id') 125 dogfood_id = self.request.GET.get('dogfood_id')
79 if dogfood_id: 126 if dogfood_id:
80 query['dogfooding_prerelease_id'] = dogfood_id 127 query['dogfooding_prerelease_id'] = dogfood_id
81 application = self.app.updates[self.app.current_update] 128 current_update = self.app.current_update
129 application = current_update.application()
82 query['build_id'] = application.build_id 130 query['build_id'] = application.build_id
83 query['version'] = application.version 131 query['version'] = application.version
84 132
85 # build query string 133 # build query string
86 if query: 134 if query:
87 query = '?' + '&amp;'.join(['%s=%s' % (key, value) for key, value in query.items()]) 135 query = '?' + '&amp;'.join(['%s=%s' % (key, value) for key, value in query.items()])
88 else: 136 else:
89 query = '' 137 query = ''
90 138
91 # compute the hash
92 with file(update_path) as f:
93 sha512 = hashlib.sha512(f.read()).hexdigest()
94 139
95 # template variables 140 # template variables
96 variables = dict(update=self.app.current_update, 141 variables = dict(update=current_update.filename,
97 size=os.path.getsize(update_path), 142 size=current_update.size,
98 hash=sha512, 143 hash=current_update.hash(),
99 query=query) 144 query=query)
100 145
101 return Response(content_type='text/xml', 146 return Response(content_type='text/xml',
102 body=body % variables) 147 body=body % variables)
103 148
116 self.handlers = [ Get ] 161 self.handlers = [ Get ]
117 162
118 # cache 163 # cache
119 self.updates = {} 164 self.updates = {}
120 self.current_update = None 165 self.current_update = None
121 self.current_stamp = '0000-00-00_000000'
122 self.cached_response = None
123 166
124 # scan directory 167 # scan directory
125 self.scan() 168 self.scan()
126 assert self.current_update # ensure there is at least one update 169 assert self.current_update # ensure there is at least one update
127 170
128 def __call__(self, environ, start_response): 171 def __call__(self, environ, start_response):
129 """WSGI application""" 172 """WSGI application"""
130 173
131 request = Request(environ) 174 request = Request(environ)
132 new = self.scan() 175 new = self.scan()
133 if (not new) and (self.cached_response is not None):
134 return self.cached_response(environ, start_response)
135 for h in self.handlers: 176 for h in self.handlers:
136 if h.match(request): 177 if h.match(request):
137 handler = h(self, request) 178 handler = h(self, request)
138 res = handler() 179 res = handler()
139 self.cached_response = res
140 break 180 break
141 else: 181 else:
142 res = exc.HTTPNotFound() 182 res = exc.HTTPNotFound()
143 183
144 return res(environ, start_response) 184 return res(environ, start_response)
146 def scan(self): 186 def scan(self):
147 """ 187 """
148 scan the directory for updates 188 scan the directory for updates
149 returns True if new updates are found, False otherwise 189 returns True if new updates are found, False otherwise
150 """ 190 """
151 prefix = 'b2g_update_' 191
152 suffix = '.mar' 192 # check for new updates
153 contents = [i for i in os.listdir(self.directory) 193 contents = Update.updates(self.directory)
154 if i.startswith(prefix) and i.endswith(suffix)]
155 contents = set(contents)
156 new = contents.difference(self.updates.keys()) 194 new = contents.difference(self.updates.keys())
157 if not new: 195 if not new:
158 # directory contents unchanged from cached values 196 # directory contents unchanged from cached values
159 return False 197 return False
160 198
161 for update in new: 199 for update in new:
162 stamp = update[len(prefix):-len(suffix)] 200 self.updates[update] = Update(self.directory, update)
163 application_ini = 'application_%s.ini' % stamp 201 if self.current_update:
164 application_ini = os.path.join(self.directory, application_ini) 202 if self.updates[update].stamp > self.current_update.stamp:
165 assert os.path.exists(application_ini) 203 self.current_update = self.updates[update]
166 self.updates[update] = Application(application_ini) 204 else:
167 if stamp > self.current_stamp: 205 self.current_update = self.updates[update]
168 self.current_update = update
169 self.current_stamp = stamp
170 206
171 # TODO: could remove old files from the cache if not found in contents 207 # TODO: could remove old files from the cache if not found in contents
172 208
173 return True 209 return True
174 210