changeset 281:0d2c2a379c63

work merged
author "Rex Tsai <chihchun@kalug.linux.org.tw>"
date Tue, 02 Dec 2008 17:21:21 +0800
parents b5897d63f44e (diff) a9847bf80058 (current diff)
children fcc5535266c2 10f14db31151
files
diffstat 84 files changed, 2721 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/README	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,57 @@
+Definition
+
+    * GAE: Google App Engine
+    * GAEO: Google App Engine Oil ( Web Framwork )
+
+Requirment Of Develope Env
+
+    * download eagle-eye and setup it. see more in eagel-eye README
+    * download GAE SDK and setup it.
+    * download GAEO Framwork and setup it. see more in GAEO document.
+
+    * add following in ~/.bashrc
+
+        export GAEO_HOME='~/codes/gaeo'  # put your installation directory here.
+        export GAE_HOME='~/codes/google_appengine'
+        PATH=$PATH:$GAEO_HOME/bin:$GAE_HOME:~/bin/ 
+
+Launch Web site
+
+    * using GAE dev_server.py to launch.
+    * browse http://localhost:8080 to see the index page.
+
+Develope new version of models if chichchun has changed database schema.
+
+    if db schema has changed, the GAEO models in web site need to rebuild,
+    a model generator will get colum informations in all tables in ikariam.sqlite, 
+    and makes new GAEO models, which has a required properties, you may want to 
+    modify it later if some properties is wrong.
+
+    the model relation will not generate in this action, you have added it by hand.
+
+    [ WARRING ]
+    it will re-generate all models, the web site can not work until
+    you have done the work that re-design models.
+
+    $ ./rebuild_world
+    
+How many model are implemented already?
+ 	
+ 	player, island, city, ally
+    
+How to kwnow What input fileds of the model in POST ?
+ 
+ 	use action "attrs", example: http://localhost:8080/player/attrs
+
+How to display model information ?
+
+	use action "show", example: http://localhost:8080/player/show/1
+
+	p.s the last number 1 is the Ikariam Game ID of the model.
+
+Hot to get the datas of the model in JSON formate?
+
+	use action "json", example: http//"localhost:8080/player/json/1"
+	
+	p.s the last number 1 is the Ikariam Game ID of the model.
+ 	
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/app.yaml	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,17 @@
+application: ikweb
+version: 1
+api_version: 1
+runtime: python
+
+handlers:
+- url: /css
+  static_dir: assets/css
+- url: /js
+  static_dir: assets/js
+- url: /img
+  static_dir: assets/img
+- url: /favicon.ico
+  static_files: favicon.ico
+  upload: favicon.ico
+- url: .*
+  script: main.py
Binary file ikweb/ikweb/application/controller/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/controller/ally.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,9 @@
+from google.appengine.ext import db
+
+from gaeo.controller import BaseController
+
+from model.ally import Ally
+
+class AllyController(BaseController):
+
+    pass
\ No newline at end of file
Binary file ikweb/ikweb/application/controller/ally.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/controller/city.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,9 @@
+from google.appengine.ext import db
+
+from gaeo.controller import BaseController
+
+from model.city import City
+
+class CityController(BaseController):
+
+    pass
\ No newline at end of file
Binary file ikweb/ikweb/application/controller/city.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/controller/island.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,9 @@
+from google.appengine.ext import db
+
+from gaeo.controller import BaseController
+
+from model.island import Island
+
+class IslandController(BaseController):
+
+    pass
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/controller/player.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,10 @@
+from google.appengine.ext import db
+
+from gaeo.controller import BaseController
+
+from model.player import Player
+from model.ally import Ally
+
+class PlayerController(BaseController):
+    
+    pass
\ No newline at end of file
Binary file ikweb/ikweb/application/controller/player.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/controller/welcome.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,5 @@
+from gaeo.controller import BaseController
+
+class WelcomeController(BaseController):
+    def index(self):
+        pass
Binary file ikweb/ikweb/application/controller/welcome.pyc has changed
Binary file ikweb/ikweb/application/model/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/model/ally.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,12 @@
+from google.appengine.ext import db
+from gaeo.model import BaseModel
+
+class Ally(BaseModel):
+    game_id = db.IntegerProperty()
+    members_total = db.IntegerProperty(verbose_name='members_total',name='members_total')
+    name = db.TextProperty(verbose_name='name',name='name')
+    score = db.IntegerProperty(verbose_name='score',name='score')
+    lastupdate_at = db.DateTimeProperty(auto_now=True)
+    url = db.TextProperty(verbose_name='url',name='url')   
+
+    int_attrs = ['score','members_total','game_id']
\ No newline at end of file
Binary file ikweb/ikweb/application/model/ally.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/model/city.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,22 @@
+from google.appengine.ext import db
+from gaeo.model import BaseModel
+
+from model.island import Island
+from model.player import Player
+
+# city belongs to one player and one island.
+
+class City(BaseModel):
+    game_id = db.IntegerProperty()
+    name = db.TextProperty(verbose_name='name',name='name')
+    level = db.IntegerProperty(verbose_name='level',name='level')
+    island = db.ReferenceProperty(Island)
+    island_id = db.IntegerProperty(verbose_name='island_id',name='island_id')    
+    player = db.ReferenceProperty(Player)
+    player_id = db.IntegerProperty(verbose_name='player',name='player')    
+    status = db.StringProperty(verbose_name='status',name='status')
+    lastupdate_at = db.DateTimeProperty()
+    
+    unshow_attrs = ['owner_id','island_id']
+    ref_attrs = ['island_id','player_id']  
+    int_attrs = ['level','game_id']
Binary file ikweb/ikweb/application/model/city.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/model/island.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,10 @@
+from google.appengine.ext import db
+from gaeo.model import BaseModel
+
+class Island(BaseModel):
+    game_id = db.IntegerProperty()
+    name = db.TextProperty(verbose_name='name',name='name')
+    x = db.IntegerProperty(verbose_name='x',name='x')
+    y = db.IntegerProperty(verbose_name='y',name='y')
+
+    int_attrs = ['x','y']
\ No newline at end of file
Binary file ikweb/ikweb/application/model/island.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/model/player.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,26 @@
+from google.appengine.ext import db
+from gaeo.model import BaseModel
+
+from model.ally import Ally
+
+class Player(BaseModel):
+    game_id = db.IntegerProperty()
+    ally = db.ReferenceProperty(Ally)
+    ally_id = db.IntegerProperty(verbose_name='ally_id',name='ally_id')    
+    army_score_main = db.IntegerProperty(verbose_name='army_score_main',name='army_score_main')
+    building_score_main = db.IntegerProperty(verbose_name='building_score_main',name='building_score_main')
+    building_score_secondary = db.IntegerProperty(verbose_name='building_score_secondary',name='building_score_secondary')
+    name = db.StringProperty(verbose_name='name',name='name')
+    research_score_main = db.IntegerProperty(verbose_name='research_score_main',name='research_score_main')
+    research_score_secondary = db.IntegerProperty(verbose_name='research_score_secondary',name='research_score_secondary')
+    score = db.IntegerProperty(verbose_name='score',name='score')
+    trader_score_secondary = db.IntegerProperty(verbose_name='trader_score_secondary',name='trader_score_secondary')
+    lastupdate_at = db.DateTimeProperty(auto_now=True)
+    
+    unshow_attrs = ['ally']
+    
+    int_attrs = ['army_score_main', 'building_score_main',
+                 'building_score_secondary', 'research_score_main',
+                 'research_score_secondary', 'score',
+                 'trader_score_secondary',
+                'game_id', 'ally_id']
\ No newline at end of file
Binary file ikweb/ikweb/application/model/player.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/application/model/report.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,20 @@
+from google.appengine.ext import db
+from gaeo.model import BaseModel
+
+class Report(BaseModel):
+    attacker = db.TextProperty(verbose_name='attacker',name='attacker')
+    city = db.IntegerProperty(verbose_name='city',name='city')
+    cityLevel = db.IntegerProperty(verbose_name='cityLevel',name='cityLevel')
+    crystal = db.IntegerProperty(default=None,verbose_name='crystal',name='crystal')
+    date = db.DatetimeProperty(verbose_name='date',name='date')
+    defender = db.TextProperty(verbose_name='defender',name='defender')
+    gold = db.IntegerProperty(default=None,verbose_name='gold',name='gold')
+    id = db.IntegerProperty(verbose_name='id',name='id')
+    island = db.IntegerProperty(verbose_name='island',name='island')
+    sulfur = db.IntegerProperty(default=None,verbose_name='sulfur',name='sulfur')
+    targetCity = db.TextProperty(verbose_name='targetCity',name='targetCity')
+    time = db.DatetimeProperty(verbose_name='time',name='time')
+    wallLevel = db.IntegerProperty(verbose_name='wallLevel',name='wallLevel')
+    wine = db.IntegerProperty(default=None,verbose_name='wine',name='wine')
+    winner = db.TextProperty(verbose_name='winner',name='winner')
+    wood = db.IntegerProperty(default=None,verbose_name='wood',name='wood')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+""" The gaeo library package. """
+
+import re
+
+from google.appengine.ext import webapp
+
+from gaeo.dispatch import dispatcher
+
+
+class Config:
+    """ The singleton of GAEO's configuration """
+
+    class __impl:
+        def __init__(self):
+            self.template_dir = ''
+            self.session_store = 'memcache'
+            self.app_name = ''
+
+    __instance = None
+
+    def __init__(self):
+        if Config.__instance is None:
+            Config.__instance = Config.__impl()
+
+        self.__dict__['_Config__instance'] = Config.__instance
+
+    def __getattr__(self, attr):
+        return getattr(self.__instance, attr)
+
+    def __setattr__(self, attr, value):
+        return setattr(self.__instance, attr, value)
+
+
+class MainHandler(webapp.RequestHandler):
+    """Handles all requests
+    """
+    def get(self, *args):
+        self.__process_request()
+
+    def post(self, *args):
+        self.__process_request()
+        
+    def head(self, *args):
+        self.__process_request()
+        
+    def options(self, *args):
+        self.__process_request()
+        
+    def put(self, *args):
+        self.__process_request()
+        
+    def delete(self, *args):
+        self.__process_request()
+        
+    def trace(self, *args):
+        self.__process_request()
+
+    def __process_request(self):
+        """dispatch the request"""
+        dispatcher.dispatch(self)
+
Binary file ikweb/ikweb/gaeo/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/controller/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 GAEO Team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""GAEO controller package
+"""
+
+import new
+import os
+import re
+import logging
+
+from google.appengine.ext.webapp import template
+
+import gaeo
+import errors
+import helper
+
+class BaseController(object):
+    """The BaseController is the base class of action controllers.
+        Action controller handles the requests from clients.
+    """
+    def __init__(self, hnd, params = {}):
+        self.hnd = hnd
+        self.resp = self.response = hnd.response
+        self.req = self.request = hnd.request
+        self._env = self.request.environ # shortcut
+        self.params = params
+
+        rp = hnd.request.params.mixed()
+        for k in rp:
+            self.params[k] = rp[k]
+
+        self._controller = params['controller']
+        self._action = params['action']
+        self.has_rendered = False
+        self.__config = gaeo.Config()
+
+        self.__tpldir = os.path.join(
+            self.__config.template_dir,
+            self._controller
+        )
+        self._template_values = {}
+
+        # implement parameter nesting as in rails
+        self.params=self.__nested_params(self.params)
+        
+        # detect the mobile platform
+        self._is_mobile = self.__detect_mobile()
+        self._is_iphone = self.__detect_iphone()
+
+        # alias the cookies
+        self.cookies = self.request.cookies
+
+        # create the session
+        try:
+            store = self.__config.session_store
+            exec('from gaeo.session.%s import %sSession' %
+                (store, store.capitalize()))
+        
+            self.session = eval('%sSession' % store.capitalize())(
+                                hnd, '%s_session' % self.__config.app_name)
+        except:
+            raise errors.ControllerInitError('Initialize Session Error!')
+
+        # add request method (get, post, head, put, ....)
+        self._request_method = self.request.environ['REQUEST_METHOD'].lower()
+        
+        # tell if an ajax call (X-Request)
+        self._is_xhr = self._env.has_key('HTTP_X_REQUESTED_WITH') and \
+                        self._env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
+        
+
+        # add helpers
+        helpers = dir(helper)
+        for h in helpers:
+            if not re.match('^__', h):
+                self.__dict__[h] = new.instancemethod(eval('helper.%s' % h), self, BaseController)
+
+    #this can be used in application controller, it is meant to be called before every controller
+    def implicit_action(self):
+        pass 
+
+    def before_action(self):
+        pass
+    
+    def after_action(self):
+        pass
+        
+    def invalid_action(self):
+        """ If the router went to an invalid action """
+        self.hnd.error(404)
+        self.render(text="Invalid action")
+
+    def to_json(self, obj, **kwds):
+        """ Convert a dict to JSON. Inspired from SimpleJSON """
+        from gaeo.controller.jsonencoder import JSONEncoder
+        
+        if not kwds:
+            return JSONEncoder(skipkeys=False, ensure_ascii=True, check_circular=True,
+                allow_nan=True, indent=None, separators=None, encoding='utf-8',
+                default=None).encode(obj)
+        else:
+            return JSONEncoder(
+                skipkeys=False, ensure_ascii=True, check_circular=True,
+                allow_nan=True, indent=None, separators=None,
+                encoding='utf-8', default=None, **kwds).encode(obj)
+
+    def render(self, *html, **opt):
+        o = self.resp.out
+        h = self.resp.headers
+
+        # check the default Content-Type is 'text/html; charset=utf-8'
+        h['Content-Type'] = 'text/html; charset=utf-8'
+        if html:
+            for h in html:
+                o.write(h.decode('utf-8'))
+        elif opt:
+            if opt.get('html'):
+                o.write(opt.get('html').decode('utf-8'))                
+            elif opt.get('text'):
+                h['Content-Type'] = 'text/plain; charset=utf-8'
+                o.write(str(opt.get('text')).decode('utf-8'))
+            elif opt.get('json'):
+                h['Content-Type'] = 'application/json; charset=utf-8'
+                o.write(opt.get('json').decode('utf-8'))
+            elif opt.get('xml'):
+                h['Content-Type'] = 'text/xml; charset=utf-8'
+                o.write(opt.get('xml').decode('utf-8'))
+            elif opt.get('script'):
+                h['Content-Type'] = 'text/javascript; charset=utf-8'
+                o.write(opt.get('script').decode('utf-8'))
+            elif opt.get('template'):
+                context = self.__dict__
+                if isinstance(opt.get('values'), dict):
+                    context.update(opt.get('values'))
+                o.write(template.render(
+                    os.path.join(self.__tpldir,
+                                 opt.get('template') + '.html'),
+                    context
+                ))
+            elif opt.get('template_string'):
+                context = self.__dict__
+                if isinstance(opt.get('values'), dict):
+                    context.update(opt.get('values'))
+                from django.template import Context, Template
+                t = Template(opt.get('template_string').encode('utf-8'))
+                c = Context(context)
+                o.write(t.render(c))
+            elif opt.get('image'): # for sending an image content
+                import imghdr
+                img_type = imghdr.what('ignored_filename', opt.get('image'))
+                h['Content-Type'] = 'image/' + img_type
+                o.write(opt.get('image'))
+            else:
+                raise errors.ControllerRenderTypeError('Render type error')
+        self.has_rendered = True
+
+    def redirect(self, url, perm = False):
+        self.has_rendered = True # dirty hack, make gaeo don't find the template
+        self.hnd.redirect(url, perm)
+
+    def respond_to(self, **blk):
+        """ according to self.params['format'] to respond appropriate stuff
+        """
+        if self.params.has_key('format') and blk.has_key(self.params['format']):
+            logging.error(self.params['format'])
+            blk[self.params['format']]()
+
+    def __detect_mobile(self):
+        h = self.request.headers
+
+        # wap.wml
+        ha = h.get('Accept')
+        if ha and (ha.find('text/vnd.wap.wml') > -1 or ha.find('application/vnd.wap.xhtml+xml') > -1):
+            return True
+        
+        wap_profile = h.get('X-Wap-Profile')
+        profile = h.get("Profile")
+        opera_mini = h.get('X-OperaMini-Features')
+        ua_pixels = h.get('UA-pixels')
+        
+        if wap_profile or profile or opera_mini or ua_pixels:
+            return True
+        
+        # FIXME: add common user agents
+        common_uas = ['sony', 'noki', 'java', 'midp', 'benq', 'wap-', 'wapi']
+        
+        ua = h.get('User-Agent')
+        if ua and ua[0:4].lower() in common_uas:
+            return True
+        
+        return False
+        
+    def __detect_iphone(self):
+        """ for detecting iPhone/iPod """
+        ua = self.request.headers.get('User-Agent')
+        if ua:
+            ua = ua.lower();
+            return ua.find('iphone') > -1 or ua.find('ipod') > -1
+        else:
+            return False
+            
+
+    # Helper methods for parameter nesting as in rails
+    def __appender(self,dict,arr,val):
+            if len(arr) > 1:
+                try:
+                    dict[arr[0]]
+                except KeyError:
+                    dict[arr[0]]={}
+                return {arr[0]: self.__appender(dict[arr[0]],arr[1:],val)}
+            else:
+                dict[arr[0]]=val
+                return 
+
+    def __nested_params(self,prm):
+        prm2={}
+        for param in prm:
+            parray = param.replace(']',"").split('[')
+            if len(parray) == 1:
+                parray = parray[0].split('-')
+            self.__appender(prm2,parray,prm[param])
+        return prm2
+        
Binary file ikweb/ikweb/gaeo/controller/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/controller/errors.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+""" The gaeo controller errors """
+
+class ControllerError(Exception):
+    """ Base error class of controllers' errors """
+
+class ControllerInitError(ControllerError):
+    pass
+
+class ControllerRenderError(ControllerError):
+    """ error occured while render """
+
+class ControllerRenderTypeError(ControllerRenderError):
+    """ Render an invalid type """
Binary file ikweb/ikweb/gaeo/controller/errors.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/controller/helper.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+""" helper module """
+
+def clear_session(self):
+    if self.session:
+        import logging
+        logging.info('clear session')
+        self.session.invalidate()
\ No newline at end of file
Binary file ikweb/ikweb/gaeo/controller/helper.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/controller/jsonencoder.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,406 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#Copyright (c) 2006 Bob Ippolito
+#
+# 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.
+"""
+Implementation of JSONEncoder
+"""
+import re
+
+try:
+    from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+    pass
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+    '\\': '\\\\',
+    '"': '\\"',
+    '\b': '\\b',
+    '\f': '\\f',
+    '\n': '\\n',
+    '\r': '\\r',
+    '\t': '\\t',
+}
+for i in range(0x20):
+    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def floatstr(o, allow_nan=True):
+    # Check for specials.  Note that this type of test is processor- and/or
+    # platform-specific, so do tests which don't depend on the internals.
+
+    if o != o:
+        text = 'NaN'
+    elif o == INFINITY:
+        text = 'Infinity'
+    elif o == -INFINITY:
+        text = '-Infinity'
+    else:
+        return FLOAT_REPR(o)
+
+    if not allow_nan:
+        raise ValueError("Out of range float values are not JSON compliant: %r"
+            % (o,))
+
+    return text
+
+
+def encode_basestring(s):
+    """
+    Return a JSON representation of a Python string
+    """
+    def replace(match):
+        return ESCAPE_DCT[match.group(0)]
+    return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+        s = s.decode('utf-8')
+    def replace(match):
+        s = match.group(0)
+        try:
+            return ESCAPE_DCT[s]
+        except KeyError:
+            n = ord(s)
+            if n < 0x10000:
+                return '\\u%04x' % (n,)
+            else:
+                # surrogate pair
+                n -= 0x10000
+                s1 = 0xd800 | ((n >> 10) & 0x3ff)
+                s2 = 0xdc00 | (n & 0x3ff)
+                return '\\u%04x\\u%04x' % (s1, s2)
+    return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+try:
+    encode_basestring_ascii = c_encode_basestring_ascii
+except NameError:
+    encode_basestring_ascii = py_encode_basestring_ascii
+
+
+class JSONEncoder(object):
+    """
+    Extensible JSON <http://json.org> encoder for Python data structures.
+
+    Supports the following objects and types by default:
+    
+    +-------------------+---------------+
+    | Python            | JSON          |
+    +===================+===============+
+    | dict              | object        |
+    +-------------------+---------------+
+    | list, tuple       | array         |
+    +-------------------+---------------+
+    | str, unicode      | string        |
+    +-------------------+---------------+
+    | int, long, float  | number        |
+    +-------------------+---------------+
+    | True              | true          |
+    +-------------------+---------------+
+    | False             | false         |
+    +-------------------+---------------+
+    | None              | null          |
+    +-------------------+---------------+
+
+    To extend this to recognize other objects, subclass and implement a
+    ``.default()`` method with another method that returns a serializable
+    object for ``o`` if possible, otherwise it should call the superclass
+    implementation (to raise ``TypeError``).
+    """
+    __all__ = ['__init__', 'default', 'encode', 'iterencode']
+    item_separator = ', '
+    key_separator = ': '
+    def __init__(self, skipkeys=False, ensure_ascii=True,
+            check_circular=True, allow_nan=True, sort_keys=False,
+            indent=None, separators=None, encoding='utf-8', default=None):
+        """
+        Constructor for JSONEncoder, with sensible defaults.
+
+        If skipkeys is False, then it is a TypeError to attempt
+        encoding of keys that are not str, int, long, float or None.  If
+        skipkeys is True, such items are simply skipped.
+
+        If ensure_ascii is True, the output is guaranteed to be str
+        objects with all incoming unicode characters escaped.  If
+        ensure_ascii is false, the output will be unicode object.
+
+        If check_circular is True, then lists, dicts, and custom encoded
+        objects will be checked for circular references during encoding to
+        prevent an infinite recursion (which would cause an OverflowError).
+        Otherwise, no such check takes place.
+
+        If allow_nan is True, then NaN, Infinity, and -Infinity will be
+        encoded as such.  This behavior is not JSON specification compliant,
+        but is consistent with most JavaScript based encoders and decoders.
+        Otherwise, it will be a ValueError to encode such floats.
+
+        If sort_keys is True, then the output of dictionaries will be
+        sorted by key; this is useful for regression tests to ensure
+        that JSON serializations can be compared on a day-to-day basis.
+
+        If indent is a non-negative integer, then JSON array
+        elements and object members will be pretty-printed with that
+        indent level.  An indent level of 0 will only insert newlines.
+        None is the most compact representation.
+
+        If specified, separators should be a (item_separator, key_separator)
+        tuple.  The default is (', ', ': ').  To get the most compact JSON
+        representation you should specify (',', ':') to eliminate whitespace.
+
+        If specified, default is a function that gets called for objects
+        that can't otherwise be serialized.  It should return a JSON encodable
+        version of the object or raise a ``TypeError``.
+
+        If encoding is not None, then all input strings will be
+        transformed into unicode using that encoding prior to JSON-encoding.
+        The default is UTF-8.
+        """
+
+        self.skipkeys = skipkeys
+        self.ensure_ascii = ensure_ascii
+        self.check_circular = check_circular
+        self.allow_nan = allow_nan
+        self.sort_keys = sort_keys
+        self.indent = indent
+        self.current_indent_level = 0
+        if separators is not None:
+            self.item_separator, self.key_separator = separators
+        if default is not None:
+            self.default = default
+        self.encoding = encoding
+
+    def _newline_indent(self):
+        return '\n' + (' ' * (self.indent * self.current_indent_level))
+
+    def _iterencode_list(self, lst, markers=None):
+        if not lst:
+            yield '[]'
+            return
+        if markers is not None:
+            markerid = id(lst)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = lst
+        yield '['
+        if self.indent is not None:
+            self.current_indent_level += 1
+            newline_indent = self._newline_indent()
+            separator = self.item_separator + newline_indent
+            yield newline_indent
+        else:
+            newline_indent = None
+            separator = self.item_separator
+        first = True
+        for value in lst:
+            if first:
+                first = False
+            else:
+                yield separator
+            for chunk in self._iterencode(value, markers):
+                yield chunk
+        if newline_indent is not None:
+            self.current_indent_level -= 1
+            yield self._newline_indent()
+        yield ']'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode_dict(self, dct, markers=None):
+        if not dct:
+            yield '{}'
+            return
+        if markers is not None:
+            markerid = id(dct)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = dct
+        yield '{'
+        key_separator = self.key_separator
+        if self.indent is not None:
+            self.current_indent_level += 1
+            newline_indent = self._newline_indent()
+            item_separator = self.item_separator + newline_indent
+            yield newline_indent
+        else:
+            newline_indent = None
+            item_separator = self.item_separator
+        first = True
+        if self.ensure_ascii:
+            encoder = encode_basestring_ascii
+        else:
+            encoder = encode_basestring
+        allow_nan = self.allow_nan
+        if self.sort_keys:
+            keys = dct.keys()
+            keys.sort()
+            items = [(k, dct[k]) for k in keys]
+        else:
+            items = dct.iteritems()
+        _encoding = self.encoding
+        _do_decode = (_encoding is not None
+            and not (_encoding == 'utf-8'))
+        for key, value in items:
+            if isinstance(key, str):
+                if _do_decode:
+                    key = key.decode(_encoding)
+            elif isinstance(key, basestring):
+                pass
+            # JavaScript is weakly typed for these, so it makes sense to
+            # also allow them.  Many encoders seem to do something like this.
+            elif isinstance(key, float):
+                key = floatstr(key, allow_nan)
+            elif isinstance(key, (int, long)):
+                key = str(key)
+            elif key is True:
+                key = 'true'
+            elif key is False:
+                key = 'false'
+            elif key is None:
+                key = 'null'
+            elif self.skipkeys:
+                continue
+            else:
+                raise TypeError("key %r is not a string" % (key,))
+            if first:
+                first = False
+            else:
+                yield item_separator
+            yield encoder(key)
+            yield key_separator
+            for chunk in self._iterencode(value, markers):
+                yield chunk
+        if newline_indent is not None:
+            self.current_indent_level -= 1
+            yield self._newline_indent()
+        yield '}'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode(self, o, markers=None):
+        if isinstance(o, basestring):
+            if self.ensure_ascii:
+                encoder = encode_basestring_ascii
+            else:
+                encoder = encode_basestring
+            _encoding = self.encoding
+            if (_encoding is not None and isinstance(o, str)
+                    and not (_encoding == 'utf-8')):
+                o = o.decode(_encoding)
+            yield encoder(o)
+        elif o is None:
+            yield 'null'
+        elif o is True:
+            yield 'true'
+        elif o is False:
+            yield 'false'
+        elif isinstance(o, (int, long)):
+            yield str(o)
+        elif isinstance(o, float):
+            yield floatstr(o, self.allow_nan)
+        elif isinstance(o, (list, tuple)):
+            for chunk in self._iterencode_list(o, markers):
+                yield chunk
+        elif isinstance(o, dict):
+            for chunk in self._iterencode_dict(o, markers):
+                yield chunk
+        else:
+            if markers is not None:
+                markerid = id(o)
+                if markerid in markers:
+                    raise ValueError("Circular reference detected")
+                markers[markerid] = o
+            for chunk in self._iterencode_default(o, markers):
+                yield chunk
+            if markers is not None:
+                del markers[markerid]
+
+    def _iterencode_default(self, o, markers=None):
+        newobj = self.default(o)
+        return self._iterencode(newobj, markers)
+
+    def default(self, o):
+        """
+        Implement this method in a subclass such that it returns
+        a serializable object for ``o``, or calls the base implementation
+        (to raise a ``TypeError``).
+
+        For example, to support arbitrary iterators, you could
+        implement default like this::
+            
+            def default(self, o):
+                try:
+                    iterable = iter(o)
+                except TypeError:
+                    pass
+                else:
+                    return list(iterable)
+                return JSONEncoder.default(self, o)
+        """
+        raise TypeError("%r is not JSON serializable" % (o,))
+
+    def encode(self, o):
+        """
+        Return a JSON string representation of a Python data structure.
+
+        >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+        '{"foo": ["bar", "baz"]}'
+        """
+        # This is for extremely simple cases and benchmarks.
+        if isinstance(o, basestring):
+            if isinstance(o, str):
+                _encoding = self.encoding
+                if (_encoding is not None 
+                        and not (_encoding == 'utf-8')):
+                    o = o.decode(_encoding)
+            if self.ensure_ascii:
+                return encode_basestring_ascii(o)
+            else:
+                return encode_basestring(o)
+        # This doesn't pass the iterator directly to ''.join() because the
+        # exceptions aren't as detailed.  The list call should be roughly
+        # equivalent to the PySequence_Fast that ''.join() would do.
+        chunks = list(self.iterencode(o))
+        return ''.join(chunks)
+
+    def iterencode(self, o):
+        """
+        Encode the given object and yield each string
+        representation as available.
+        
+        For example::
+            
+            for chunk in JSONEncoder().iterencode(bigobject):
+                mysocket.write(chunk)
+        """
+        if self.check_circular:
+            markers = {}
+        else:
+            markers = None
+        return self._iterencode(o, markers)
+
+__all__ = ['JSONEncoder']
Binary file ikweb/ikweb/gaeo/controller/jsonencoder.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/dispatch/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Dispatcher Package
+"""
\ No newline at end of file
Binary file ikweb/ikweb/gaeo/dispatch/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/dispatch/dispatcher.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import re
+import logging
+
+import router
+import sys 
+import os
+from traceback import *
+
+HTTP_ERRORS = {
+    '400': 'Bad Request',
+    '402': 'Payment Required',
+    '403': 'Forbidden',
+    '404': 'Not Found',
+    '500': 'Internal Server Error'
+}
+
+TXMT_LINKS = False # set true to show textmate links on tracebacks
+DEBUG = True # set true to show traceback on error pages
+
+def dispatch(hnd):
+    
+    # generate nice traceback with optional textmate links
+    def nice_traceback(traceback):
+        tb=""
+        for line in traceback.splitlines(1):
+            filename = re.findall('File "(.+)",', line)
+            linenumber = re.findall(', line\s(\d+),', line)
+            modulename = re.findall(', in ([A-Za-z]+)', line)
+            if filename and linenumber and not re.match("<(.+)>",filename[0]):
+                fn=filename[0]
+                mn="in %s" % modulename[0] if modulename else ""
+                fnshort=os.path.basename(fn)
+                ln=linenumber[0]
+                if TXMT_LINKS:
+                    html="<a href='txmt://open/?url=file://%s&line=%s'>%s:%s %s</a> %s" % (fn,ln,fnshort,ln,mn,line)
+                else:
+                    html="<b>%s:%s %s</b> %s" % (fnshort,ln,mn,line)
+                tb+=html
+            else:
+                tb+=line
+        return tb
+    
+    # show error and write to log
+    def show_error(code, log_msg = ''):
+        hnd.error(code)
+        if sys.exc_info()[0]:
+            exception_name = sys.exc_info()[0].__name__
+            exception_details = str(sys.exc_info()[1])
+            exception_traceback = ''.join(format_exception(*sys.exc_info()))
+            special_info = str(exception_details) != str(log_msg)
+            logging.error(exception_name)
+            logging.error(exception_details)
+            logging.error(log_msg)
+            logging.error(exception_traceback)
+            hnd.response.out.write('<h1>%s</h1>' % HTTP_ERRORS[str(code)])
+            if DEBUG:
+                tb=nice_traceback(exception_traceback)
+                if special_info: logging.error(log_msg)
+                hnd.response.out.write('<h3>%s: %s</h3>' % (exception_name, exception_details))
+                if special_info: hnd.response.out.write('<pre> %s </pre>' % log_msg)
+                hnd.response.out.write('<h1> Traceback </h1>')
+                hnd.response.out.write('<pre> %s </pre>' % tb)
+        else:
+            hnd.response.out.write('<h1> %s </h1>' % log_msg)
+
+    # resolve the URL
+    url = hnd.request.path
+    r = router.Router()
+    route = r.resolve(url)
+
+    if route is None:
+        try:
+            raise Exception('invalid URL')
+        except Exception, e:
+            show_error(500, e)
+    else:
+        # create the appropriate controller
+        try:
+            exec('from controller import %s' % route['controller']) in globals()
+            ctrl = eval('%s.%sController' % (
+                        route['controller'],
+                        route['controller'].capitalize()
+                    ))(hnd, route)
+
+            # dispatch
+            logging.info('URL "%s" is dispatched to: %sController#%s',
+                         url,
+                         route['controller'].capitalize(),
+                         route['action'])
+        except ImportError, e:
+            show_error(404, "Controller doesn't exist")
+        except AttributeError, e:  # the controller has not been defined.
+            show_error(404, "Controller doesn't exist")
+        except Exception, e:
+            show_error(500, e)
+        else:
+            try:
+                ctrl.attrs
+                action = getattr(ctrl, route['action'], None)
+                if action is not None:
+                    ctrl.implicit_action()
+                    ctrl.before_action()
+                    action()
+                    ctrl.after_action()
+
+                    if not ctrl.has_rendered:
+                        ctrl.render(template=route['action'], values=ctrl.__dict__)
+                else: # invalid action
+                    logging.error('Invalid action `%s` in `%s`' % (route['action'], route['controller']))
+                    try:
+                        raise Exception('invalid action')
+                    except Exception, e:
+                        show_error(500, e)
+            except Exception, e:
+                show_error(500, e)
Binary file ikweb/ikweb/gaeo/dispatch/dispatcher.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/dispatch/router.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import re
+from copy import copy
+import logging
+
+class RuleError(Exception):
+    """Base Error"""
+
+class RuleNoControllerError(RuleError):
+    """No controller"""
+
+class Rule(object):
+    """ Handles each routing rule. """
+    def __init__(self, pattern, **param):
+        super(Rule, self).__init__()
+
+        self.pattern = pattern[:-1] if pattern.endswith('/') else pattern
+        self.regex = self.pattern
+        self.param = param
+        self.matches = re.findall(':([^/\.]+)', self.pattern)
+
+        for i in range(len(self.matches)):
+            self.regex = self.regex.replace(':' + self.matches[i], '([^/\.]+)')
+            self.param[self.matches[i]] = i
+        self.validate()
+
+    def __eq__(self, other):
+        return self.regex == other.regex
+
+    def __getattr__(self, attr):
+        try:
+            return getattr(self, 'param')[attr]
+        except KeyError:
+            raise AttributeError, attr
+
+    def __str__(self):
+        from operator import itemgetter
+        return ', '.join(['%s: %s' % (k, v) for k, v in \
+            sorted(self.param.items(), key = itemgetter(1))])
+
+    def match_url(self, url):
+        if url.endswith('/'):
+            url = url[:-1]
+        try:
+            mat = re.findall(self.regex, url)[0]
+        except IndexError:
+            return None
+
+        param = copy(self.param)
+        if isinstance(mat, basestring):
+            if self.matches:
+                param[self.matches[0]] = mat
+        elif isinstance(mat, tuple):
+            for i in range(len(mat)):
+                param[self.matches[i]] = mat[i]
+        return param
+
+    def url_for(self, controller, **param):
+        param['controller'] = controller
+        url = self.pattern
+        for match in self.matches:
+            if match not in param:
+                return None
+            url = url.replace(':' + match, str(param[match]))
+            del param[match]
+
+        # extra parameters
+        ep = '&'.join(['%s=%s' % (k, v) for k, v in param.items() if k not in self.param])
+
+        return url + '?' + ep if ep else url
+
+    def validate(self):
+        if 'controller' not in self.param:
+            raise RuleNoControllerError
+
+        if 'action' not in self.param:
+            self.param['action'] = 'index'
+
+        if not self.regex.startswith('^'):
+            self.regex = '^' + self.regex
+        if not self.regex.endswith('$'):
+            self.regex = self.regex + '$'
+
+
+class Router:
+    """ Handles the url routing... """
+
+    class __impl:
+        def __init__(self):
+            self.__routing_root = {
+                'controller': 'welcome',
+                'action': 'index',
+            }
+            self.__routing_table = []
+            # used to store default pattern (but match last)
+            self.__routing_table_fallback = [
+                Rule('/:controller/:action'),
+                Rule('/:controller')
+            ]
+
+        def connect(self, pattern, **tbl):
+            """ Add routing pattern """
+
+            rule = Rule(pattern, **tbl)
+            if rule not in self.__routing_table:
+                self.__routing_table.append(rule)
+
+        def disconnect(self, pattern):
+            rule = Rule(pattern)
+            if rule in self.__routing_table:
+                self.__routing_table.remove(rule)
+
+        def root(self, **map):
+            """ Set the root (/) routing... """
+            self.__routing_root['controller'] = \
+                map.get('controller', self.__routing_root['controller'])
+            self.__routing_root['action'] = \
+                map.get('action', self.__routing_root['action'])
+
+        def resolve(self, url):
+            """ Resolve the url to the correct mapping """
+
+            if url == '/':
+                return self.__routing_root
+
+            ret = self.__resolve_by_table(url, self.__routing_table)
+            if ret is None: # fallback
+                ret = self.__resolve_by_table(url, self.__routing_table_fallback)
+            return ret
+
+        def __resolve_by_table(self, url, rules):
+            """ Resolve url by the given table """
+            for r in rules:
+                ret = r.match_url(url)
+                if ret:
+                    return ret
+            return None
+
+        def url_for(self, controller, **param):
+            for r in self.__routing_table:
+                ret = r.url_for(controller, **param)
+                if ret:
+                    return ret
+            return None
+
+    __instance = None
+
+    def __init__(self):
+        if Router.__instance is None:
+            Router.__instance = Router.__impl()
+        self.__dict__['_Router__instance'] = Router.__instance
+
+    def __getattr__(self, attr):
+        return getattr(self.__instance, attr)
+
+    def __setattr__(self, attr, value):
+        return setattr(self.__instance, attr, value)
+
Binary file ikweb/ikweb/gaeo/dispatch/router.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/model/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 GAEO Team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""GAEO model package
+"""
+import re
+
+from google.appengine.ext import db
+
+def pluralize(noun):
+    if re.search('[sxz]$', noun):
+        return re.sub('$', 'es', noun)
+    elif re.search('[^aeioudgkprt]h$', noun):
+        return re.sub('$', 'es', noun)
+    elif re.search('[^aeiou]y$', noun):
+        return re.sub('y$', 'ies', noun)
+    else:
+        return noun + 's'
+
+class BaseModel(db.Model):
+    """BaseModel is the base class of data model."""
+
+    @classmethod
+    def belongs_to(cls, ref_cls):
+        """ Declare a many-to-one relationship """
+        if ref_cls is None:
+            raise Exception('No referenced class')
+        
+        ref_name = ref_cls.__name__.lower()
+        if ref_name not in cls._properties:
+            attr = db.ReferenceProperty(ref_cls, collection_name=pluralize(cls.__name__.lower()))
+            cls._properties[ref_name] = attr
+            attr.__property_config__(cls, ref_name)
+
+    @classmethod
+    def has_and_belongs_to_many(cls, ref_cls):
+        if ref_cls is None:
+            raise Exception('No referenced class')
+        
+        f_name = pluralize(cls.__name__.lower())
+        t_name = pluralize(ref_cls.__name__.lower())
+        
+        if t_name not in cls._properties:
+            attr = db.ListProperty(db.Key)
+            cls._properties[t_name] = attr
+            attr.__property_config__(cls, t_name)
+        if f_name not in ref_cls._properties:
+            attr = property(lambda self: cls.gql('WHERE %s = :1' % t_name, self.key()))
+            ref_cls._properties[f_name] = attr
+            attr.__property_config__(ref_cls, f_name)
+    
+    @classmethod
+    def named_scope(cls, name, order_by=None, **conds):
+        if name not in cls._properties:
+            cond_str = "WHERE "
+            for cond in conds.iterkeys():
+                if len(cond_str) > 6:
+                    cond_str += ' AND '
+                cond_str += '%s %s' % (cond, conds[cond])
+                
+            if order_by:
+                cond_str += ' ORDER BY %s' % order_by
+                
+            attr = property(lambda self: cls.gql(cond_str))
+            cls._properties[name] = attr
+            attr.__property_config__(cls, name)
+    
+    def update_attributes(self, kwd_dict = {}, **kwds):
+        """Update the specified properties"""
+        need_change = False
+        
+        # if user passed a dict, merge to kwds (Issue #3)
+        if kwd_dict:
+            kwd_dict.update(kwds)
+            kwds = kwd_dict
+        
+        props = self.properties()
+        for prop in props.values():
+            if prop.name in kwds:
+                if not need_change:
+                    need_change = True
+                prop.__set__(self, kwds[prop.name])
+        
+        if need_change:
+            self.update()
+
+    def set_attributes(self, kwd_dict = {}, **kwds):
+        """set the specified properties, but not update"""
+        
+        # Issue #3
+        if kwd_dict:
+            kwd_dict.update(kwds)
+            kwds = kwd_dict
+        
+        props = self.properties()
+        for prop in props.values():
+            if prop.name in kwds:
+                prop.__set__(self, kwds[prop.name])
+        
+    def save(self):
+        self.put()
+        
+    def update(self):
+        self.put()
+        
Binary file ikweb/ikweb/gaeo/model/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/session/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+""" GAEO Session package """
+import logging
+from random import choice
+from string import digits, letters
+
+SESSIONID_LEN = 64
+POOL = digits + letters
+
+class Session(dict):
+    """ Session is an abstract class that declares sessions basic
+    operations. """
+
+    def __init__(self, hnd, name, timeout):
+        """The Session's constructor.
+
+        @param hnd      The webapp.ReqeustHanlder object.
+        @param name     The session name.
+        @param timeout  The time interval (in sec) that the session expired.
+        """
+
+        dict.__init__(self)
+        self._name = name
+        self._hnd = hnd
+        self._timeout = timeout
+        self._id = ''.join([ choice(POOL) for i in range(SESSIONID_LEN) ])
+
+        self._invalidated = False
+
+    def save(self):
+        pass
+
+    def invalidate(self):
+        pass
Binary file ikweb/ikweb/gaeo/session/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/session/memcache.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Lin-Chieh Shangkuan & Liang-Heng Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+""" GAEO Session - memcache store """
+import random
+import pickle
+import logging
+
+from google.appengine.api import memcache
+
+from gaeo import session
+
+class MemcacheSession(session.Session):
+    """ session that uses memcache """
+
+    def __init__(self, hnd, name = 'gaeo_session', timeout = 60 * 60):
+        super(MemcacheSession, self).__init__(hnd, name, timeout)
+
+        # check from cookie
+        if name in hnd.request.cookies:
+            self._id = hnd.request.cookies[name]
+            session_data = memcache.get(self._id)
+            if session_data:
+                self.update(pickle.loads(session_data))
+                memcache.set(self._id, session_data, timeout)
+        else:   # not in the cookie, set it
+            cookie = '%s=%s' % (name, self._id)
+            hnd.response.headers.add_header('Set-Cookie', cookie)
+
+    def put(self):
+        if not self._invalidated:
+            memcache.set(self._id, pickle.dumps(self.copy()), self._timeout)
+
+    def invalidate(self):
+        """Invalidates the session data"""
+        self._hnd.response.headers.add_header(
+            'Set-Cookie',
+            '%s=; expires=Thu, 1-Jan-1970 00:00:00 GMT;' % self._name
+        )
+        memcache.delete(self._id)
+        self.clear()
+        self._invalidated = True
Binary file ikweb/ikweb/gaeo/session/memcache.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/view/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 GAEO Team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+""" GAEO View package """
Binary file ikweb/ikweb/gaeo/view/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/view/helper/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 GAEO Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""" GAEO view helper module """
\ No newline at end of file
Binary file ikweb/ikweb/gaeo/view/helper/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/gaeo/view/helper/ajax.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 GAEO Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""" GAEO view ajax helpers """
+
+def link_to_function(title, script, html = {}):
+    """ Return a link that contains onclick scripts """
+    html_part = ''
+    if html:
+        for key, val in html.items():
+            space = ' ' if html_part else ''
+            html_part = '%s%s="%s"' % (space, key, val)
+        
+    return '<a href="#" %s onclick="%s;return false;">%s</a>' % (html_part, script, title)
+
+
+def javascript_tag(scripts):
+    """ Wraps scripts with <script> tag. """
+    return '<script type="text/javascript">%s</script>' % scripts
+
+
+def remote_script(url, **opts):
+    """ Create a ajax request script """
+    method = opts.get('method', 'get');
+    # request parameters
+    data = opts.get('data', '')
+    # callback function
+    callback = opts.get('callback', '')    
+    dataType = opts.get('dataType', '')
+
+    params = "'%s'" % data if data else ''
+    if callback:
+        cbf = 'function(data, textStatus){%s}' % callback
+        params = params + ', %s' % cbf if params else cbf
+    params = params + ", '%s'" % dataType if dataType else params
+    if params:
+        params = ', ' + params
+
+    return """$.%s('%s'%s)""" % (method, url, params)
+
+def link_to_remote(title, url, **opts):
+    """ Create a link that request a remote action (jQuery-based) """
+    # check if the title is specified
+    if not title:
+        raise Exception('Missing title of the link.')
+    
+    # remote action
+    if not url:
+        raise Exception('Missing remote action.')
+    
+    script = remote_script(url, **opts)    
+    return link_to_function(title, script, opts.get('html', {}))
+
+
+def load_from_remote(title, url, target, **opts):
+    """ Create a link that load data from a remote action (jQuery-based) """
+    # check if the title is specified
+    if not title:
+        raise Exception('Missing title of the link.')
+
+    # remote action url
+    if not url:
+        raise Exception('Missing remote action.')
+        
+    # load target #id
+    if not target:
+        raise Exception('Missing the id of loaded data target.')
+        
+    data = opts.get('data', '')
+    callback = opts.get('callback', '')    
+    params = data
+    if callback:
+        cbf = 'function(){%s}' % callback
+        params = '%s,%s' % (params, cbf) if params else cbf
+    
+    params = ', ' + params if params else ''
+    script = """$('#%s').load('%s'%s);""" % (target, url, params)
+    
+    return link_to_function(title, script, opts.get('html', {}))
+
+
+def periodically_call_remote(url, **opts):
+    """ Periodically call a remote action. (jQuery-based) """
+    if not url:
+        raise Exception('Missing remote action url.')
+    
+    # frequency, default 1000 ms
+    freq = opts.get('frequency', 1000)
+    script = "setInterval(function(){%s}, %s)" % (remote_script(url, **opts), freq)
+    return javascript_tag(script)
+    
Binary file ikweb/ikweb/gaeo/view/helper/ajax.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/index.yaml	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,23 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run.  If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED").  If you want to manage some indexes
+# manually, move them above the marker line.  The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
+
+# Used once in query history.
+- kind: Player
+  properties:
+  - name: game_id
+  - name: lastupdate_at
+
+# Used 4 times in query history.
+- kind: Player
+  properties:
+  - name: lastupdate_at
+    direction: desc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/main.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,56 @@
+import logging
+import os
+import re
+import sys
+import wsgiref.handlers
+
+from google.appengine.ext import webapp
+
+import gaeo
+from gaeo.dispatch import router
+
+def initRoutes():
+    r = router.Router()
+    
+    #TODO: add routes here
+
+    r.connect('/:controller/:action/:id')
+
+def initPlugins():
+    """
+    Initialize the installed plugins
+    """
+    plugins_root = os.path.join(os.path.dirname(__file__), 'plugins')
+    if os.path.exists(plugins_root):
+        plugins = os.listdir(plugins_root)
+        for plugin in plugins:
+            if not re.match('^__', plugin):
+                try:
+                    exec('from plugins import %s' % plugin)
+                except ImportError:
+                    logging.error('Unable to import %s' % (plugin))
+                except SyntaxError:
+                    logging.error('Unable to import name %s' % (plugin))
+
+def main():
+    # add the project's directory to the import path list.
+    sys.path.append(os.path.dirname(__file__))
+    sys.path.append(os.path.join(os.path.dirname(__file__), 'application'))
+
+    # get the gaeo's config (singleton)
+    config = gaeo.Config()
+    # setup the templates' location
+    config.template_dir = os.path.join(
+        os.path.dirname(__file__), 'application', 'templates')
+
+    initRoutes()
+    # initialize the installed plugins
+    initPlugins()
+
+    app = webapp.WSGIApplication([
+                (r'.*', gaeo.MainHandler),
+            ], debug=True)
+    wsgiref.handlers.CGIHandler().run(app)
+
+if __name__ == '__main__':
+    main()
Binary file ikweb/ikweb/plugins/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/plugins/ikariam/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,105 @@
+def load_model(modelname):
+    try:
+        return eval(modelname)
+    except NameError:
+        module = __import__("model.%s" % modelname.lower(), {}, {}, [modelname])
+        return getattr(module,modelname)
+        
+class IkariamModelPlugin:
+    
+    server = "s4"
+    base_url = "http://%s.ikariam.tw" % server
+    
+    @classmethod
+    def get_by_gameid(cls, id):
+        obj = cls.gql("WHERE game_id ="+str(id)+" ORDER BY lastupdate_at").get()
+        return obj
+    
+    def _mk_attrs(self):
+        from copy import copy
+        attrs = copy(self.params)
+        
+        for ref_name in ref_attrs:
+            try:
+                modelname = self.params[ref_name].replace('_id','')
+                model = load_model(modelname)
+                obj = model.get_by_game_id(self.params[ref_name])            
+                attrs[modelname] = obj
+            except KeyError:
+                pass
+        
+        return self._tr_attrtype(attrs, 'int', int_attrs)    
+
+class IkaraimControllerPlugin:
+
+    def _get_modelname(self):
+        return self.__class__.__name__.replace('Controller','')
+
+    def attrs(self):                       
+        modelname = self._get_modelname()
+        model = load_model(modelname)
+        
+        def fn(e):
+            if e not in getattr(model, 'unshow_attrs', []) and \
+               e not in ['__module__','__doc__','_properties',
+                         'unshow_attrs', 'int_attrs', 'lastupdate_at']:
+                return e
+        
+        self.ui().render_result(200, '<br/>'.join(filter(fn, model.__dict__.keys())))
+
+    def show(self):
+        datas = self._get_modeldatas()
+        
+        content = []        
+        for k,v in datas.items():
+            content.append("%s:%s<br/>"%(k,v))
+        
+        self.ui().render_result(200, ''.join(content))
+    
+    def json(self):
+        datas = self._get_modeldatas()
+        self.ui().render_json_of(datas)
+  
+    def _get_modeldatas(self):
+        modelname = self._get_modelname()
+        model = load_model(modelname)
+        obj = model.get_by_gameid(self.params['id'])  
+        return self._tr_attrtype(obj._entity,'str')
+    
+    def create(self):
+        self._update('create')
+
+    def edit(self):
+        self._update('edit')
+        
+    def _update(self, mode='create'):
+        modelname = self._get_modelname()
+        model = load_model(modelname)
+        
+        if 'edit' == mode:
+            obj = model.get_by_gameid(self.params['game_id'])
+        else:
+            obj = model
+        attrs = self._mk_attrs()
+        obj.update_attributes(attrs)
+        obj.save()  
+        
+    def _tr_attrtype(self, attrs, type, include_attrs=None):
+        
+        if not include_attrs:
+            attrnames = attrs.keys()
+        else:
+            attrnames = include_attrs    
+        
+        for attrname in attrnames:
+            try:
+                attrs[attrname] = eval(type)(attrs[attrname])
+            except KeyError:
+                pass
+        return attrs        
+
+from gaeo.model import BaseModel
+BaseModel.__bases__ += (IkariamModelPlugin,)
+
+from gaeo.controller import BaseController
+BaseController.__bases__ += (IkaraimControllerPlugin,)
\ No newline at end of file
Binary file ikweb/ikweb/plugins/ikariam/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/ikweb/plugins/lazyui/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Hsin Yi, Chen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+from gaeo.view.helper import ajax
+
+class LazyUiPlugin:
+    
+    def ui(self, frontend = 'html'):
+        """
+        get web page ui helper.
+        # close dialog window.
+        self.ui().close_dialog();
+        """ 
+        cls = 'Lazy'+frontend.capitalize()+'Ui'
+        self.__dict__.setdefault('_uihelper', eval(cls)(self) )
+        return self.__dict__.get('_uihelper')
+             
+class LazyHtmlUi:
+    __instance = None
+    
+    def __init__(self, ctrl):
+    	"""
+    	Controller Decoration.
+    	"""
+        if LazyHtmlUi.__instance is None:
+            LazyHtmlUi.__instance = ctrl
+    
+    def __getattr__(self, attr):
+        return getattr(self.__instance, attr)
+
+    def __setattr__(self, attr, value):
+        return setattr(self.__instance, attr, value)    
+    
+    def close_dialog(self, **after_act):
+        """
+        close web borwser diaolog window.
+        
+        # close the window and reload parent page.
+        close_dialog(after_act = {'action':'update_parent'}
+        
+        # using custom javascript code to controll parent page, for excample
+        # remove a block.
+        callback_js = self.create_controlls_parent_js('Editor.inserHTML', 'hello')
+        close_dialog(after_act = {'msg':'sucess.','action':'update_parent', 'callback':callback_js})
+                
+        # close the window and redirect.
+        close_dialog(after_act = {'action':'redirect','to_url':'http://www.example.com'})        
+        """
+        scripts = after_act.get('msg') and "alert('%s');" % after_act.get('msg')
+       
+        if 'redirect' == after_act.get('action'):
+            self.redirect(after_act.get('to_url', '/') )
+        elif 'update_parent' == after_act.get('action'):
+            if after_act.get('callback'):
+                scripts += after_act.get('callback')
+            else:
+            	scripts += "window.parent.location.reload();"
+          	        
+        scripts += "window.close();"
+        self.render_js(scripts)
+
+    def controlls_parent(self, ctrl, *argvs ):
+        """
+        calls parent window function, or object method.
+        
+        # insert a JPG image with 500x500 size to parent window.
+        controlls_parent_window('ImgEditor.insert', [ '/images/no.jpg', 500, 500] )
+        """
+        scripts = self.create_controlls_parent_window_js(ctrl, argvs)
+        self.render_js(scripts)
+        
+    def create_controlls_parent_js(self, ctrl, *argvs):
+    	return "window.parent.%s(%s);" % ( ctrl, str(argvs) and ','.join( [ '"'+str(x)+'"' for x in argvs ] ) )
+        
+    def alert_and_redirect(self, msg = '', dest_url = None):
+        """
+        alert message and redirect to next url if destention url is speficied,
+        otherwise, the web siet viewer will be redirect back to his last browsed page.        
+        
+        alert_and_redirect('action success!', 'http://example.com')
+        """
+        scripts = "alert('%s');" % msg
+        if dest_url:
+            scripts += "document.location.href='%s'" % ( msg, dest_url )
+        else:
+            scripts += "window.back();"
+        self.render_js(scripts)    
+    
+    def render_result(self, http_code=200, content=''):
+        """
+        display result info for frontend controller.
+		for example, swfupload file uploader requires a page that 
+		displays http state code to know the result after uploading
+     	files is finished.
+        """
+        self.hnd.error(http_code)
+        self.render(content)
+        
+    def render_js(self, scripts):
+        """
+        render javasciprt code to output.
+        
+        render_js("alert('hellow');document.location.href='/'")
+        """
+        self.render(ajax.javascript_tag(scripts))
+        
+    def render_json_of(self, obj):
+        """
+        render JSON of an object
+        """
+        self.render(self.to_json(obj))        
+
+    def render_xml_of(self, obj):
+        """
+        render XML of an object
+        """
+        self.render(self.to_xml(obj))
+
+from gaeo.controller import BaseController
+BaseController.__bases__ += (LazyUiPlugin,) 
Binary file ikweb/ikweb/plugins/lazyui/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/rebuild_world	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,8 @@
+#/bin/bash
+EAGLE_EYE=$HOME/lazyproject/eagle-eye
+echo "setup ikariam web site."
+
+db_schema=$EAGLE_EYE/ikariam.sqlite
+echo "load sqlite file:"$db_schema
+cd ikweb
+python ../tools/lazygen_model.py sqlite:///$db_schema
Binary file ikweb/tools/dummy.sqlite has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/functional_test.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,29 @@
+from lazy.www import c
+
+def create_player():
+    datas = {'game_id':1,
+             'army_score_main':10000,
+             'building_score_main':1000,
+             'building_score_secondary':100,
+             'name':'tplayer',
+             'research_score_main':122,
+             'research_score_secondary':122,
+             'score':10000,
+             'trader_score_secondary':1000000    
+    }
+    c("http://localhost:8080/player/create").post(datas)
+    
+def edit_player():
+    datas = {'game_id':1,
+             'name':'tfplayer'
+             }
+    c("http://localhost:8080/player/edit").post(datas)
+    
+import sys
+
+act = sys.argv[1]
+modelname = sys.argv[2]
+
+funcname = "%s_%s" % (act,modelname)
+print 'test',funcname
+eval(funcname)()
Binary file ikweb/tools/lazy/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/README.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,16 @@
+"""
+Requirements:
+
+	lxml - python libxml binding
+
+	it needs to installing the following packages before install lxml.
+
+    *  libxml 2.6.21 or later. It can be found here: http://xmlsoft.org/downloads.html
+    *  libxslt 1.1.15 or later. It can be found here: http://xmlsoft.org/XSLT/downloads.html
+		
+	If you use Ubuntu, here is what you need to do.
+	
+	$ apt-get install libxml2-dev libxslt1-dev
+	$ eazy_install lxml
+	
+"""
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Hsin Yi, Chen
+"""
+    [Note] the project is not available yet.
+
+    A web page fetcing tool chain that has a JQuery-like selector and supports chain working.
+    
+    Here is an exmaple can show the the main idea, To restrive a content you want
+    in a div box in a web page, and then post and restrive next content in the other
+    web page with the param you just maked from the content in first restriving.
+    finally, storage the production.
+    
+    def func(s):
+        return {'msg':s}
+    
+    try:
+        c("http://example.tw/").get().find("////ul/text()") \
+            .build_param( func ).post_to("http://example2.com") \
+            .save_as('hellow.html')
+    except:
+        pass
+        
+    more complex example
+        
+    try:
+        c("http://example.tw/").retry(4, '5m').get() \
+            .find("#id > div"). \
+            .build_param( func ).post_to("http://example2.com") \
+            .save_as('hellow.html') \
+            .end().find("#id2 > img").download('pretty-%s.jpg'). \
+            tar_and_zip("pretty_girl.tar.gz")
+    except NotFound:
+        print "the web page is not found."
+    except NoPermissionTosave:
+        print "the files can not be save with incorrect permission."
+    else:
+        print "unknow error."
+"""
+from lazy.www.work import WorkFlow
+from lazy.www.work.fetch import Fetcher
+from lazy.www.work.storage import FileStorager
+from lazy.www.core import SemiProduct
+import os
+import sys
+import re
+
+def parse_scheme(scheme):
+    try:
+        return re.findall("(\w+):\/\/(.*\/?)",scheme)[0]
+    except:
+        sys.stdout.write("the scheme is not supported.")
+        sys.exit()
+
+def c(scheme, worker=None):
+    """
+    connect to a web apge
+    
+    >>> c('http://localhost:8080').get().worker.working_product.content
+    'It works!!\\n'
+    
+    >>> c('http://localhost:8080').get().find('//text()')
+    'It works!!\\n'    
+    """
+    target_type, path = parse_scheme(scheme)
+
+    #@todo: SemiProduct Factory.
+    if worker is None:
+        if 'file' == target_type:
+            s= SemiProduct(source=path)        
+            worker = FileStorager(s)
+        else:
+            s= SemiProduct(source=scheme)        
+            worker = Fetcher(s)
+
+    return WorkFlow(worker)
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
Binary file ikweb/tools/lazy/www/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/core/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,12 @@
+                          
+class SemiProduct:
+
+    last_work = None
+    source = None
+    content = None
+    
+    def __init__(self, **kwds):
+        self.source = kwds.get('source','')        
+        
+    def __str__(self):        
+        return self.content
\ No newline at end of file
Binary file ikweb/tools/lazy/www/core/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/core/utils.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,4 @@
+
+def mix_in(py_class, mixin_class):
+    if mixin_class not in py_class.__bases__:
+        py_class.__bases__ += (mixin_class,)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/work/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,11 @@
+from lazy.www.work.fetch import Fetcher
+from lazy.www.work.find import Finder
+from lazy.www.core import SemiProduct
+
+class WorkFlow:
+
+    def __init__(self, worker):
+        self.worker = worker
+        
+    def __getattr__(self, name):
+        return getattr(self.worker, name)
Binary file ikweb/tools/lazy/www/work/__init__.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/work/fetch.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,72 @@
+import urllib2
+import urllib
+import cookielib
+import os
+
+class Fetcher:
+    
+    opener = None
+    
+    working_product = None
+    
+    """
+    A Semi Production Decoration for content fetching.
+    
+    handles content restriving.
+    
+    >>> o = Fetcher( SemiProduct(source="http://localhost:8080") )
+    >>> o.get().working_product.content
+    'It works!!\\n'
+    """
+    def __init__(self, working_product):
+        self.working_product = working_product
+    
+    def get(self, data = {}):
+        """        
+        send datas via http get method.
+        """        
+        res = urllib2.urlopen(self.working_product.source, urllib.urlencode(data))
+        return res.read()
+    
+    def post(self, data = {} ):
+        """
+        send datas via http post method.
+        
+        >>> o = Fetcher( SemiProduct(source="http://localhost:8080") )
+        >>> o.post({'a':'b'}).working_product.content
+        'It works!!\\n'
+        """        
+        res = urllib2.urlopen(self.working_product.source, urllib.urlencode(data))
+        return res.read()    
+
+    def refer(self, refer_url):
+        """
+        refer getter/setter.
+
+        >>> o = Fetcher( SemiProduct(source="http://localhost:8080") )
+        >>> o.refer('http://www.example.com')        
+        """
+        raise NotImplementedError
+
+    def retry(self, count = 0, intval = 0, timeout = 0):
+        """
+        retry to fetch the content.
+
+        >>> o = Fetcher( SemiProduct(source="http://localhost:8080") )
+        >>> o.retry(4)        
+        """        
+        raise NotImplementedError
+    
+class Retry:
+    
+    """
+     A Fetcher Decoration for retry goal.
+     
+     
+    """
+    def __init__(self, fetcher):
+        raise NotImplementedError
+    
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
\ No newline at end of file
Binary file ikweb/tools/lazy/www/work/fetch.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/work/find.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,27 @@
+from lxml import etree
+from cStringIO import StringIO
+
+class Finder:
+
+    dom_tree = None
+    xpath = None
+
+    def __init__(self, working_product):
+        self.working_prodcut = working_product
+
+        self.encoding = 'utf8'
+    
+    def find(self, express , callback = None):
+        
+        if self.dom_tree is None:   self.set_dom_tree(self.working_prodcut.content)
+ 
+        xpath = self.dom_tree.xpath(express)
+        
+        if callback:    return self.callback(xpath)
+        return xpath
+
+    def set_dom_tree(self, content):
+        stream = StringIO(content)
+
+        parser = etree.XMLParser(encoding=self.encoding)
+        self.dom_tree = etree.parse(stream, parser)
\ No newline at end of file
Binary file ikweb/tools/lazy/www/work/find.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazy/www/work/storage.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,34 @@
+class FileStorager:
+    
+    opener = None    
+    working_product = None
+    
+    """
+    A Semi Production Decoration for content storaging.
+    
+    handles content storaging.
+    
+    >>> o = Fetcher( SemiProduct(source="file:///tmp/a.txt") )
+    >>> o.get().working_product.content
+    'It works!!\\n'
+    """
+    def __init__(self, working_product):
+        self.working_product = working_product
+    
+    def get(self, data = {}):
+        """        
+        send datas via http get method.
+        """        
+        res = open(self.working_product.source)
+        return res.read()
+    
+    def post(self, data = {} ):
+        """
+        send datas via http post method.
+        
+        >>> o = Fetcher( SemiProduct(source="file:///tmp/a.txt") )
+        >>> o.post({'a':'b'}).working_product.content
+        'It works!!\\n'
+        """        
+        res = open(self.working_product.source)
+        return res.read()
\ No newline at end of file
Binary file ikweb/tools/lazy/www/work/storage.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ikweb/tools/lazygen_model.py	Tue Dec 02 17:21:21 2008 +0800
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+import re
+import os
+import sys
+
+from sqlalchemy import *
+# dirty import XD
+gaeo_home = os.getenv('GAEO_HOME').replace('~',os.getenv('HOME'))+'/bin'
+try:
+    sys.path.append(gaeo_home)
+    from gaeogen import *
+except ImportError, e:
+    print "can not import gaeogen, the gaeogen path maybe wrong, got path:%s" % gaeo_home
+    sys.exit()
+
+
+def sqlite_column_mapper(model_class, table_obj):
+    """
+    map sqlite columns of the table to GAEO Model
+    
+    >>> metadata = MetaData()
+    >>> users_table = Table('users', metadata,
+    ...     Column('id', Integer, primary_key=True),
+    ...     Column('name', String),
+    ...     Column('fullname', String),
+    ...     Column('password', String)
+    ... )
+    >>> model = sqlite_column_mapper(GenModel, users_table)
+    >>> model.generate_content()
+    """
+    def convertor(col_type):
+        lowertype = col_type.lower()
+
+        if re.match('char', lowertype): col_type = "String"
+        if re.match('(int|integer)', lowertype):    col_type = 'Integer'
+        return col_type
+    
+    model = model_class(table_obj.name)
+    
+    for column in table_obj.columns:        
+        model.add_property( "%s" % GenProperty(column.name, 
+                                               column, 
+                                               convertor))
+    return model
+
+class GenProperty(object):
+    """
+    GAE Property Generator.
+        
+    class Property(verbose_name=None, name=None, default=None, required=False, validator=None, choices=None)
+    """
+    db_prefix = "^(SL|PG|Max|MS|FB|Ac|Info|Oracle)"        
+
+    def __init__(self, name, column=False, convertor=False):
+        """
+        >>> col = Column('summary', String(2000))
+        >>> p = GenProperty('summary', col)
+        >>> "%s" % p
+        "summary:StringProperty(verbose_name='summary',name='summary')"
+        >>> meta = MetaData()
+        >>> meta.bind = create_engine("sqlite:///dummy.sqlite")
+        >>> t = Table('ally', meta, autoload=True)        
+        >>> p = GenProperty('name', t.columns.get('name'))
+        >>> "%s" % p
+        "name:TextProperty(verbose_name='name',name='name')"
+        >>> p = GenProperty('name', t.columns.get('time'))
+        >>> "%s" % p
+        "name:DatetimeProperty(verbose_name='name',name='name')"
+        """
+        self.name = name
+        self.column = column
+        
+        # very dirty ...
+        if column:
+                self.type_name = re.sub( self.db_prefix,'',
+                                         column.type.__class__.__name__)
+                if convertor:
+                    self.type_name = convertor(self.type_name)
+        
+    def __str__(self):
+        return self.gen()
+    
+    def gen(self):
+        """
+        generate gaeo property code.
+        """
+        clause= self.create_clause(self.type_name, self.column) 
+        return gae_property_code(self.name, self.type_name, clause)        
+    
+    def create_clause(self, type, column):
+        """
+        create property cluase.
+                
+        >>> s = Column('summary', String(2000))
+        >>> code = GenProperty(s.name).create_clause('string', s)
+        >>> code
+        {'verbose_name': 'summary', 'name': 'summary'}
+        """
+        clause= self.create_basic_clause(column)
+        
+        try:
+            other_caluse = getattr(self, 'create_%s_clause' % type.lower())(column)
+            clause.update(other_caluse)            
+        except:
+            pass
+        
+        return clause
+    
+    def create_basic_clause(self, column):
+        """
+        create basic property cluase.
+        """
+        clause = {'verbose_name':self.name, 'name':self.name}
+            
+        if column.default:
+            clause.setdefault('default', column.default)
+        elif column.server_default:
+            clause.setdefault('default', column.server_default)
+            
+        return clause
+
+def gae_property_code(name, type, clause):
+    """
+    generate google app engine property code.
+    
+    >>> gae_property_code('name', 'string', {'default':'hychen','name':'hi'})
+    "name:StringProperty(default='hychen',name='hi')"
+    """
+    argv = []
+    
+    def _quote(v):
+        try:
+            return "'"+v+"'"
+        except TypeError:
+            pass
+    
+    for k,v in clause.items():
+        argv.append("%s=%s" % (k, _quote(v) ) )
+
+    return "%s:%sProperty(%s)" % (name, 
+                                  type.capitalize(),
+                                   ','.join(argv))
+
+class ModelMaker:
+
+    is_build = False
+    
+    def __init__(self, **kwds):
+        """
+        to create GAEO Model
+        
+        >>> maker = ModelMaker(schema="sqlite:///dummy.sqlite", autoload=True)
+        >>> maker.build()        
+        """
+        self.models = []
+        self.set_engine(kwds['schema'])
+        
+        try:                        
+            if True == kwds['autoload']:
+                self.table_names = self.db_engine.table_names()
+            else:
+                self.table_names = kwds['include_tables']
+        except KeyError:
+            print "input wrong argv."
+        
+    def set_engine(self, schema):
+        """
+        set db engine
+        """
+        self.db_engine = create_engine(schema)        
+        self.meta = MetaData()        
+        self.meta.bind = self.db_engine
+
+    def build(self):
+        """
+        build models by talbse in database.
+        """
+        mapper_func = '%s_column_mapper' % self.db_engine.name.lower()
+        
+        for table_name in self.table_names:
+            table = Table(table_name, self.meta, autoload=True)            
+            self.models.append( eval(mapper_func)( GenModel, table) )
+            
+        self.is_build = True
+            
+    def save(self):
+        """
+        save model
+        """
+        if self.is_build:
+            application_dir = os.path.join(os.getcwd(), 'application')
+            model_dir = os.path.join(application_dir, 'model')
+                        
+            # check if the model directory had been created
+            if not os.path.exists(os.path.join(model_dir, '__init__.py')):
+                create_file(os.path.join(model_dir, '__init__.py'), [])
+                                        
+            for model in self.models:
+                print 'Creating Model %s ...' % model.name
+                model.save(os.path.join(model_dir, '%s.py' % model.name))
+        else:
+            print "not build yet."
+
+def gen_models_from_db(db_schema, include_tables=False):
+    """
+    generate models form database.
+    
+    gen_models_from_db("sqlite://dumy.sqlite")
+    
+    # spefiy tables
+    gen_models_from_db("sqlite://dumy.sqlite", include_tables=['tb1','tb2'])
+    """
+    if not include_tables:
+        maker = ModelMaker(schema=db_schema, autoload=True)
+    else:
+        maker = ModelMaker(schema=db_schema, include_tables=include_tables)
+        
+    maker.build()
+    maker.save()
+
+if '__main__' == __name__:
+    import sys
+    
+    if 'test' == sys.argv[1]:    
+        import doctest
+        doctest.testmod()
+    else:
+        gen_models_from_db(sys.argv[1])
--- a/pyikriam/__init__.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -1,3 +1,94 @@
-from ikariam import Ikariam
+from lazy.www import c
+from lconf import LoadConfigfile
+import cookielib
+import os
+import urllib2
+import urllib
+class Ikariam:
+
+    cities = {}
+
+    def __init__(self, DEBUG=False):
+        self.COOKIEFILE = '/tmp/ikariam.lwp'
+        self.confdata=LoadConfigfile().cd
+        #self.baseurl='http://'+self.confdata['server']
+        if DEBUG:
+            self.debug = True
+            self.baseurl = self.debug_url()
+        else:
+            self.baseurl = 'http://s4.ikariam.tw'
+            self.cj = cookielib.LWPCookieJar()
+            if os.path.isfile(self.COOKIEFILE):
+                self.cj.load(self.COOKIEFILE)
+     
+            opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
+            opener.addheaders = [('User-agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.8.1.12pre) Gecko/20071220 BonEcho/2.0.0.12pre')]
+            urllib2.install_opener(opener)
+    
+            self.login()
+
+    def debug_url(self):
+        """
+        get the path of testing datas.
+        >>>
+        
+        @return: file path with scheme 'file://'
+        """
+        return 'file://'+ os.path.abspath(os.path.curdir) + '/tests'
 
