ewa.wsgiapp

Package ewa :: Module wsgiapp |
[hide private]
[frames] | no frames]

# Source Code for Module ewa.wsgiapp

      1  """
      2
      3  A WSGI application that generates mp3s dynamically
      4  according to a ruleset.
      5
      6
      7  """
      8  import mimetypes
      9  import os
     10  import time
     11
     12  import eyeD3
     13
     14  import ewa.audio
     15  from ewa.logutil import debug, info, error, exception
     16
     17  _codes={200:'200 OK',
     18          404:'404 Not Found',
     19          500:'500 Internal Server Error'}
     20
     21  GENERIC_SENDFILE_HEADER='X-Sendfile'
     22
     23  LIGHTTPD_SENDFILE_HEADER='X-LIGHTTPD-send-file'
     24
     25  MP3_MIMETYPE='audio/mpeg'
     26
     27  mimetypes.init()
     28
     29 -def guess_mime(filename):
     30      mtype, extension=mimetypes.guess_type(filename)
     31      if mtype is None:
     32          return 'application/octet-stream'
     33      return mtype
     34
     35 -class EwaApp(object):
     36
     37 -    def __init__(self,
     38                   rule,
     39                   basedir,
     40                   targetdir=None,
     41                   stream=False,
     42                   refresh_rate=0,
     43                   use_xsendfile=True,
     44                   sendfile_header=GENERIC_SENDFILE_HEADER,
     45  #                 index_directories=False,
     46                   content_disposition='',
     47                   **spliceKwargs):
     48          self.rule=rule
     49          self.stream=stream
     50          if stream and targetdir:
     51              raise ValueError, "in streaming mode but targetdir supplied"
     52          self.spliceKwargs=spliceKwargs
     53          self.refresh_rate=refresh_rate
     54          self.use_xsendfile=use_xsendfile
     55          self.sendfile_header=sendfile_header
     56  #        self.index_directories=index_directories
     57          self.content_disposition=content_disposition
     58          if self.stream:
     59              self.provider=ewa.audio.StreamAudioProvider(basedir)
     60          else:
     61              self.provider=ewa.audio.FSAudioProvider(basedir,
     62                                                      targetdir)
     63
     64      basedir=property(lambda x: x.provider.basedir)
     65
     66      targetdir=property(lambda x: getattr(x.provider, 'basedir', None))
     67
     68 -    def send(self, start_response, status, headers=None, iterable=None):
     69          codeline=_codes[status]
     70          if headers is None:
     71              headers=[('Content-Type', 'text/plain')]
     72          start_response(codeline, headers)
     73          if iterable is None:
     74              return [codeline[4:]]
     75          else:
     76              return iterable
     77
     78
     79 -    def _create_combined(self, mp3file):
     80          # strip leading '/'
     81          if mp3file.startswith('/'):
     82              mp3file=mp3file[1:]
     83          mainpath=self.provider.get_main_path(mp3file)
     84          # if this blows up, propagate
     85          maintime=os.path.getmtime(mainpath)
     86          if os.path.isdir(mainpath):
     87  ##             if self.index_directories:
     88  ##                 # implement this eventually.
     89  ##                 # probably the file-existence/directory
     90  ##                 # check should be moved to __call__
     91  ##                 pass
     92              raise OSError
     93          if self.stream:
     94              try:
     95                  return self.provider.create_combined(mp3file,
     96                                                       self.rule,
     97                                                       **self.spliceKwargs), MP3_MIMETYPE
     98              except (ewa.audio.AudioProviderException, eyeD3.InvalidAudioFormatException):
     99                  info("%s cannot be processed.  Serving statically", mainpath)
    100                  return open(mainpath), guess_mime(mainpath)
    101          else:
    102              path=self.provider.get_combined_path(mp3file)
    103              try:
    104                  mtime=os.path.getmtime(path)
    105              except OSError:
    106                  debug("OSError in getting mod time (ok)")
    107                  pass
    108              else:
    109                  # if the main file modified?
    110                  regen= maintime > mtime
    111                  if not regen:
    112                      if self.refresh_rate==0:
    113                          debug("no refresh, returning target path")
    114                          return path, MP3_MIMETYPE
    115                      else:
    116                          t=time.time()
    117                          if t-mtime < self.refresh_rate:
    118                              debug("not necessary to refresh, returning target path")
    119                              return path, MP3_MIMETYPE
    120
    121              # if we get here we regenerate
    122              debug("need to regenerate combined file")
    123              try:
    124                  path2=self.provider.create_combined(mp3file,
    125                                                      self.rule,
    126                                                      **self.spliceKwargs)
    127              except (ewa.audio.AudioProviderException, eyeD3.InvalidAudioFormatException):
    128                  info("%s cannot be processed.  Serving statically", mainpath)
    129                  return mainpath, guess_mime(mainpath)
    130              # should be the same
    131              debug("path returned from provider: %s", path2)
    132              debug("our calculated path: %s", path)
    133              return path2, MP3_MIMETYPE
    134
    135
    136 -    def __call__(self, environ, start_response):
    137          mp3file=environ['SCRIPT_NAME']+environ['PATH_INFO']
    138          info("mp3file: %s", mp3file)
    139          if not mp3file:
    140              return self.send(start_response, 404)
    141          try:
    142              result, mtype=self._create_combined(mp3file)
    143          except (OSError, IOError):
    144              exception("Error in looking for file %s", mp3file)
    145              return self.send(start_response, 404)
    146          except:
    147              error("error creating combined file")
    148              exception("internal server error")
    149              return self.send(start_response, 500)
    150          else:
    151              return self.sendfile(result, start_response, mtype)
    152
    153 -    def sendfile(self, result, start_response, mtype):
    154          headers=[('Content-Type', mtype)]
    155          if mtype==MP3_MIMETYPE and self.content_disposition:
    156              headers.append(('Content-Disposition', self.content_disposition))
    157          if self.use_xsendfile:
    158              length=os.path.getsize(result)
    159              headers.extend([(self.sendfile_header, result),
    160                              ('Content-Length', "%d" % length)])
    161              debug('headers are: %s', headers)
    162              return self.send(start_response,
    163                               200,
    164                               headers,
    165                               "OK")
    166          else:
    167              if not self.stream:
    168                  result=open(result, 'rb')
    169              return self.send(start_response,
    170                               200,
    171                               headers,
    172                               result)
    173