diff options
Diffstat (limited to 'olpcfr')
-rw-r--r-- | olpcfr/__init__.py | 3 | ||||
-rw-r--r-- | olpcfr/_server.py | 26 | ||||
-rw-r--r-- | olpcfr/config.py | 111 | ||||
-rw-r--r-- | olpcfr/flask/__init__.py | 8 | ||||
-rw-r--r-- | olpcfr/flask/_app.py | 60 | ||||
-rw-r--r-- | olpcfr/semanticxo/__init__.py | 1 | ||||
-rw-r--r-- | olpcfr/semanticxo/_runner.py | 45 | ||||
-rw-r--r-- | olpcfr/tools/__init__.py | 2 | ||||
-rw-r--r-- | olpcfr/tools/_logger.py | 10 | ||||
-rw-r--r-- | olpcfr/tools/_obj.py | 9 | ||||
-rw-r--r-- | olpcfr/tools/image.py | 110 | ||||
-rw-r--r-- | olpcfr/tools/registry.py | 48 | ||||
-rw-r--r-- | olpcfr/tools/sound.py | 111 | ||||
-rw-r--r-- | olpcfr/tools/storage.py | 150 |
14 files changed, 694 insertions, 0 deletions
diff --git a/olpcfr/__init__.py b/olpcfr/__init__.py new file mode 100644 index 0000000..0274fab --- /dev/null +++ b/olpcfr/__init__.py @@ -0,0 +1,3 @@ +# olpcfr import +from olpcfr._server import Server +from olpcfr.flask import BUNDLE, ROOT diff --git a/olpcfr/_server.py b/olpcfr/_server.py new file mode 100644 index 0000000..8b79403 --- /dev/null +++ b/olpcfr/_server.py @@ -0,0 +1,26 @@ +# python import +import atexit, multiprocessing + +# olpcfr import +from olpcfr import config + + +class Server(object): + + def __init__(self): + from olpcfr.flask import app, run_app + if app: + # start the server + self._server = multiprocessing.Process(target=run_app) + self._server.start() + # .. required to close properly + atexit.register(self.close) + else: + # .. nothing to start + self._server = None + + def close(self): + # stop web thread if started + if self._server: + self._server.terminate() + self._server.join() diff --git a/olpcfr/config.py b/olpcfr/config.py new file mode 100644 index 0000000..9180e54 --- /dev/null +++ b/olpcfr/config.py @@ -0,0 +1,111 @@ + +# python import +import logging +from ConfigParser import SafeConfigParser + +# get application logger +logger = logging.getLogger('atoideweb') + + +class Config(object): + + class __Singleton: + """Our singleton object. + """ + + def __init__(self, config=None): + """Create the new singleton with the application config. + + :param config: SafeConfigParser object for all the application + :see: `ConfigParser.SafeConfigParser` + """ + # ensure config + if config is None: + # ... + self.__config = SafeConfigParser() + # ... + self.__config.read('config.ini') + # ... + else: + self.__config = config + + def set(self, path, value, type_=str): + # set value + self.set_value(*path.split(">"), value=value, type_=type_) + + def get(self, path, type_=str): + """A little jQuery like shortcut for the `get_value` method. + + :param path: something like "section>option" + :param type_: type of the expected value. Default: str + :return: expected value in the expected type or None + :rtype: `object` + """ + # get value + _value = self.get_value(*path.split(">"), type_=type_) + # check and return + return None if _value is None or _value == "" else _value + + def set_value(self, section, option, value=None, type_=str): + # check has config + if self.__config is None: + return + # ensure section + if self.__config.has_section(section): + pass + else: + self.__config.add_section(section) + # do set + self.__config.set(section, option, value) + + def get_value(self, section, option, type_=str): + """Simple config value getter to avoid exception risk when getting + a config value. If the section and option exist, returns the + corresponding value, None otherwise. + + The `type_` parameter specify the expected option type and + return. + + :param section: section name of the expected value + :param option: option name name of the expected value + :param type_: type of the expected value. Default: str + :return: expected value in the expected type or None + :rtype: `object` + """ + # check has config + if self.__config is None: + return None + # check value exist + elif self.__config.has_section(section) \ + and self.__config.has_option(section, option): + # check expected value type + if type_ is int: + return self.__config.getint(section, option) + elif type_ is bool: + return self.__config.getboolean(section, option) + elif type_ is str: + return self.__config.get(section, option) + elif type_ is list: + _v = self.__config.get(section, option) + return _v.split(' ') + # unexpected type ?? + else: + return None + # value does not exist + else: + # do nothing + return None + + # singleton instance + instance = None + + def __new__(c, config=None, force=False): + """Singleton new init. + """ + # if doesn't already initialized + if not Config.instance \ + or force is True: + # create a new instance + Config.instance = Config.__Singleton(config=config) + # return the manager object + return Config.instance diff --git a/olpcfr/flask/__init__.py b/olpcfr/flask/__init__.py new file mode 100644 index 0000000..1c3de70 --- /dev/null +++ b/olpcfr/flask/__init__.py @@ -0,0 +1,8 @@ +# flask import +try: + from flask import request, jsonify +except Exception, e: + pass + +# olpcfr import +from olpcfr.flask._app import app, render, run_app, APP_NAME, BUNDLE, ROOT diff --git a/olpcfr/flask/_app.py b/olpcfr/flask/_app.py new file mode 100644 index 0000000..371fe59 --- /dev/null +++ b/olpcfr/flask/_app.py @@ -0,0 +1,60 @@ +# python import +import os + +# common gettext import +from gettext import gettext + +# olpcfr import +from olpcfr import config + +# get APP_NAME or default name +APP_NAME = config.Config().get('activity>name') +APP_NAME = 'my_activity' if APP_NAME is None else APP_NAME + +# get app config values +_debug = config.Config().get('server>debug') +_key = config.Config().get('server>secret_key') +_port = config.Config().get('server>port', type_=int) + +# sugar or debug root path factory +try: + from sugar.activity import activity + BUNDLE = activity.get_bundle_path() + ROOT = activity.get_activity_root() +except Exception, e: + BUNDLE = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', '..')) + ROOT = BUNDLE + + +try: + # flask import + import flask + # init app + app = flask.Flask(__name__) + app.debug = True if _debug is None else _debug + app.secret_key = 'NO_KEY_OOPS' if _key is None else _key + # override jinja template path + app.jinja_loader.searchpath = [os.path.join(BUNDLE, 'templates')] + # init static folder path + from werkzeug import SharedDataMiddleware + app.wsgi_app = SharedDataMiddleware(app.wsgi_app, + {'/static': os.path.join(BUNDLE, 'static')}) +except Exception, e: + app = None + + +def run_app(): + """run method to trigger at from python class. + """ + if app: + app.run(port=_port) + else: + pass + + +def render(template, **context): + """Crappy hack for gettext issue in templates! + """ + context['_'] = gettext + return flask.render_template(template, **context) diff --git a/olpcfr/semanticxo/__init__.py b/olpcfr/semanticxo/__init__.py new file mode 100644 index 0000000..eea4501 --- /dev/null +++ b/olpcfr/semanticxo/__init__.py @@ -0,0 +1 @@ +from olpcfr.semanticxo._runner import Runner diff --git a/olpcfr/semanticxo/_runner.py b/olpcfr/semanticxo/_runner.py new file mode 100644 index 0000000..cdbe650 --- /dev/null +++ b/olpcfr/semanticxo/_runner.py @@ -0,0 +1,45 @@ +# python import +import os, shlex, subprocess, tarfile + +# olpcfr tools import +from olpcfr.tools import logger, storage + + +class Runner(object): + + class __Singleton: + + def __init__(self): + self.proc = None + + def run(self): + # prepare paths + _store_path = storage.get_path(path='store') + # change dir for unzipping + os.chdir(_store_path) + if os.path.exists('redstore'): + pass + else: + _trip_zip = storage.get_path(path='data/triplestore.tar.bz2') + # extract files in tmp dir + _tar = tarfile.open(_trip_zip) + _tar.extractall() + _tar.close() + # get args + args = shlex.split('sh %s/wrapper.sh' % _store_path) + self.proc = subprocess.Popen(args) + # stay in the dir + os.chdir(storage.BUNDLE) + + def stop(self): + pass + + # singleton instance + instance = None + + def __new__(c): + if Runner.instance is None: + Runner.instance = Runner.__Singleton() + else: + pass + return Runner.instance diff --git a/olpcfr/tools/__init__.py b/olpcfr/tools/__init__.py new file mode 100644 index 0000000..071fee0 --- /dev/null +++ b/olpcfr/tools/__init__.py @@ -0,0 +1,2 @@ +from olpcfr.tools._obj import obj +from olpcfr.tools._logger import logger diff --git a/olpcfr/tools/_logger.py b/olpcfr/tools/_logger.py new file mode 100644 index 0000000..f8bb9bd --- /dev/null +++ b/olpcfr/tools/_logger.py @@ -0,0 +1,10 @@ + +# flask logger +from olpcfr.flask import app, APP_NAME +if app: + logger = app.logger +# std logger +else: + import logging + logger = logging.getLogger(APP_NAME) + logger.setLevel(logging.DEBUG) diff --git a/olpcfr/tools/_obj.py b/olpcfr/tools/_obj.py new file mode 100644 index 0000000..85c461a --- /dev/null +++ b/olpcfr/tools/_obj.py @@ -0,0 +1,9 @@ + +class obj(object): + + def __init__(self, d): + for a, b in d.items(): + if isinstance(b, (list, tuple)): + setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b]) + else: + setattr(self, a, obj(b) if isinstance(b, dict) else b) diff --git a/olpcfr/tools/image.py b/olpcfr/tools/image.py new file mode 100644 index 0000000..cffc156 --- /dev/null +++ b/olpcfr/tools/image.py @@ -0,0 +1,110 @@ + +# python import +import logging, os, struct, StringIO + +# atoideweb import +from atoideweb.tools import registry + + +def compute_width_height(width, height, max_width, max_height, use_max=False): + # compute ratio + _ratio_scr = max_width / float(max_height) + _ratio_img = width / float(height) + # .. + if width > max_width\ + or height > max_height: + if _ratio_img > _ratio_scr: + width = max_width + height = int(max_width / _ratio_img) + elif _ratio_img < _ratio_scr: + width = int(max_height * _ratio_img) + height = max_height + else: + width = max_width + height = max_height + # .. + return width, height + # .. + elif use_max is True: + return max_width, max_height + # .. + else: + return width, height + + +def get_image_info(path): + """Tricky method found on Internet that returns the image info from a given + raw image data. + """ + # little check + _info = registry.InfoRegistry().get_info(path) + # already exist + if _info is not None: + return _info + elif os.path.exists(path): + pass + else: + return None, 0, 0 + # read file + _f = open(path) + _data = _f.read() + _f.close() + # + size = len(_data) + height = 0 + width = 0 + content_type = None + + # handle GIFs + if (size >= 10) and _data[:6] in ('GIF87a', 'GIF89a'): + # Check to see if content_type is correct + content_type = 'image/gif' + w, h = struct.unpack("<HH", _data[6:10]) + width = int(w) + height = int(h) + + # See PNG 2. Edition spec (http://www.w3.org/TR/PNG/) + # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR' + # and finally the 4-byte width, height + elif ((size >= 24) and _data.startswith('\211PNG\r\n\032\n') + and (_data[12:16] == 'IHDR')): + content_type = 'image/png' + w, h = struct.unpack(">LL", _data[16:24]) + width = int(w) + height = int(h) + + # Maybe this is for an older PNG version. + elif (size >= 16) and _data.startswith('\211PNG\r\n\032\n'): + # Check to see if we have the right content type + content_type = 'image/png' + w, h = struct.unpack(">LL", _data[8:16]) + width = int(w) + height = int(h) + + # handle JPEGs + elif (size >= 2) and _data.startswith('\377\330'): + content_type = 'image/jpeg' + jpeg = StringIO.StringIO(_data) + jpeg.read(2) + b = jpeg.read(1) + try: + while (b and ord(b) != 0xDA): + while (ord(b) != 0xFF): b = jpeg.read(1) + while (ord(b) == 0xFF): b = jpeg.read(1) + if (ord(b) >= 0xC0 and ord(b) <= 0xC3): + jpeg.read(3) + h, w = struct.unpack(">HH", jpeg.read(4)) + break + else: + jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2) + b = jpeg.read(1) + width = int(w) + height = int(h) + except struct.error: + pass + except ValueError: + pass + # udpate registry + registry.InfoRegistry().set_info(path, (content_type, width, height)) + # .. + return content_type, width, height diff --git a/olpcfr/tools/registry.py b/olpcfr/tools/registry.py new file mode 100644 index 0000000..7e575c3 --- /dev/null +++ b/olpcfr/tools/registry.py @@ -0,0 +1,48 @@ + +# python import +import logging + + +class Registry(object): + + class __Singleton: + """Our singleton object. + """ + + def __init__(self): + """Create the new singleton for a simple use. + """ + # ensure config + self.__dict = dict() + + def get_info(self, path): + # .. + if path in self.__dict: + return self.__dict[path] + else: + return None + + def set_info(self, path, info): + # clear previous + if path in self.__dict: + del self.__dict[path] + else: + pass + # ... + self.__dict[path] = info + + # singleton instance + instance = None + + def __new__(c, force=False): + """Singleton new init. + """ + # if doesn't already initialized + if not Registry.instance \ + or force is True: + # create a new instance + Registry.instance = Registry.__Singleton() + else: + pass + # return the manager object + return Registry.instance diff --git a/olpcfr/tools/sound.py b/olpcfr/tools/sound.py new file mode 100644 index 0000000..7ac6a57 --- /dev/null +++ b/olpcfr/tools/sound.py @@ -0,0 +1,111 @@ + +# python import +import gst +# .. +from datetime import timedelta + + +class Player(object): + + def __init__(self, loop=False): + # playing flag + self.playing = False + self.loop = loop + # player object + self.player = None + self._init_player() + # file to play + self._soundfile = None + + def _reload_cb(self, bus, message): + if self.loop is True: + self.player.set_state(gst.STATE_READY) + self.player.set_state(gst.STATE_PLAYING) + else: + pass + + def _error_cb(self, bus, message): + self.player.set_state(gst.STATE_NULL) + + def _init_player(self): + # make player + self.player = gst.element_factory_make("playbin", "player") + # video fake + _fakesink = gst.element_factory_make('fakesink', "my-fakesink") + self.player.set_property("video-sink", _fakesink) + # bus .. + bus = self.player.get_bus() + bus.add_signal_watch() + bus.connect('message::eos', self._reload_cb) + bus.connect('message::error', self._error_cb) + + def serialize(self): + # little check + if self._soundfile is None: + return None + else: + return file(self._soundfile, 'r').read() + + def load(self, soundfile): + # file to play + self._soundfile = soundfile + # little check + if self._soundfile is None: + pass + else: + # load sound file + self.player.set_state(gst.STATE_NULL) + self.player.set_property('uri', 'file://' + self._soundfile) + + def get_position(self): + # little check + if self._soundfile is None: + return None + else: + # ... + _position = self.player.query_duration(gst.FORMAT_TIME)[0] + # ... + return timedelta(seconds=(_position / gst.SECOND)) + + def get_duration(self): + # little check + if self._soundfile is None: + return None + else: + # _duration = self.player.query_duration(gst.FORMAT_TIME)[0] + # .. + _parser = gst.parse_launch("filesrc name=source ! decodebin2 ! fakesink") + # .. + _source = _parser.get_by_name("source") + _source.set_property("location", self._soundfile) + # .. + _parser.set_state(gst.STATE_PLAYING) + _parser.get_state() + # .. + _format = gst.Format(gst.FORMAT_TIME) + _duration = _parser.query_duration(_format)[0] + _parser.set_state(gst.STATE_NULL) + # .. + return timedelta(seconds=(_duration / gst.SECOND)) + + def seek(self, time): + # little check + if self._soundfile is None: + return + else: + # format time + _seek = time * 1000000000 + # do seek + self.player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, _seek) + + def play(self): + self.playing = True + self.player.set_state(gst.STATE_PLAYING) + + def pause(self): + self.playing = False + self.player.set_state(gst.STATE_PAUSED) + + def stop(self): + self.playing = False + self.player.set_state(gst.STATE_NULL) diff --git a/olpcfr/tools/storage.py b/olpcfr/tools/storage.py new file mode 100644 index 0000000..3e6ebbf --- /dev/null +++ b/olpcfr/tools/storage.py @@ -0,0 +1,150 @@ +# python import +import os + +# olpcfr import +from olpcfr import BUNDLE, ROOT + + +ACTIVITY_NAMES = { + 'paint': 'org.laptop.Oficina', + 'record': 'org.laptop.RecordActivity', + } + + +def check_dir(path): + if os.path.exists(path): + pass + else: + os.mkdir(path) + + +def check_file(path): + if os.path.exists(path): + pass + else: + _f = open(path, 'wb') + _f.write('') + _f.close() + + +def get_path(path=None, bundle=True): + # .. + path = 'static' if path is None else path + # .. + if bundle is True: + return os.path.join(BUNDLE, path) + else: + return os.path.join(ROOT, path) + + +def list_dir(path=None, bundle=True): + # .. + path = get_path(path=path, bundle=bundle) + # .. + return os.listdir(path) + + +def get_sound_path(filename, path=None, bundle=True, ext=None): + # .. + path = get_path(path=path, bundle=bundle) + filename = filename if ext is None else '%s.%s' % (filename, ext) + # .. + return os.path.join(path, filename) + + +def get_sound_path(filename, path=None, bundle=True, ext='ogg'): + # .. + return get_sound_path(filename, path=path, bundle=bundle, ext=ext) + + +def get_image_path(filename, path=None, bundle=True, ext='png'): + # .. + return get_sound_path(filename, path=path, bundle=bundle, ext=ext) + + +def __do_query(query): + from sugar.datastore import datastore + # find in ds + _results, _count = datastore.find(query, sorting='timestamp') + for _r in _results: + # get meta + _m = _r.get_metadata() + if 'activity' in query: + yield _r + elif _m['activity'] == '': + yield _r + else: + continue + + +def get_journal_objects(activity_name=None, mime_type=None): + # init + _query = dict() + # prepare query name + if activity_name is None\ + and mime_type is None: + return [] + elif mime_type is None: + return __do_query({'activity': ACTIVITY_NAMES[activity_name]}) + else: + return __do_query({'mime_type': mime_type}) + + +def list_info_from_journal(activity_name=None, mime_type=None): + # get objects first + _objs = get_journal_objects(activity_name=activity_name, mime_type=mime_type) + # make unique titles + _titles = {} + # return infos + for _o in _objs: + # get meta + _m = _o.get_metadata() + # get title + _t = _m['title'] + # ensure description + _d = _m['description'] if 'description' in _m else '' + _p = _m['preview'] if 'preview' in _m else None + # little check + if _t in _titles: + # udpate reg + _titles[_t] += 1 + # update value to show + _t = '%s (%s)' % (_t, _titles[_t]) + # init title reg + else: + _titles[_t] = 1 + # ensure info + yield { + 'activity_id' : _m['activity_id'], + 'description' : _d, + 'timestamp' : _m['timestamp'], + 'preview' : _p, + 'title' : _t, + } + + +def list_files_from_journal(activity_name=None, mime_type=None): + # get objects first + _objs = get_journal_objects(activity_name=activity_name, + mime_type=mime_type) + # return paths + for _o in _objs: + # TODO open the files + yield _o.get_file_path() + + +def get_path_from_journal(timestamp, mime_type): + from sugar.datastore import datastore + # .. + _query = { + 'timestamp': int(timestamp), + 'mime_type': mime_type + } + # find in ds + _results, _count = datastore.find(_query) + # .. + if _count == 1: + # get path + return _results[0].get_file_path() + else: + return None |