-__all__ = ('Ikariam',)
+    def login(self):     
+        print "login to %s...." % self.confdata['server']
+        params = {"universe":self.confdata['server'], \
+        "name":self.confdata['user'], \
+        "password":self.confdata['pass']}
+        ret = c(self.baseurl+'/index.php?action=loginAvatar&function=login').get(params).get_content()
+        self.cj.save(self.COOKIEFILE)
+        
+    def logout(self):
+        print "logut from %s...." % self.confdata['server']
+        c(self.baseurl+'/index.php?action=loginAvatar&function=logout')
+        os.remove(self.COOKIEFILE)
+        
+    def find_number_in(self, html, xpath):    
+        return parse_to_int(self.find_data_in(html, xpath))
+    
+    def find_data_in(self, work, xpath):
+        if work is None or xpath is None:   return
+        return work.find(xpath)
+    
+    def city(self, id):
+	       return self.cities.get(id, IkariamCity(id=id, core=self) )
+    
+class IkariamCity:
+    
+    def __init__(self, id, core ):
+        self.core = core
+        self.id = id
+        self.params = {'view':'city','id':id}
+        self.resources = {'gold':0,'wood':0,'wine':0,'marble':0,'crystal':0,'sulfur':0}
+        
+        # define xpath queries
+        self.xpath = {
+                      'gold':"/html/body[@id='city']/div[@id='container']/div[@id='container2']/div[@id='globalResources']/ul/li[2]/a/span[@id='value_gold']/text()",
+                      'wood':"/html/body[@id='city']/div[@id='container']/div[@id='container2']/div[@id='cityResources']/ul/li[3]/span[@id='value_wood']/text()",
+                      'wine':"/////div[@id='cityResources']/ul/li[4]/span[@id='value_wine']/text()",
+                      'marble':"/////div[@id='cityResources']/ul/li[4]/span[@id='value_wine']/text()",
+                      'crystal':"/////div[@id='cityResources']/ul/li[4]/span[@id='value_wine']/text()",
+                      'sulfur':"/////div[@id='cityResources']/ul/li[4]/span[@id='value_wine']/text()"
+                      }
+        
+    def sync(self):
+        print "pull datas of the city %s" % self.id
+                                
+        if self.core.debug:
+            work = c(self.core.baseurl+'/viewcity.html').get()
+        else:
+            work = c(self.core.baseurl).get(self.params)
+            
+        #print work.find(self.xpath['gold']).get_content()
+        print work.find(self.xpath['wood']).get_content()
+        print work.find(self.xpath['wood']).get_content()
+        
+def parse_to_int( str ):    pass
+   # print str[0]
+   # if str: return int( str.strip().replace(',',''))
\ No newline at end of file
--- a/pyikriam/buildings.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/buildings.py	Tue Dec 02 17:21:21 2008 +0800
@@ -1,4 +1,4 @@
-from lazy.www import c
+from lazy.www import c, Resource
 from lxml import etree
 from StringIO import StringIO
 from sync_utils import sync_tagclass, sync_tagvalue
