blob: 253b15e5d03503e18a18d436713ba953be5931d3 [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/env python
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6
7'''
8This module provides both client and server side of a XML RPC based server which
9can be used to handle factory test states (status) and shared persistent data.
10'''
11
12
Jon Salz758e6cc2012-04-03 15:47:07 +080013import logging
Jon Salz258a40c2012-04-19 12:34:01 +080014import mimetypes
Jon Salz758e6cc2012-04-03 15:47:07 +080015import os
Jon Salz5eee01c2012-06-04 16:07:33 +080016import Queue
17import re
Jon Salz758e6cc2012-04-03 15:47:07 +080018import shelve
19import shutil
Jon Salz258a40c2012-04-19 12:34:01 +080020import SocketServer
Jon Salz758e6cc2012-04-03 15:47:07 +080021import sys
22import threading
Jon Salz258a40c2012-04-19 12:34:01 +080023import time
24
25from hashlib import sha1
Jon Salz5eee01c2012-06-04 16:07:33 +080026from uuid import uuid4
Hung-Te Linf2f78f72012-02-08 19:27:11 +080027
Jon Salz0697cbf2012-07-04 15:14:04 +080028import factory_common # pylint: disable=W0611
Jon Salz258a40c2012-04-19 12:34:01 +080029
30from jsonrpclib import jsonclass
31from jsonrpclib import jsonrpc
32from jsonrpclib import SimpleJSONRPCServer
Jon Salz83591782012-06-26 11:09:58 +080033from cros.factory.test import factory
34from cros.factory.test.factory import TestState
35from cros.factory.goofy import system
36from cros.factory.test import unicode_to_string
Hung-Te Linf2f78f72012-02-08 19:27:11 +080037
Jon Salz49a7d152012-06-19 15:04:09 +080038
Hung-Te Linf2f78f72012-02-08 19:27:11 +080039DEFAULT_FACTORY_STATE_PORT = 0x0FAC
40DEFAULT_FACTORY_STATE_ADDRESS = 'localhost'
41DEFAULT_FACTORY_STATE_BIND_ADDRESS = 'localhost'
Jon Salz8796e362012-05-24 11:39:09 +080042DEFAULT_FACTORY_STATE_FILE_PATH = factory.get_state_root()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080043
44
45def _synchronized(f):
Jon Salz0697cbf2012-07-04 15:14:04 +080046 '''
47 Decorates a function to grab a lock.
48 '''
49 def wrapped(self, *args, **kw):
50 with self._lock: # pylint: disable=W0212
51 return f(self, *args, **kw)
52 return wrapped
Hung-Te Linf2f78f72012-02-08 19:27:11 +080053
54
Jon Salz758e6cc2012-04-03 15:47:07 +080055def clear_state(state_file_path=None):
Jon Salz0697cbf2012-07-04 15:14:04 +080056 '''Clears test state (removes the state file path).
Jon Salz758e6cc2012-04-03 15:47:07 +080057
Jon Salz0697cbf2012-07-04 15:14:04 +080058 Args:
59 state_file_path: Path to state; uses the default path if None.
60 '''
61 state_file_path = state_file_path or DEFAULT_FACTORY_STATE_FILE_PATH
62 logging.warn('Clearing state file path %s' % state_file_path)
63 if os.path.exists(state_file_path):
64 shutil.rmtree(state_file_path)
Jon Salz758e6cc2012-04-03 15:47:07 +080065
66
Jon Salz258a40c2012-04-19 12:34:01 +080067class TestHistoryItem(object):
Jon Salz0697cbf2012-07-04 15:14:04 +080068 def __init__(self, path, state, log, trace=None):
69 self.path = path
70 self.state = state
71 self.log = log
72 self.trace = trace
73 self.time = time.time()
Jon Salz258a40c2012-04-19 12:34:01 +080074
75
Jon Salzaeb4fd42012-06-05 15:08:30 +080076class PathResolver(object):
Jon Salz0697cbf2012-07-04 15:14:04 +080077 '''Resolves paths in URLs.'''
78 def __init__(self):
79 self._paths = {}
Jon Salzaeb4fd42012-06-05 15:08:30 +080080
Jon Salz0697cbf2012-07-04 15:14:04 +080081 def AddPath(self, url_path, local_path):
82 '''Adds a prefix mapping:
Jon Salzaeb4fd42012-06-05 15:08:30 +080083
Jon Salz0697cbf2012-07-04 15:14:04 +080084 For example,
Jon Salzaeb4fd42012-06-05 15:08:30 +080085
Jon Salz0697cbf2012-07-04 15:14:04 +080086 AddPath('/foo', '/usr/local/docs')
Jon Salzaeb4fd42012-06-05 15:08:30 +080087
Jon Salz0697cbf2012-07-04 15:14:04 +080088 will cause paths to resolved as follows:
Jon Salzaeb4fd42012-06-05 15:08:30 +080089
Jon Salz0697cbf2012-07-04 15:14:04 +080090 /foo -> /usr/local/docs
91 /foo/index.html -> /usr/local/docs/index.html
Jon Salzaeb4fd42012-06-05 15:08:30 +080092
Jon Salz0697cbf2012-07-04 15:14:04 +080093 Args:
94 url_path: The path in the URL
95 '''
96 self._paths[url_path] = local_path
Jon Salzaeb4fd42012-06-05 15:08:30 +080097
Jon Salz0697cbf2012-07-04 15:14:04 +080098 def Resolve(self, url_path):
99 '''Resolves a path mapping.
Jon Salzaeb4fd42012-06-05 15:08:30 +0800100
Jon Salz0697cbf2012-07-04 15:14:04 +0800101 Returns None if no paths match.'
Jon Salzaeb4fd42012-06-05 15:08:30 +0800102
Jon Salz0697cbf2012-07-04 15:14:04 +0800103 Args:
104 url_path: A path in a URL (starting with /).
105 '''
106 if not url_path.startswith('/'):
107 return None
Jon Salzaeb4fd42012-06-05 15:08:30 +0800108
Jon Salz0697cbf2012-07-04 15:14:04 +0800109 prefix = url_path
110 while prefix != '':
111 local_prefix = self._paths.get(prefix)
112 if local_prefix:
113 return local_prefix + url_path[len(prefix):]
114 prefix, _, _ = prefix.rpartition('/')
Jon Salzaeb4fd42012-06-05 15:08:30 +0800115
Jon Salz0697cbf2012-07-04 15:14:04 +0800116 root_prefix = self._paths.get('/')
117 if root_prefix:
118 return root_prefix + url_path
Jon Salzaeb4fd42012-06-05 15:08:30 +0800119
120
Jon Salz258a40c2012-04-19 12:34:01 +0800121@unicode_to_string.UnicodeToStringClass
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800122class FactoryState(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800123 '''
124 The core implementation for factory state control.
125 The major provided features are:
126
127 SHARED DATA
128 You can get/set simple data into the states and share between all tests.
129 See get_shared_data(name) and set_shared_data(name, value) for more
130 information.
131
132 TEST STATUS
133 To track the execution status of factory auto tests, you can use
134 get_test_state, get_test_states methods, and update_test_state
135 methods.
136
137 All arguments may be provided either as strings, or as Unicode strings in
138 which case they are converted to strings using UTF-8. All returned values
139 are strings (not Unicode).
140
141 This object is thread-safe.
142
143 See help(FactoryState.[methodname]) for more information.
144
145 Properties:
146 _generated_files: Map from UUID to paths on disk. These are
147 not persisted on disk (though they could be if necessary).
148 _generated_data: Map from UUID to (mime_type, data) pairs for
149 transient objects to serve.
150 _generated_data_expiration: Priority queue of expiration times
151 for objects in _generated_data.
152 '''
153
154 def __init__(self, state_file_path=None):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800155 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800156 Initializes the state server.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800157
Jon Salz0697cbf2012-07-04 15:14:04 +0800158 Parameters:
159 state_file_path: External file to store the state information.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800160 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800161 state_file_path = state_file_path or DEFAULT_FACTORY_STATE_FILE_PATH
162 if not os.path.exists(state_file_path):
163 os.makedirs(state_file_path)
164 self._tests_shelf = shelve.open(state_file_path + '/tests')
165 self._data_shelf = shelve.open(state_file_path + '/data')
166 self._test_history_shelf = shelve.open(state_file_path +
167 '/test_history')
168 self._lock = threading.RLock()
169 self.test_list_struct = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800170
Jon Salz0697cbf2012-07-04 15:14:04 +0800171 self._generated_files = {}
172 self._generated_data = {}
173 self._generated_data_expiration = Queue.PriorityQueue()
174 self._resolver = PathResolver()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800175
Jon Salz0697cbf2012-07-04 15:14:04 +0800176 if TestState not in jsonclass.supported_types:
177 jsonclass.supported_types.append(TestState)
Jon Salz258a40c2012-04-19 12:34:01 +0800178
Jon Salz0697cbf2012-07-04 15:14:04 +0800179 @_synchronized
180 def close(self):
181 '''
182 Shuts down the state instance.
183 '''
184 for shelf in [self._tests_shelf,
185 self._data_shelf,
186 self._test_history_shelf]:
187 try:
188 shelf.close()
189 except:
190 logging.exception('Unable to close shelf')
Jon Salz5eee01c2012-06-04 16:07:33 +0800191
Jon Salz0697cbf2012-07-04 15:14:04 +0800192 @_synchronized
193 def update_test_state(self, path, **kw):
194 '''
195 Updates the state of a test.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800196
Jon Salz0697cbf2012-07-04 15:14:04 +0800197 See TestState.update for the allowable keyword arguments.
Jon Salz66f65e62012-05-24 17:40:26 +0800198
Jon Salz0697cbf2012-07-04 15:14:04 +0800199 @param path: The path to the test (see FactoryTest for a description
200 of test paths).
201 @param kw: See TestState.update for allowable arguments (e.g.,
202 status and increment_count).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800203
Jon Salz0697cbf2012-07-04 15:14:04 +0800204 @return: A tuple containing the new state, and a boolean indicating
205 whether the state was just changed.
206 '''
207 state = self._tests_shelf.get(path)
208 old_state_repr = repr(state)
209 changed = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800210
Jon Salz0697cbf2012-07-04 15:14:04 +0800211 if not state:
212 changed = True
213 state = TestState()
Jon Salz20d8e932012-03-17 15:04:23 +0800214
Jon Salz0697cbf2012-07-04 15:14:04 +0800215 changed = changed | state.update(**kw) # Don't short-circuit
Jon Salz20d8e932012-03-17 15:04:23 +0800216
Jon Salz0697cbf2012-07-04 15:14:04 +0800217 if changed:
218 logging.debug('Updating test state for %s: %s -> %s',
219 path, old_state_repr, state)
220 self._tests_shelf[path] = state
221 self._tests_shelf.sync()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800222
Jon Salz0697cbf2012-07-04 15:14:04 +0800223 return state, changed
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800224
Jon Salz0697cbf2012-07-04 15:14:04 +0800225 @_synchronized
226 def get_test_state(self, path):
227 '''
228 Returns the state of a test.
229 '''
230 return self._tests_shelf[path]
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800231
Jon Salz0697cbf2012-07-04 15:14:04 +0800232 @_synchronized
233 def get_test_paths(self):
234 '''
235 Returns a list of all tests' paths.
236 '''
237 return self._tests_shelf.keys()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800238
Jon Salz0697cbf2012-07-04 15:14:04 +0800239 @_synchronized
240 def get_test_states(self):
241 '''
242 Returns a map of each test's path to its state.
243 '''
244 return dict(self._tests_shelf)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800245
Jon Salz0697cbf2012-07-04 15:14:04 +0800246 def get_test_list(self):
247 '''
248 Returns the test list.
249 '''
250 return self.test_list.to_struct()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800251
Jon Salz0697cbf2012-07-04 15:14:04 +0800252 @_synchronized
253 def set_shared_data(self, *key_value_pairs):
254 '''
255 Sets shared data items.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800256
Jon Salz0697cbf2012-07-04 15:14:04 +0800257 Args:
258 key_value_pairs: A series of alternating keys and values
259 (k1, v1, k2, v2...). In the simple case this can just
260 be a single key and value.
261 '''
262 assert len(key_value_pairs) % 2 == 0, repr(key_value_pairs)
263 for i in range(0, len(key_value_pairs), 2):
264 self._data_shelf[key_value_pairs[i]] = key_value_pairs[i + 1]
265 self._data_shelf.sync()
Jon Salz258a40c2012-04-19 12:34:01 +0800266
Jon Salz0697cbf2012-07-04 15:14:04 +0800267 @_synchronized
268 def get_shared_data(self, key, optional=False):
269 '''
270 Retrieves a shared data item.
Jon Salzb1b39092012-05-03 02:05:09 +0800271
Jon Salz0697cbf2012-07-04 15:14:04 +0800272 Args:
273 key: The key whose value to retrieve.
274 optional: True to return None if not found; False to raise
275 a KeyError.
276 '''
277 if optional:
278 return self._data_shelf.get(key)
279 else:
280 return self._data_shelf[key]
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800281
Jon Salz0697cbf2012-07-04 15:14:04 +0800282 @_synchronized
283 def has_shared_data(self, key):
284 '''
285 Returns if a shared data item exists.
286 '''
287 return key in self._data_shelf
Jon Salz4f6c7172012-06-11 20:45:36 +0800288
Jon Salz0697cbf2012-07-04 15:14:04 +0800289 @_synchronized
290 def del_shared_data(self, key, optional=False):
291 '''
292 Deletes a shared data item.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800293
Jon Salz0697cbf2012-07-04 15:14:04 +0800294 Args:
295 key: The key whose value to retrieve.
296 optional: False to raise a KeyError if not found.
297 '''
298 try:
299 del self._data_shelf[key]
300 except KeyError:
301 if not optional:
302 raise
Hung-Te Lin163f7512012-02-17 18:58:57 +0800303
Jon Salz0697cbf2012-07-04 15:14:04 +0800304 @_synchronized
305 def add_test_history(self, history_item):
306 path = history_item.path
307 assert path
Jon Salz4f6c7172012-06-11 20:45:36 +0800308
Jon Salz0697cbf2012-07-04 15:14:04 +0800309 length_key = path + '[length]'
310 num_entries = self._test_history_shelf.get(length_key, 0)
311 self._test_history_shelf[path + '[%d]' % num_entries] = history_item
312 self._test_history_shelf[length_key] = num_entries + 1
313
314 @_synchronized
315 def get_test_history(self, paths):
316 if type(paths) != list:
317 paths = [paths]
318 ret = []
319
320 for path in paths:
321 i = 0
322 while True:
323 value = self._test_history_shelf.get(path + '[%d]' % i)
324
325 i += 1
326 if not value:
327 break
328 ret.append(value)
329
330 ret.sort(key=lambda item: item.time)
331
332 return ret
333
334 @_synchronized
335 def url_for_file(self, path):
336 '''Returns a URL that can be used to serve a local file.
337
338 Args:
339 path: path to the local file
340
341 Returns:
342 url: A (possibly relative) URL that refers to the file
343 '''
344 uuid = str(uuid4())
345 uri_path = '/generated-files/%s/%s' % (uuid, os.path.basename(path))
346 self._generated_files[uuid] = path
347 return uri_path
348
349 @_synchronized
350 def url_for_data(self, mime_type, data, expiration_secs=None):
351 '''Returns a URL that can be used to serve a static collection
352 of bytes.
353
354 Args:
355 mime_type: MIME type for the data
356 data: Data to serve
357 expiration_secs: If not None, the number of seconds in which
358 the data will expire.
359 '''
360 uuid = str(uuid4())
361 self._generated_data[uuid] = mime_type, data
362 if expiration_secs:
363 now = time.time()
364 self._generated_data_expiration.put(
365 (now + expiration_secs, uuid))
366
367 # Reap old items.
368 while True:
Jon Salz4f6c7172012-06-11 20:45:36 +0800369 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800370 item = self._generated_data_expiration.get_nowait()
371 except Queue.Empty:
372 break
Hung-Te Lin163f7512012-02-17 18:58:57 +0800373
Jon Salz0697cbf2012-07-04 15:14:04 +0800374 if item[0] < now:
375 del self._generated_data[item[1]]
376 else:
377 # Not expired yet; put it back and we're done
378 self._generated_data_expiration.put(item)
379 break
380 uri_path = '/generated-data/%s' % uuid
381 return uri_path
Jon Salz258a40c2012-04-19 12:34:01 +0800382
Jon Salz0697cbf2012-07-04 15:14:04 +0800383 @_synchronized
384 def register_path(self, url_path, local_path):
385 self._resolver.AddPath(url_path, local_path)
Jon Salz258a40c2012-04-19 12:34:01 +0800386
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 def get_system_status(self):
388 '''Returns system status information.
Jon Salz258a40c2012-04-19 12:34:01 +0800389
Jon Salz0697cbf2012-07-04 15:14:04 +0800390 This may include system load, battery status, etc. See
391 system.SystemStatus().
392 '''
393 return system.SystemStatus().__dict__
Jon Salz49a7d152012-06-19 15:04:09 +0800394
Jon Salzaeb4fd42012-06-05 15:08:30 +0800395
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396def get_instance(address=DEFAULT_FACTORY_STATE_ADDRESS,
Jon Salz0697cbf2012-07-04 15:14:04 +0800397 port=DEFAULT_FACTORY_STATE_PORT):
398 '''
399 Gets an instance (for client side) to access the state server.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800400
Jon Salz0697cbf2012-07-04 15:14:04 +0800401 @param address: Address of the server to be connected.
402 @param port: Port of the server to be connected.
403 @return An object with all public functions from FactoryState.
404 See help(FactoryState) for more information.
405 '''
406 return jsonrpc.ServerProxy('http://%s:%d' % (address, port),
407 verbose=False)
Jon Salz258a40c2012-04-19 12:34:01 +0800408
409
410class MyJSONRPCRequestHandler(SimpleJSONRPCServer.SimpleJSONRPCRequestHandler):
Jon Salz0697cbf2012-07-04 15:14:04 +0800411 def do_GET(self):
412 logging.debug('HTTP request for path %s', self.path)
Jon Salz258a40c2012-04-19 12:34:01 +0800413
Jon Salz0697cbf2012-07-04 15:14:04 +0800414 handler = self.server.handlers.get(self.path)
415 if handler:
416 return handler(self)
Jon Salz258a40c2012-04-19 12:34:01 +0800417
Jon Salz0697cbf2012-07-04 15:14:04 +0800418 match = re.match('^/generated-data/([-0-9a-f]+)$', self.path)
419 if match:
420 generated_data = self.server._generated_data.get(match.group(1))
421 if not generated_data:
422 logging.warn('Unknown or expired generated data %s',
423 match.group(1))
424 self.send_response(404)
425 return
Jon Salz5eee01c2012-06-04 16:07:33 +0800426
Jon Salz0697cbf2012-07-04 15:14:04 +0800427 mime_type, data = generated_data
Jon Salz5eee01c2012-06-04 16:07:33 +0800428
Jon Salz0697cbf2012-07-04 15:14:04 +0800429 self.send_response(200)
430 self.send_header('Content-Type', mime_type)
431 self.send_header('Content-Length', len(data))
432 self.end_headers()
433 self.wfile.write(data)
Jon Salz5eee01c2012-06-04 16:07:33 +0800434
Jon Salz0697cbf2012-07-04 15:14:04 +0800435 if self.path.endswith('/'):
436 self.path += 'index.html'
Jon Salz258a40c2012-04-19 12:34:01 +0800437
Jon Salz0697cbf2012-07-04 15:14:04 +0800438 if ".." in self.path.split("/"):
439 logging.warn("Invalid path")
440 self.send_response(404)
441 return
Jon Salz258a40c2012-04-19 12:34:01 +0800442
Jon Salz0697cbf2012-07-04 15:14:04 +0800443 mime_type = mimetypes.guess_type(self.path)
444 if not mime_type:
445 logging.warn("Unable to guess MIME type")
446 self.send_response(404)
447 return
Jon Salz258a40c2012-04-19 12:34:01 +0800448
Jon Salz0697cbf2012-07-04 15:14:04 +0800449 local_path = None
450 match = re.match('^/generated-files/([-0-9a-f]+)/', self.path)
451 if match:
452 local_path = self.server._generated_files.get(match.group(1))
453 if not local_path:
454 logging.warn('Unknown generated file %s in path %s',
455 match.group(1), self.path)
456 self.send_response(404)
457 return
Jon Salz5eee01c2012-06-04 16:07:33 +0800458
Jon Salz0697cbf2012-07-04 15:14:04 +0800459 local_path = self.server._resolver.Resolve(self.path)
460 if not local_path or not os.path.exists(local_path):
461 logging.warn("File not found: %s", (local_path or self.path))
462 self.send_response(404)
463 return
Jon Salz258a40c2012-04-19 12:34:01 +0800464
Jon Salz0697cbf2012-07-04 15:14:04 +0800465 self.send_response(200)
466 self.send_header("Content-Type", mime_type[0])
467 self.send_header("Content-Length", os.path.getsize(local_path))
468 self.end_headers()
469 with open(local_path) as f:
470 shutil.copyfileobj(f, self.wfile)
Jon Salz258a40c2012-04-19 12:34:01 +0800471
472
473class ThreadedJSONRPCServer(SocketServer.ThreadingMixIn,
Jon Salz0697cbf2012-07-04 15:14:04 +0800474 SimpleJSONRPCServer.SimpleJSONRPCServer):
475 '''The JSON/RPC server.
Jon Salz258a40c2012-04-19 12:34:01 +0800476
Jon Salz0697cbf2012-07-04 15:14:04 +0800477 Properties:
478 handlers: A map from URLs to callbacks handling them. (The callback
479 takes a single argument: the request to handle.)
480 '''
481 def __init__(self, *args, **kwargs):
482 SimpleJSONRPCServer.SimpleJSONRPCServer.__init__(self, *args, **kwargs)
483 self.handlers = {}
Jon Salz258a40c2012-04-19 12:34:01 +0800484
Jon Salz0697cbf2012-07-04 15:14:04 +0800485 def add_handler(self, url, callback):
486 self.handlers[url] = callback
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800487
488
489def create_server(state_file_path=None, bind_address=None, port=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800490 '''
491 Creates a FactoryState object and an JSON/RPC server to serve it.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800492
Jon Salz0697cbf2012-07-04 15:14:04 +0800493 @param state_file_path: The path containing the saved state.
494 @param bind_address: Address to bind to, defaulting to
495 DEFAULT_FACTORY_STATE_BIND_ADDRESS.
496 @param port: Port to bind to, defaulting to DEFAULT_FACTORY_STATE_PORT.
497 @return A tuple of the FactoryState instance and the SimpleJSONRPCServer
498 instance.
499 '''
500 # We have some icons in SVG format, but this isn't recognized in
501 # the standard Python mimetypes set.
502 mimetypes.add_type('image/svg+xml', '.svg')
Jon Salz258a40c2012-04-19 12:34:01 +0800503
Jon Salz0697cbf2012-07-04 15:14:04 +0800504 if not bind_address:
505 bind_address = DEFAULT_FACTORY_STATE_BIND_ADDRESS
506 if not port:
507 port = DEFAULT_FACTORY_STATE_PORT
508 instance = FactoryState(state_file_path)
509 instance._resolver.AddPath(
510 '/',
511 os.path.join(factory.FACTORY_PACKAGE_PATH, 'goofy/static'))
Jon Salzaeb4fd42012-06-05 15:08:30 +0800512
Jon Salz0697cbf2012-07-04 15:14:04 +0800513 server = ThreadedJSONRPCServer(
514 (bind_address, port),
515 requestHandler=MyJSONRPCRequestHandler,
516 logRequests=False)
Jon Salz258a40c2012-04-19 12:34:01 +0800517
Jon Salz0697cbf2012-07-04 15:14:04 +0800518 # Give the server the information it needs to resolve URLs.
519 server._generated_files = instance._generated_files
520 server._generated_data = instance._generated_data
521 server._resolver = instance._resolver
Jon Salz5eee01c2012-06-04 16:07:33 +0800522
Jon Salz0697cbf2012-07-04 15:14:04 +0800523 server.register_introspection_functions()
524 server.register_instance(instance)
525 server.web_socket_handler = None
526 return instance, server