def __init__(self, config={}, config_path=None): Application.__init__(self, template_path=template_path, autoescape=None) EventTarget.__init__(self) self.logger = getLogger('wall') self.bricks = {} self.post_types = {} self.clients = [] self.current_post = None self._init = True self._setup_logger() config_paths = [os.path.join(res_path, 'default.cfg')] if config_path: config_paths.append(config_path) try: parser = SafeConfigParser() parser.read(config_paths) except ConfigParserError as e: self.logger.error('failed to parse configuration file') self._init = False return self.config = {} for section in parser.sections(): prefix = section + '.' if section != 'wall' else '' for key, value in parser.items(section): self.config[prefix + key] = value self.config.update(config) self.db = ObjectRedis(StrictRedis(db=int(self.config['db'])), self._decode_redis_hash) self.posts = RedisContainer(self.db, 'posts') self.add_post_type(TextPost) self.add_post_type(ImagePost) self.msg_handlers = { 'post': self.post_msg, 'post_new': self.post_new_msg, 'get_history': self.get_history_msg } self.add_event_listener('posted', self._posted) # initialize bricks bricks = self.config['bricks'].split() for name in bricks: module = __import__(name, globals(), locals(), [b'foo']) brick = module.Brick(self) self.bricks[brick.id] = brick self.do_post_handlers = [] for handler in self.config['do_post_handlers'].split(): if handler not in ['note', 'history']: self.logger.warning('configuration: invalid item in do_post_handlers: "{}" unknown'.format(handler)); continue if handler in self.do_post_handlers: self.logger.warning('configuration: invalid item in do_post_handlers: "{}" non-unique'.format(handler)) continue self.do_post_handlers.append(handler) if self.config['debug'] == 'True': self.settings['debug'] = True tornado.autoreload.watch(os.path.join(res_path, 'default.cfg')) tornado.autoreload.start() # setup URL handlers urls = [ ('/$', ClientPage), ('/display$', DisplayPage), ('/display/post$', DisplayPostPage), ('/api/socket$', Socket), ] for brick in self.bricks.values(): urls.append(('/static/{0}/(.+)$'.format(brick.id), StaticFileHandler, {'path': brick.static_path})) urls.append(('/static/(.+)$', StaticFileHandler, {'path': static_path})) self.add_handlers('.*$', urls)
class WallApp(Application, EventTarget): def __init__(self, config={}, config_path=None): Application.__init__(self, template_path=template_path, autoescape=None) EventTarget.__init__(self) self.logger = getLogger('wall') self.bricks = {} self.post_types = {} self.clients = [] self.current_post = None self._init = True self._setup_logger() config_paths = [os.path.join(res_path, 'default.cfg')] if config_path: config_paths.append(config_path) try: parser = SafeConfigParser() parser.read(config_paths) except ConfigParserError as e: self.logger.error('failed to parse configuration file') self._init = False return self.config = {} for section in parser.sections(): prefix = section + '.' if section != 'wall' else '' for key, value in parser.items(section): self.config[prefix + key] = value self.config.update(config) self.db = ObjectRedis(StrictRedis(db=int(self.config['db'])), self._decode_redis_hash) self.posts = RedisContainer(self.db, 'posts') self.add_post_type(TextPost) self.add_post_type(ImagePost) self.msg_handlers = { 'post': self.post_msg, 'post_new': self.post_new_msg, 'get_history': self.get_history_msg } self.add_event_listener('posted', self._posted) # initialize bricks bricks = self.config['bricks'].split() for name in bricks: module = __import__(name, globals(), locals(), [b'foo']) brick = module.Brick(self) self.bricks[brick.id] = brick self.do_post_handlers = [] for handler in self.config['do_post_handlers'].split(): if handler not in ['note', 'history']: self.logger.warning('configuration: invalid item in do_post_handlers: "{}" unknown'.format(handler)); continue if handler in self.do_post_handlers: self.logger.warning('configuration: invalid item in do_post_handlers: "{}" non-unique'.format(handler)) continue self.do_post_handlers.append(handler) if self.config['debug'] == 'True': self.settings['debug'] = True tornado.autoreload.watch(os.path.join(res_path, 'default.cfg')) tornado.autoreload.start() # setup URL handlers urls = [ ('/$', ClientPage), ('/display$', DisplayPage), ('/display/post$', DisplayPostPage), ('/api/socket$', Socket), ] for brick in self.bricks.values(): urls.append(('/static/{0}/(.+)$'.format(brick.id), StaticFileHandler, {'path': brick.static_path})) urls.append(('/static/(.+)$', StaticFileHandler, {'path': static_path})) self.add_handlers('.*$', urls) @property def js_modules(self): return [b.js_module for b in self.bricks.values()] @property def scripts(self): scripts = [] for brick in self.bricks.values(): scripts.extend(brick.id + '/' + s for s in brick.scripts) return scripts @property def stylesheets(self): stylesheets = [] for brick in self.bricks.values(): stylesheets.extend(brick.id + '/' + s for s in brick.stylesheets) return stylesheets def run(self): if not self._init: return self.listen(8080) self.logger.info('server started') IOLoop.instance().start() def add_message_handler(self, type, handler): """ Extension API: register a new message `handler` for messages of the given `type`. A message handler is a function `handle(msg)` that processes a received message. It may return a `Message`, which is sent back to the sender as response. If a (subclass of) `Error` is raised, it is converted to a `Message` and sent back to the sender as error response. """ self.msg_handlers[type] = handler def sendall(self, msg): for client in self.clients: client.send(msg) def post_msg(self, msg): # TODO: error handling post = self.post(msg.data['id']) return Message('post', post.json()) def post_new_msg(self, msg): # wake display Popen('DISPLAY=:0.0 xset dpms force on', shell=True) post_type = msg.data.pop('type') post = self.post_new(post_type, **msg.data) return Message('post_new', post.json()) def get_history_msg(self, msg): return Message('get_history', [p.json('common') for p in self.get_history()]) def post(self, id): try: post = self.posts[id] except KeyError: raise ValueError('id_nonexistent') if self.current_post: self.current_post.deactivate() self.current_post = post post.posted = datetime.utcnow().isoformat() self.db.hset(post.id, 'posted', post.posted) post.activate() self.dispatch_event(Event('posted', post=post)) return post def post_new(self, type, **args): try: post_type = self.post_types[type] except KeyError: raise ValueError('type_nonexistent') post = post_type.create(self, **args) self.db.sadd('posts', post.id) return self.post(post.id) def get_history(self): return sorted(self.posts.values(), key=lambda p: p.posted, reverse=True) def add_post_type(self, post_type): """ Extension API: register a new post type. `post_type` is a class (type) that extends `Post`. """ self.post_types[post_type.__name__] = post_type def _decode_redis_hash(self, hash): post_type = self.post_types[hash['__type__']] return post_type(self, **hash) def _setup_logger(self): logger = getLogger() logger.setLevel(DEBUG) handler = StreamHandler() handler.setFormatter( Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s')) logger.addHandler(handler) def _posted(self, event): self.sendall(Message('posted', {'post': event.args['post'].json()}))