PageRenderTime 84ms CodeModel.GetById 40ms app.highlight 19ms RepoModel.GetById 21ms app.codeStats 0ms

/kai/controllers/docs.py

https://bitbucket.org/bbangert/kai/
Python | 178 lines | 134 code | 26 blank | 18 comment | 45 complexity | 0fd019191b7a99f13b27181444e0773f MD5 | raw file
  1import mimetypes
  2import logging
  3import os
  4
  5import pylons
  6from couchdb import ResourceConflict
  7from paste.urlparser import StaticURLParser
  8from pylons import config, request, response, session, tmpl_context as c
  9from pylons.controllers.util import abort, redirect
 10from pylons.decorators import jsonify
 11
 12from kai.lib.base import BaseController, json, render
 13from kai.model import Documentation
 14
 15log = logging.getLogger(__name__)
 16
 17class DocsController(BaseController):
 18    """Documentation Controller
 19    
 20    The Documentation controller handles uploads of docs, as well as
 21    doc display.
 22    
 23    """
 24    def __before__(self):
 25        c.active_tab = 'Documentation'
 26        c.active_sub = 'Reference'
 27    
 28    def view(self, version, url, language):
 29        # Change the url back to a string (just in case)
 30        url = str(url)
 31        
 32        if 'doc_version' not in session or version != session['doc_version']:
 33            session['doc_version'] = version
 34            session.save()
 35        
 36        # Check for serving images
 37        if url.startswith('_images/'):
 38            img_srv = StaticURLParser(config['image_dir'])
 39            request.environ['PATH_INFO'] = '/Pylons/%s/%s' % (version, url[8:])
 40            return img_srv(request.environ, self.start_response)
 41        
 42        # If the docs are pre-0.9.7, use the old viewer
 43        if version < '0.9.7':
 44            return self._view_old(version, url)
 45        
 46        # Check a few other space cases and alter the URL as needed
 47        if url.startswith('glossary/'):
 48            c.active_sub = 'Glossary'
 49        if url.startswith('modules/'):
 50            c.active_sub = 'Modules'
 51        if url == 'modules/':
 52            url = 'modindex'
 53        if url == 'index/':
 54            url = 'genindex'
 55            c.active_sub = 'Index'
 56        if url.endswith('/'):
 57            url = url[:-1]
 58        
 59        # Grab the doc from CouchDB
 60        c.doc = Documentation.fetch_doc('Pylons', version, language, url)
 61        if not c.doc:
 62            # Try again with index just in case
 63            url += '/index'
 64            c.doc = Documentation.fetch_doc('Pylons', version, language, url)
 65            if not c.doc:
 66                abort(404)
 67        if url == 'modindex':
 68            return render('/docs/modindex.mako')
 69        if url == 'genindex':
 70            return render('/docs/genindex.mako')
 71        if url == 'objects.inv':
 72            response.content_type = 'text/plain'
 73            return c.doc['content']
 74        
 75        if url == 'search':
 76            redirect(pylons.url('search'))
 77        return render('/docs/view.mako')
 78    
 79    def _view_old(self, version, url):
 80        if request.path_info.endswith('docs') or request.path_info.endswith('docs/'):
 81            redirect(pylons.url('/docs/%s/' % str(version)))
 82
 83        base_path = os.path.normpath(config.get('doc_dir'))
 84        if url == 'index':
 85            url = 'index.html'
 86        c.url = os.path.normpath(os.path.join(base_path, version, url))
 87        
 88        # Prevent circumvention of the base docs path and bad paths
 89        if not c.url.startswith(base_path) or not os.path.exists(c.url):
 90            if version > '0.9.5':
 91                redirect(pylons.url('cdocs', page='Home'))
 92            abort(404)
 93        c.version = version
 94        return render('/docs/load_content.mako')
 95        
 96    @jsonify
 97    def upload_image(self):
 98        dockey = config['doc.security_key']
 99        if request.environ['HTTP_AUTHKEY'] != dockey:
100            abort(401)
101        doc = request.body
102        
103        version = request.GET.get('version')
104        project = request.GET.get('project')
105        name = request.GET.get('name')
106        if not version or not project or not name:
107            response.status = 500
108            return dict(status='error', reason='No version/project combo found')
109        fdir = os.path.join(config['image_dir'], project, version)
110        
111        # Ensure this didn't escape our current dir, we must be under images
112        if not fdir.startswith(config['image_dir']):
113            abort(500)
114        
115        fname = os.path.join(fdir, name)
116        
117        # Ensure we're under the parent dir we should be
118        if not fname.startswith(fdir):
119            abort(500)
120        ensure_dir(fdir)
121        try:
122            with open(os.path.join(fdir, name), 'w') as w:
123                w.write(doc)
124        except:
125            abort(500)
126        else:
127            return dict(status='ok')
128    
129    @jsonify
130    def upload(self):
131        dockey = config['doc.security_key']
132        if request.environ['HTTP_AUTHKEY'] != dockey:
133            abort(401)
134        
135        try:
136            doc = json.loads(request.body)
137        except:
138            response.status = 500
139            return dict(status='error', reason='Unable to parse JSON')
140        doc['type'] = 'Documentation'
141        
142        update = False
143        # Check to see we don't have it already
144        existing_doc = list(Documentation.doc_key(self.db)[doc['filename'], doc['version'], doc['language'], doc['project']])
145        if existing_doc:
146            existing_doc = dict(existing_doc[0].doc)
147            update = True
148        try:
149            if update:
150                existing_doc.update(doc)
151                self.db.update([existing_doc])
152            else:
153                self.db.create(doc)
154        except ResourceConflict:
155            response.status = 409
156            return dict(status='conflict')
157        return dict(status='ok')
158    
159    @jsonify
160    def delete_revision(self, project, version):
161        dockey = config['doc.security_key']
162        if request.environ['HTTP_AUTHKEY'] != dockey:
163            abort(401)
164        
165        # Documentation.delete_revision(project, version)
166        return dict(status='ok')
167
168
169def ensure_dir(dir):
170    """Copied from Mako, ensures a directory exists in the filesystem"""
171    tries = 0
172    while not os.path.exists(dir):
173        try:
174            tries += 1
175            os.makedirs(dir, 0750)
176        except:
177            if tries > 5:
178                raise