[tests] Port unittest_cwctl to py3k
On python 2, sys.stdout takes bytes-like objects whereas python 3's
takes unicode-like objects and handles the encoding on its own.
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""Set of static resources controllers for :- /data/...- /static/...- /fckeditor/..."""importosimportos.pathasospimporthashlibimportmimetypesimportthreadingimporttempfilefromtimeimportmktimefromdatetimeimportdatetime,timedeltafromloggingimportgetLoggerfromcubicwebimportForbiddenfromcubicweb.webimportNotFoundfromcubicweb.web.http_headersimportgenerateDateTimefromcubicweb.web.controllerimportControllerfromcubicweb.web.views.urlrewriteimportURLRewriterclassStaticFileController(Controller):"""an abtract class to serve static file Make sure to add your subclass to the STATIC_CONTROLLERS list"""__abstract__=Truedirectory_listing_allowed=Falsedefmax_age(self,path):"""max cache TTL"""return60*60*24*7defstatic_file(self,path):"""Return full content of a static file. XXX iterable content would be better """debugmode=self._cw.vreg.config.debugmodeifosp.isdir(path):ifself.directory_listing_allowed:returnu''raiseForbidden(path)ifnotosp.isfile(path):raiseNotFound()ifnotdebugmode:# XXX: Don't provide additional resource information to error responses## the HTTP RFC recommands not going further than 1 year aheadexpires=datetime.now()+timedelta(days=6*30)self._cw.set_header('Expires',generateDateTime(mktime(expires.timetuple())))# XXX system call to os.stats could be cached once and for all in# production mode (where static files are not expected to change)## Note that: we do a osp.isdir + osp.isfile before and a potential# os.read after. Improving this specific call will not help## Real production environment should use dedicated static file serving.self._cw.set_header('last-modified',generateDateTime(os.stat(path).st_mtime))ifself._cw.is_client_cache_valid():return''# XXX elif uri.startswith('/https/'): uri = uri[6:]mimetype,encoding=mimetypes.guess_type(path)ifmimetypeisNone:mimetype='application/octet-stream'self._cw.set_content_type(mimetype,osp.basename(path),encoding)withopen(path,'rb')asresource:returnresource.read()@propertydefrelpath(self):"""path of a requested file relative to the controller"""path=self._cw.form.get('static_relative_path')ifpathisNone:path=self._cw.relative_path(includeparams=True)returnpathclassConcatFilesHandler(object):"""Emulating the behavior of modconcat this serve multiple file as a single one. """def__init__(self,config):self._resources={}self.config=configself.logger=getLogger('cubicweb.web')self.lock=threading.Lock()def_resource(self,path):"""get the resouce"""try:returnself._resources[path]exceptKeyError:self._resources[path]=self.config.locate_resource(path)returnself._resources[path]def_up_to_date(self,filepath,paths):""" The concat-file is considered up-to-date if it exists. In debug mode, an additional check is performed to make sure that concat-file is more recent than all concatenated files """ifnotosp.isfile(filepath):returnFalseifself.config.debugmode:concat_lastmod=os.stat(filepath).st_mtimeforpathinpaths:dirpath,rid=self._resource(path)ifridisNone:raiseNotFound(path)path=osp.join(dirpath,rid)ifos.stat(path).st_mtime>concat_lastmod:returnFalsereturnTruedefbuild_filepath(self,paths):"""return the filepath that will be used to cache concatenation of `paths` """_,ext=osp.splitext(paths[0])fname='cache_concat_'+hashlib.md5((';'.join(paths)).encode('ascii')).hexdigest()+extreturnosp.join(self.config.appdatahome,'uicache',fname)defconcat_cached_filepath(self,paths):filepath=self.build_filepath(paths)ifnotself._up_to_date(filepath,paths):withself.lock:ifself._up_to_date(filepath,paths):# first check could have raced with some other thread# updating the filereturnfilepathfd,tmpfile=tempfile.mkstemp(dir=os.path.dirname(filepath))try:f=os.fdopen(fd,'wb')forpathinpaths:dirpath,rid=self._resource(path)ifridisNone:# In production mode log an error, do not return a 404# XXX the erroneous content is cached anywayself.logger.error('concatenated data url error: %r file ''does not exist',path)ifself.config.debugmode:raiseNotFound(path)else:withopen(osp.join(dirpath,rid),'rb')assource:forlineinsource:f.write(line)f.write(b'\n')f.close()except:os.remove(tmpfile)raiseelse:os.rename(tmpfile,filepath)returnfilepathclassDataController(StaticFileController):"""Controller in charge of serving static files in /data/ Handles mod_concat-like URLs. """__regid__='data'def__init__(self,*args,**kwargs):super(DataController,self).__init__(*args,**kwargs)config=self._cw.vreg.configself.base_datapath=config.data_relpath()self.data_modconcat_basepath='%s??'%self.base_datapathself.concat_files_registry=ConcatFilesHandler(config)defpublish(self,rset=None):config=self._cw.vreg.config# includeparams=True for modconcat-like urlsrelpath=self.relpathifrelpath.startswith(self.data_modconcat_basepath):paths=relpath[len(self.data_modconcat_basepath):].split(',')filepath=self.concat_files_registry.concat_cached_filepath(paths)else:# skip leading '/data/' and url paramsifrelpath.startswith(self.base_datapath):prefix=self.base_datapathelse:prefix='data/'relpath=relpath[len(prefix):]relpath=relpath.split('?',1)[0]dirpath,rid=config.locate_resource(relpath)ifdirpathisNone:raiseNotFound()filepath=osp.join(dirpath,rid)returnself.static_file(filepath)classFCKEditorController(StaticFileController):"""Controller in charge of serving FCKEditor related file The motivational for a dedicated controller have been lost. """__regid__='fckeditor'defpublish(self,rset=None):config=self._cw.vreg.configifself._cw.https:uiprops=config.https_uipropselse:uiprops=config.uipropsrelpath=self.relpathifrelpath.startswith('fckeditor/'):relpath=relpath[len('fckeditor/'):]relpath=relpath.split('?',1)[0]returnself.static_file(osp.join(uiprops['FCKEDITOR_PATH'],relpath))classStaticDirectoryController(StaticFileController):"""Controller in charge of serving static file in /static/ """__regid__='static'defpublish(self,rset=None):staticdir=self._cw.vreg.config.static_directoryrelpath=self.relpath[len(self.__regid__)+1:]returnself.static_file(osp.join(staticdir,relpath))STATIC_CONTROLLERS=[DataController,FCKEditorController,StaticDirectoryController]classStaticControlerRewriter(URLRewriter):"""a quick and dirty rewritter in charge of server static file. This is a work around the flatness of url handling in cubicweb."""__regid__='static'priority=10defrewrite(self,req,uri):forctrlinSTATIC_CONTROLLERS:ifuri.startswith('/%s/'%ctrl.__regid__):breakelse:self.debug("not a static file uri: %s",uri)raiseKeyError(uri)relpath=self._cw.relative_path(includeparams=False)self._cw.form['static_relative_path']=self._cw.relative_path(includeparams=True)returnctrl.__regid__,None