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
31 mtype, extension = mimetypes.guess_type(filename)
32 if mtype is None:
33 return 'application/octet-stream'
34 return mtype
35
36
38
39 - def __init__(self,
40 rule,
41 basedir,
42 targetdir=None,
43 stream=False,
44 refresh_rate=0,
45 use_xsendfile=True,
46 sendfile_header=GENERIC_SENDFILE_HEADER,
47 content_disposition='',
48 **spliceKwargs):
49 self.rule = rule
50 self.stream = stream
51 if stream and targetdir:
52 raise ValueError("in streaming mode but targetdir supplied")
53 self.spliceKwargs = spliceKwargs
54 self.refresh_rate = refresh_rate
55 self.use_xsendfile = use_xsendfile
56 self.sendfile_header = sendfile_header
57 self.content_disposition = content_disposition
58 if self.stream:
59 self.provider = ewa.audio.StreamAudioProvider(basedir,
60 tolerate_vbr=False)
61 else:
62 self.provider = ewa.audio.FSAudioProvider(basedir,
63 False,
64 targetdir)
65
66 basedir = property(lambda x: x.provider.basedir)
67
68 targetdir = property(lambda x: getattr(x.provider, 'basedir', None))
69
70 - def send(self, start_response, status, headers=None, iterable=None):
71 codeline = _codes[status]
72 if headers is None:
73 headers = [('Content-Type', 'text/plain')]
74 start_response(codeline, headers)
75 if iterable is None:
76 return [codeline[4:]]
77 else:
78 return iterable
79
81
82 if mp3file.startswith('/'):
83 mp3file = mp3file[1:]
84 mainpath = self.provider.get_main_path(mp3file)
85
86 maintime = os.path.getmtime(mainpath)
87 if os.path.isdir(mainpath):
88
89
90
91
92
93 raise OSError
94 if self.stream:
95 try:
96 return self.provider.create_combined(
97 mp3file,
98 self.rule,
99 **self.spliceKwargs), MP3_MIMETYPE
100 except (ewa.audio.AudioProviderException,
101 eyeD3.InvalidAudioFormatException):
102 info("%s cannot be processed. Serving statically", mainpath)
103 return open(mainpath), guess_mime(mainpath)
104 else:
105 path = self.provider.get_combined_path(mp3file)
106 try:
107 mtime = os.path.getmtime(path)
108 except OSError:
109 debug("OSError in getting mod time (ok)")
110 pass
111 else:
112
113 regen = maintime > mtime
114 if not regen:
115 if self.refresh_rate == 0:
116 debug("no refresh, returning target path")
117 return path, MP3_MIMETYPE
118 else:
119 t = time.time()
120 if t-mtime < self.refresh_rate:
121 debug("not necessary to refresh, "
122 "returning target path")
123 return path, MP3_MIMETYPE
124
125
126 debug("need to regenerate combined file")
127 try:
128 path2 = self.provider.create_combined(
129 mp3file,
130 self.rule,
131 **self.spliceKwargs)
132 except (ewa.audio.AudioProviderException,
133 eyeD3.InvalidAudioFormatException):
134 info("%s cannot be processed. Serving statically", mainpath)
135 return mainpath, guess_mime(mainpath)
136
137 debug("path returned from provider: %s", path2)
138 debug("our calculated path: %s", path)
139 return path2, MP3_MIMETYPE
140
141 - def __call__(self, environ, start_response):
142 mp3file = environ['SCRIPT_NAME']+environ['PATH_INFO']
143 info("mp3file: %s", mp3file)
144 if not mp3file:
145 return self.send(start_response, 404)
146 try:
147 result, mtype = self._create_combined(mp3file)
148 except (OSError, IOError):
149 exception("Error in looking for file %s", mp3file)
150 return self.send(start_response, 404)
151 except:
152 error("error creating combined file")
153 exception("internal server error")
154 return self.send(start_response, 500)
155 else:
156 return self.sendfile(result, start_response, mtype)
157
158 - def sendfile(self, result, start_response, mtype):
159 headers = [('Content-Type', mtype)]
160 if mtype == MP3_MIMETYPE and self.content_disposition:
161 headers.append(('Content-Disposition', self.content_disposition))
162 if self.use_xsendfile:
163 length = os.path.getsize(result)
164 headers.extend([(self.sendfile_header, result),
165 ('Content-Length', "%d" % length)])
166 debug('headers are: %s', headers)
167 return self.send(start_response,
168 200,
169 headers,
170 "OK")
171 else:
172 if not self.stream:
173 result = open(result, 'rb')
174 return self.send(start_response,
175 200,
176 headers,
177 result)
178