Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorflorent <florent@toopy.org>2012-02-06 21:51:28 (GMT)
committer florent <florent@toopy.org>2012-02-06 21:51:28 (GMT)
commitb7d174099e53522ccdf20d0c6cd33e96784b099e (patch)
tree650860b2dbe7a171ceac41fdbe89ce38fb246875
initial commitHEADmaster
-rw-r--r--.gitignore9
-rw-r--r--LICENSE7
-rw-r--r--olpcfr/__init__.py3
-rw-r--r--olpcfr/_server.py26
-rw-r--r--olpcfr/config.py111
-rw-r--r--olpcfr/flask/__init__.py8
-rw-r--r--olpcfr/flask/_app.py60
-rw-r--r--olpcfr/semanticxo/__init__.py1
-rw-r--r--olpcfr/semanticxo/_runner.py45
-rw-r--r--olpcfr/tools/__init__.py2
-rw-r--r--olpcfr/tools/_logger.py10
-rw-r--r--olpcfr/tools/_obj.py9
-rw-r--r--olpcfr/tools/image.py110
-rw-r--r--olpcfr/tools/registry.py48
-rw-r--r--olpcfr/tools/sound.py111
-rw-r--r--olpcfr/tools/storage.py150
-rw-r--r--setup.py41
17 files changed, 751 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5bf9088
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+syntax: glob
+*.pyc
+*.mo
+*.swp
+*.ori
+*.new
+*.egg-info
+dist
+docs/build
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1e6adb8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2011 florent.pigout@gmail.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..b2d25ab
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,41 @@
+# (C) Copyright 2010 Bewype <http://www.bewype.org>
+
+# setuptools import
+from setuptools import setup, find_packages
+
+VERSION = '0.1'
+
+setup(
+ name='Creactivity',
+ version=VERSION,
+ description='Simple Sugar new Activity Skeleton Creator',
+ author='florent.pigout@gmail.com',
+ author_email='florent.pigout@gmail.com',
+ url='',
+ download_url='',
+ license='MIT',
+ zip_safe=False,
+ include_package_data=True,
+ packages=find_packages(),
+ install_requires=[
+ "PasteScript",
+ "Tempita",
+ ],
+ namespace_packages=[],
+ classifiers = [
+ 'Development Status :: 3 - Alpha',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'License :: OSI Approved :: MIT License',
+ 'Topic :: Software Development :: Libraries :: Python Modules'
+ ],
+ scripts=[],
+ entry_points="""
+ [paste.paster_create_template]
+ creactivity = creactivity.template:CreactivityTemplate
+ creactiweb = creactiweb.template:CreactiwebTemplate
+ creactigame = creactigame.template:CreactigameTemplate
+ creactistore = creactistore.template:CreactistoreTemplate
+ """,
+ keywords = [],
+ )