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)
def __init__(self, config={}, config_path=None): super(WallApp, self).__init__('wall', self) EventTarget.__init__(self) Collection.__init__(self) Application.__init__(self, template_path=template_path, autoescape=None) 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.add_post_type(GridPost) self.msg_handlers = { 'get_history': self.get_history_msg, 'collection_get_items': self.collection_get_items_msg, 'collection_post': self.collection_post_msg, 'collection_post_new': self.collection_post_new_msg, 'collection_remove_item': self.collection_remove_item_msg } self.add_event_listener('collection_posted', self._collection_posted) self.add_event_listener('collection_item_removed', self._collection_item_removed) self.add_event_listener('collection_item_activated', self._collection_item_activated) self.add_event_listener('collection_item_deactivated', self._collection_item_deactivated) # 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 if self.config['debug'] == 'True': self.settings['debug'] = True self.settings['autoreload'] = True self.settings['compiled_template_cache'] = False self.settings['static_hash_cache'] = False self.settings['serve_traceback'] = 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/bricks/{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()}))
class WallApp(Object, EventTarget, Collection, Application): """ Wall application. Events: * `connected` * `disconnected` """ def __init__(self, config={}, config_path=None): super(WallApp, self).__init__('wall', self) EventTarget.__init__(self) Collection.__init__(self) Application.__init__(self, template_path=template_path, autoescape=None) 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.add_post_type(GridPost) self.msg_handlers = { 'get_history': self.get_history_msg, 'collection_get_items': self.collection_get_items_msg, 'collection_post': self.collection_post_msg, 'collection_post_new': self.collection_post_new_msg, 'collection_remove_item': self.collection_remove_item_msg } self.add_event_listener('collection_posted', self._collection_posted) self.add_event_listener('collection_item_removed', self._collection_item_removed) self.add_event_listener('collection_item_activated', self._collection_item_activated) self.add_event_listener('collection_item_deactivated', self._collection_item_deactivated) # 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 if self.config['debug'] == 'True': self.settings['debug'] = True self.settings['autoreload'] = True self.settings['compiled_template_cache'] = False self.settings['static_hash_cache'] = False self.settings['serve_traceback'] = 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/bricks/{0}/(.+)$'.format(brick.id), StaticFileHandler, {'path': brick.static_path})) urls.append(('/static/(.+)$', StaticFileHandler, {'path': static_path})) self.add_handlers('.*$', urls) @property def items(self): return [self.current_post] if self.current_post else [] 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 get_item(self, index): if self.current_post and index == 0: return self.current_post else: raise ValueError('index_out_of_range') def do_post(self, post): if self.current_post: self.remove_item(0) self.current_post = post self.activate_item(0) def do_remove_item(self, index): self.deactivate_item(index) post = self.current_post self.current_post = None return post def get_collection(self, id): if id == 'wall': return self else: post = self.posts.get(id) # also captures None if not isinstance(post, Collection): raise KeyError() return post # TODO: validate input in message handlers def get_history_msg(self, msg): return Message('get_history', [p.json('common') for p in self.get_history()]) def collection_get_items_msg(self, msg): collection = self.get_collection(msg.data['collection_id']) return Message('collection_get_items', [p.json() for p in collection.items]) def collection_post_msg(self, msg): collection = self.get_collection(msg.data['collection_id']) post = self.posts[msg.data['post_id']] collection.post(post) return Message('collection_post') def collection_post_new_msg(self, msg): # wake display Popen('DISPLAY=:0.0 xset dpms force on', shell=True) collection = self.get_collection(msg.data.pop('collection_id')) post_type = msg.data.pop('type') post = collection.post_new(post_type, **msg.data) return Message('collection_post_new', post.json()) def collection_remove_item_msg(self, msg): collection = self.get_collection(msg.data['collection_id']) index = int(msg.data['index']) post = collection.remove_item(index) return Message('collection_remove_item', post.json()) 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 _collection_posted(self, event): self.sendall(Message('collection_posted', { 'collection_id': event.args['collection'].id, 'post': event.args['post'].json() })) def _collection_item_removed(self, event): self.sendall(Message('collection_item_removed', { 'collection_id': event.args['collection'].id, 'index': event.args['index'], 'post': event.args['post'].json() })) def _collection_item_activated(self, event): self.sendall(Message('collection_item_activated', { 'collection_id': event.args['collection'].id, 'index': event.args['index'], 'post': event.args['post'].json() })) def _collection_item_deactivated(self, event): self.sendall(Message('collection_item_deactivated', { 'collection_id': event.args['collection'].id, 'index': event.args['index'], 'post': event.args['post'].json() })) def __str__(self): return '<{}>'.format(self.__class__.__name__) __repr__ = __str__