source: pycgibattler/trunk/index.cgi @ 89

Revision 89, 10.3 KB checked in by atzm, 13 years ago (diff)

api support (for multi language)

  • Property svn:executable set to *
  • Property svn:keywords set to Id
RevLine 
[57]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
[75]3#
4#  Copyright (C) 2010 by Atzm WATANABE <atzm@atzm.org>
5#
6#  This program is free software; you can redistribute it and/or modify it
7#  under the terms of the GNU General Public License (version 2) as
8#  published by the Free Software Foundation.  It is distributed in the
9#  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
10#  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11#  PURPOSE.  See the GNU General Public License for more details.
12#
[57]13# $Id$
[75]14#
[57]15
16from __future__ import with_statement
17
18import os
19import re
20import sys
21import cgi
[72]22import json
[57]23import glob
24import copy
25import fcntl
[89]26import base64
[57]27import shutil
28import hashlib
[89]29import xmlrpclib
[57]30import ConfigParser
31
32try:
33    import cStringIO as _StringIO
34except ImportError:
35    import StringIO as _StringIO
36
37try:
38    import cPickle as _pickle
39except ImportError:
40    import pickle as _pickle
41
42from Cheetah.Template import Template
43import pycodebattler
44
45
46CONFIG = ConfigParser.SafeConfigParser()
47CONFIG.read('pycgibattler.conf')
48
49
50class ListChooser:
51    def __init__(self, code):
52        self._code = code
53        self._digest = int(hashlib.sha512(code).hexdigest(), 16)
54
55    def lslide(self, i):
56        self._digest <<= i
57
58    def rslide(self, i):
59        self._digest >>= i
60
61    def choose(self, list_):
62        return list_[self._digest % len(list_)]
63
64
65class CharacterManager:
[66]66    VALID_ID = re.compile(r'^[a-f0-9]{128}$')
[57]67    DATA_DIR = os.path.expanduser(CONFIG.get('data', 'basedir'))
68
69    def __init__(self, id_):
70        if not self.VALID_ID.match(id_):
71            raise ValueError('Invalid ID: %s' % id_)
72        self._id = id_
73        self._locker = os.path.join(self.DATA_DIR, '.%s.lck' % id_)
74        self._prefix = os.path.join(self.DATA_DIR, id_)
75        self._codepath = os.path.join(self._prefix, '%s.py' % id_)
76        self._datapath = os.path.join(self._prefix, '%s.dat' % id_)
77
78    def id(self):
79        return self._id
80
81    def mtime(self):
82        with open(self._locker) as fp:
83            fcntl.flock(fp, fcntl.LOCK_SH)
[63]84            return os.fstat(fp.fileno()).st_mtime
[57]85
86    def delete(self):
87        with open(self._locker, 'w') as fp:
88            fcntl.flock(fp, fcntl.LOCK_EX)
89            shutil.rmtree(self._prefix, True)
90            os.remove(self._locker)  # XXX
91
92    def load(self):
93        with open(self._locker) as fp:
94            fcntl.flock(fp, fcntl.LOCK_SH)
95            code = open(self._codepath).read()
96            warrior = _pickle.load(open(self._datapath))
97            return code, warrior
98
[77]99    def entry(self):
[57]100        code, warrior = self.load()
101        return {
102            'id':      self.id(),
103            'mtime':   self.mtime(),
104            'code':    code,
105            'warrior': warrior,
106            'skills':  [warrior.skill(s) for s in warrior.skill_list()],
107        }
108
[77]109    def flatten(self):
110        code, warrior = self.load()
111        skills = []
112        for name in warrior.skill_list():
113            sk = warrior.skill(name)
114            skills.append({
115                'name':  name,
116                'type':  str(sk.skilltype()).split('.')[-1],
117                'level': sk.level(),
118                'point': sk.point(),
119            })
120        return {
121            'id':            self.id(),
122            'mtime':         int(self.mtime()),
123            'name':          warrior.name(),
124            'hitpoint':      warrior.hitpoint(),
125            'skillpoint':    warrior.skillpoint(),
126            'strength':      warrior.strength(),
127            'concentration': warrior.concentration(),
128            'defense':       warrior.defense(),
129            'agility':       warrior.agility(),
130            'luck':          warrior.luck(),
131            'skills':        skills,
132        }
133
[57]134    @classmethod
[89]135    def dump(klass, name, code, data):
[57]136        self = klass(hashlib.sha512(code).hexdigest())
137        cname = os.path.join(self._prefix, os.path.basename(name))
[89]138        warrior = self.make_warrior(code, data)
[57]139
140        with open(self._locker, 'w') as fp:
141            fcntl.flock(fp, fcntl.LOCK_EX)
142
143            try:
144                os.mkdir(self._prefix)
145            except OSError:
146                if not os.path.isdir(self._prefix):
147                    raise
148
149            open(self._codepath, 'w').write(code)
150            _pickle.dump(warrior, open(self._datapath, 'w'))
151
152            try:
153                os.symlink(os.path.basename(self._codepath), cname)
154            except OSError:
155                if not os.path.islink(cname):
156                    raise
157
158        return self
159
160    @classmethod
[89]161    def make_warrior(klass, code, data):
[67]162        chara_names = [x.strip() for x in open(os.path.expanduser(
163            CONFIG.get('character', 'name_list_file'))).xreadlines()]
[73]164        skill_data = json.load(open(os.path.expanduser(
165            CONFIG.get('character', 'skill_list_file'))))
[67]166
[57]167        lc = ListChooser(code)
[67]168        name = lc.choose(chara_names)
[57]169        skills = []
170
171        for i in range(lc.choose(range(1, 4))):
172            lc.lslide(i)
[72]173            sk = lc.choose(skill_data)
[76]174            skname = sk['name'].encode('utf-8', 'replace')
[72]175            skpoint = sk['point']
176            sktype = getattr(pycodebattler.skill, sk['type'])
177            sklevel = sk['level']
[57]178            sk = pycodebattler.skill.Skill(skname, skpoint, sktype, sklevel)
179            skills.append(sk)
180
[89]181        return pycodebattler.warrior.Warrior(
182            name,
183            int(data['hitpoint']),
184            int(data['skillpoint']),
185            int(data['strength']),
186            int(data['concentration']),
187            int(data['defense']),
188            int(data['agility']),
189            int(data['luck']),
190            skills,
191        )
[57]192
193    @classmethod
194    def list(klass):
195        return sorted((klass(os.path.basename(d)) for d in
196                       glob.iglob(os.path.join(klass.DATA_DIR, '*'))
197                       if klass.VALID_ID.match(os.path.basename(d))),
198                      cmp=klass.mtimecmp)
199
200    @classmethod
201    def sweep(klass, thresh=CONFIG.getint('limit', 'max_entries')):
202        for self in klass.list()[thresh:]:
203            self.delete()
204
205    @staticmethod
206    def mtimecmp(a, b):
207        return int(b.mtime()) - int(a.mtime())
208
209
210def do_battle(warriors, turn=CONFIG.getint('battle', 'max_turn')):
211    proto = pycodebattler.battle.BattleProto(warriors, turn)
212    result = {
213        'winner':  None,
214        'history': [],
215    }
216
217    try:
218        for turn in proto.battle():
219            actions = []
220            try:
221                for attacker, skname, damages in turn:
222                    actions.append({
223                        'attacker': copy.deepcopy(attacker),
224                        'skill':    copy.deepcopy(skname),
225                        'damages':  copy.deepcopy(damages),
226                    })
227            except pycodebattler.battle.DrawGame:
228                pass
229
230            result['history'].append({
231                'actions':  actions,
232                'warriors': copy.deepcopy(warriors),
233            })
234
235        result['winner'] = proto.winner()
236
237    except pycodebattler.battle.DrawGame:
238        pass
239
240    return result
241
242
243def httpdump(template, entries=[], result={}):
244    tmpl = Template(open(os.path.expanduser(template)).read())
245    tmpl.script = sys.argv[0]
246    tmpl.config = CONFIG
247    tmpl.entries = entries
248    tmpl.result = result
249    sys.stdout.write('Content-type: text/html; charset=UTF-8\r\n\r\n')
[76]250    sys.stdout.write(tmpl.respond().encode('utf-8', 'replace'))
[57]251    sys.stdout.flush()
252
253
[77]254def jsondump(data):
255    sys.stdout.write('Content-type: application/json; charset=UTF-8\r\n\r\n')
256    sys.stdout.write(json.dumps(data, ensure_ascii=False) + '\n')
257    sys.stdout.flush()
258
259
[89]260def scouter(lang, code):
261    if lang not in CONFIG.options('scouter_api'):
262        raise ValueError('invalid language')
263
264    proxy = xmlrpclib.ServerProxy(CONFIG.get('scouter_api', lang))
265    data = proxy.Scout({'version': 1.0,
266                        'code': base64.standard_b64encode(code)})
267
268    if type(data) is not dict:
269        raise ValueError('invalid api response')
270    if 'version' not in data:
271        raise ValueError('invalid api response (version not found)')
272    if data['version'] != 1.0:
273        raise ValueError('invalid api response (invalid version)')
274    if 'result' not in data:
275        raise ValueError('invalid api response (result not found)')
276    if type(data['result']) is not dict:
277        raise ValueError('invalid api response (invalid result)')
278    return data['result']
279
280
281def handle_mode_json(form):
282    if 'id' not in form:
283        ids = [cm.id() for cm in CharacterManager.list()]
284    else:
285        tmp = form['id']
286        if type(tmp) is not list:
287            tmp = [form['id']]
288        ids = [id_.value for id_ in tmp]
289
290    data = {}
291    try:
292        for id_ in ids:
293            data[id_] = CharacterManager(id_).flatten()
294    except:
295        return jsondump({'result': {}})
296
297    return jsondump({'result': data})
298
299
300def handle_id(form):
301    entries = [CharacterManager(form.getfirst('id')).entry()]
302    return httpdump(CONFIG.get('template', 'character'), entries=entries)
303
304
305def handle_warrior(form):
306    ids = form['warrior']
307
308    if type(ids) is not list:
309        ids = [ids]
310
311    if len(ids) > CONFIG.getint('battle', 'max_entries'):
312        raise ValueError('battle warriors too long')
313
314    warriors = [CharacterManager(id_.value).load()[1] for id_ in ids]
315    result = do_battle(warriors)
316    return httpdump(CONFIG.get('template', 'battle'), result=result)
317
318
319def handle_file(form):
320    item = form['filename']
321    lang = form.getfirst('lang')
322
323    if not item.file or not item.filename or not lang:
324        raise ValueError('missing parameter')
325
326    code = item.file.read(CONFIG.getint('limit', 'max_size'))
327    data = scouter(lang, code)
328    CharacterManager.dump(item.filename, code, data)
329    return True
330
331
[57]332def main():
333    form = cgi.FieldStorage()
334
[77]335    if 'mode' in form and form.getfirst('mode') == 'json':
[89]336        return handle_mode_json(form)
[57]337    if 'id' in form:
[89]338        return handle_id(form)
[57]339    if 'warrior' in form:
[89]340        return handle_warrior(form)
341    if 'filename' in form and 'lang' in form:
342        if not handle_file(form):
343            return httpdump(CONFIG.get('template', 'error'))
[57]344
345    CharacterManager.sweep()
346
[77]347    entries = [cm.entry() for cm in CharacterManager.list()]
[57]348    return httpdump(CONFIG.get('template', 'index'), entries=entries)
349
350
351if __name__ == '__main__':
352    try:
353        main()
354    except:
355        httpdump(CONFIG.get('template', 'error'))
Note: See TracBrowser for help on using the repository browser.