@@ -192,6 +192,25 @@
         pass
     pass
 
+class safehouse(building):
+    def __init__(self, city_id, idx, baseurl):
+        super(shipyard, self).__init__('safehouse', city_id, idx, baseurl)
+        self.data_patterns = {
+                              'vacancy_spys_total':"/::input[@id='spyCount']/@value",
+                              'trained_spys_total':"/descendant::div[@id='mainview']/div[4]/div[1]/p/span/span[2]/text()"}
+    pass
+
+    def __sync__(self, page_dom):        
+        workflow = create_workflow('find', page_dom)
+        attrs = workflow.findall(self.data_patterns).get_content()
+        for attr_name, attr_value in attrs:
+            setattr(self, attr_name, attr_value)
+        pass
+    pass
+
+    def _mk_spy(self):
+        import spys
+
 class empty_pos(position):
     res_patterns = {
         'wood': 'wood',
--- a/pyikriam/ikariam.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/ikariam.py	Tue Dec 02 17:21:21 2008 +0800
@@ -115,3 +115,7 @@
         return build_type
     pass
 
+        
+        
+        
+            
\ No newline at end of file
--- a/pyikriam/lazy/www/__init__.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/lazy/www/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -15,7 +15,7 @@
         return {'msg':s}
     
     try:
-        c("http://example.tw/").get().find("#id > div") \
+        c("http://example.tw/").get().find("////ul/text()") \
             .build_param( func ).post_to("http://example2.com") \
             .save_as('hellow.html')
     except:
@@ -38,10 +38,21 @@
         print "unknow error."
 """
 from lazy.www.work import WorkFlow
-from lazy.www.work.fetch import Fetcher, install_opener
+from lazy.www.work.fetch import Fetcher
+from lazy.www.work.storage import FileStorager
 from lazy.www.core import SemiProduct
+import os
+import sys
+import re
 
-def c(url):
+def parse_scheme(scheme):
+    try:
+        return re.findall("(\w+):\/\/(.*\/?)",scheme)[0]
+    except:
+        sys.stdout.write("the scheme is not supported.")
+        sys.exit()
+
+def c(scheme):
     """
     connect to a web apge
     
@@ -51,14 +62,17 @@
     >>> c('http://localhost:8080').get().find('//text()')
     'It works!!\\n'    
     """
-    s= SemiProduct(source=url)    
-    w = WorkFlow(Fetcher(s))
-    return w
+    target_type, path = parse_scheme(scheme)
 
-def lz_install(**kwds):
-    if('opener' == kwds.get('name')):
-       install_opener(kwds.get('cookiefile'))
+    #@todo: SemiProduct Factory.
+    if 'file' == target_type:
+        s= SemiProduct(source=path)        
+        worker = FileStorager(s)
+    else:
+        s= SemiProduct(source=scheme)        
+        worker = Fetcher(s)
+    return WorkFlow(worker)
 
 if __name__ == '__main__':
     import doctest
-    doctest.testmod()
\ No newline at end of file
+    doctest.testmod()
--- a/pyikriam/lazy/www/core/__init__.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/lazy/www/core/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -9,4 +9,38 @@
         self.source = kwds.get('source','')        
         
     def __str__(self):        
-        return self.content
\ No newline at end of file
+        return self.content
+    
+class Resource(object):
+
+    datas = {}
+    
+    data_patterns = {}
+    
+    def __init__(self, **kwds):
+        self.datas = kwds.get('datas')
+        
+    def __get__(self, key):
+        try:
+            return self.datas[key]
+        except KeyError:
+            return self.key
+        
+    def __set__(self, key, value):
+        try:
+            self.datas[key]
+        except KeyError:
+            self.key = value
+            
+    def sync(self):
+        if not self.data_patterns:  raise AttributeError("data patterns not defined.")
+        
+        express = {}
+        for attr in self.attr_xpath.keys():
+            express[attr] = self.root_xpath+self.attr_xpath[attr]
+
+        def fn(e):
+            for x in e: e[x] = e[x][0]
+            return e
+
+        self.datas = self.core.open(self.param).findall(express).process(fn).get_content()        
\ No newline at end of file
--- a/pyikriam/lazy/www/work/__init__.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/lazy/www/work/__init__.py	Tue Dec 02 17:21:21 2008 +0800
@@ -21,14 +21,15 @@
         return self.working_product.content
      
     def change_worker(self, new_worker):
-        self.serial_number += 1
+        self.serial_number += 1        
+        self.last_work = self
         self.worker = new_worker
         
     def is_fetcher(self, obj):
-        if  obj is not None:    return True
+        if obj.__class__.__name__ == 'Fetcher': return True
     
     def get(self, data = {} ):
-        if not self.is_fetcher(self.worker) :
+        if self.worker.__class__.__name__ != 'FileStorager' and not self.is_fetcher(self.worker) :
             self.change_worker( Fetcher(self.working_product) )
         
         self.working_product.content = self.worker.get(data)
@@ -42,11 +43,31 @@
         return self
     
     def is_finder(self, obj):
-        if obj is not None: return True
+        if obj.__class__.__name__ == 'Finder': return True
+    
+    def findall(self, expresses):
+        if not self.is_finder(self.worker):
+            self.change_worker( Finder(self.working_product) )
+        
+        ret = {}
+        for e in expresses.keys():
+            try:
+                ret[e] = self.worker.find(expresses[e])
+            except:
+                pass
+
+        self.working_product.content = ret
+        
+        return self
     
     def find(self, express):
         #if not self.is_finder(self.worker):
-        self.worker = Finder(self.working_product)
+        self.change_worker( Finder(self.working_product) )
+        self.last_working_product = self.working_product
         self.working_product.content = self.worker.find(express)
         
+        return self
+    
+    def process(self, fn):
+        self.working_product.content = fn(self.working_product.content)
         return self
\ No newline at end of file
--- a/pyikriam/lazy/www/work/fetch.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/lazy/www/work/fetch.py	Tue Dec 02 17:21:21 2008 +0800
@@ -3,17 +3,6 @@
 import cookielib
 import os
 
-def install_opener(cookiefile):
-    COOKIEFILE = cookiefile
-    cj = cookielib.LWPCookieJar()
-    if os.path.isfile(COOKIEFILE):
-        cj.load(COOKIEFILE)
-    else:
-        cj.save(cookiefile)
-        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-        opener.addheaders = [('User-agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.8.1.12pre) Gecko/20071220 BonEcho/2.0.0.12pre')]
-        urllib2.install_opener(opener)
-
 class Fetcher:
     
     opener = None
@@ -31,15 +20,14 @@
     """
     def __init__(self, working_product):
         self.working_product = working_product
-        
-    def get(self, data = {}):
-        """        
-        send datas via http get method.
-        """        
-        res = urllib2.urlopen(self.working_product.source, urllib.urlencode(data))
-        return res.read()
     
-    def post(self, data = {} ):
+    def get(self, **kwds):
+        return self.open(kwds)
+
+    def post(self, **kwds):
+        return self.open(kwds)
+    
+    def open(self, data = {} ):
         """
         send datas via http post method.
         
--- a/pyikriam/lazy/www/work/find.py	Tue Dec 02 17:13:36 2008 +0800
+++ b/pyikriam/lazy/www/work/find.py	Tue Dec 02 17:21:21 2008 +0800
@@ -10,14 +10,18 @@
         self.working_prodcut = working_product
 
         self.encoding = 'utf8'
-        parser = etree.HTMLParser(encoding=self.encoding)
-        self.dom_tree = etree.parse(StringIO(self.working_prodcut.content), parser)
     
     def find(self, express , callback = None):
+        
+        if self.dom_tree is None:   self.set_dom_tree(self.working_prodcut.content)
+ 
         xpath = self.dom_tree.xpath(express)
         
-        if callback is None:
-            ret = xpath
-        else:
-            ret = self.callback(xpath)
-        return ret
+        if callback:    return self.callback(xpath)
+        return xpath
+
+    def set_dom_tree(self, content):
+        stream = StringIO(content)
+
+        parser = etree.HTMLParser(encoding=self.encoding)
+        self.dom_tree = etree.parse(stream, parser)
\ No newline at end of file