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
30 mtype, extension=mimetypes.guess_type(filename)
31 if mtype is None:
32 return 'application/octet-stream'
33 return mtype
34
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
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
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
80
81 if mp3file.startswith('/'):
82 mp3file=mp3file[1:]
83 mainpath=self.provider.get_main_path(mp3file)
84
85 maintime=os.path.getmtime(mainpath)
86 if os.path.isdir(mainpath):
87
88
89
90
91
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
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
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
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