import os, mimetypes, recode, events, threading from config import config class DirectoryEntry(object): '''Base class for directory entries.''' def __init__(self, path, isabs = False): self.path = path if isabs: self.abs_path = path else: path_list = os.path.split(config.get('music_root')) + os.path.split(self.path) if '..' in path_list: raise Exception('Invalid path') self.abs_path = os.path.normpath(os.path.sep.join(path_list)) def __cmp__(self, other): return cmp(self.path, other.path) def __lt__(self, other): return self.path < other.path def __str__(self): return '{name}
'.format(path = self.path, name = os.path.basename(self.path)) def json(self): return {'type': self.entry_type, 'name': self.path} class Directory(DirectoryEntry): '''A directory entry inside a directory.''' entry_type = 'dir' def listdir(self): directories = [] files = [] for f in os.listdir(self.abs_path): abs_path = os.path.join(self.abs_path, f) rel_path = os.path.relpath(abs_path, config.get('music_root')) if os.path.isdir(abs_path): directories.append(Directory(rel_path)) elif os.path.isfile(abs_path): files.append(File(rel_path)) return sorted(directories) + sorted(files) class File(DirectoryEntry): '''A file entry inside a directory.''' entry_type = 'file' def send(self, environ, start_response): do_range = 'HTTP_RANGE' in environ if do_range: file_range = environ['HTTP_RANGE'].split('bytes=')[1] mime = mimetypes.guess_type(self.abs_path, strict = False)[0] or 'application/octet-stream' size = os.path.getsize(self.abs_path) if do_range: start, end = [int(x or 0) for x in file_range.split('-')] if end == 0: end = size-1 write_out = start_response('206 Partial Content', [ ('Content-Type', mime), ('Content-Range', 'bytes {start}-{end}/{size}'.format(start = start, end = end, size = size)), ('Content-Length', str(end - start + 1))]) f = open(self.abs_path, 'rb') f.seek(start) remaining = end-start+1 s = f.read(min(remaining, 1024)) while s: write_out(s) remaining -= len(s) s = f.read(min(remaining, 1024)) return [] start_response('200 OK', [ ('Content-Type', mime), ('Content-Length', str(size))]) return open(self.abs_path, 'rb') def get_cache_path(self): cache_path = os.path.join(config.get('cache_dir'), self.path) cache_path = os.path.splitext(cache_path)[0] + '.mp3' return cache_path def get_cache_file(self): return File(self.get_cache_path(), True) def recode(self, decoder, encoder, sessionid = None): decoder = recode.decoders[decoder]() encoder = recode.encoders[encoder]() recoder = recode.Recoder(decoder, encoder) cache_path = self.get_cache_path() cache_path_dir = os.path.dirname(cache_path) # check and create cache directory if not os.path.exists(cache_path_dir): os.mkdir(cache_path_dir) # check if file is cached if not os.path.exists(cache_path): events.event_pub.recoding(self.path) recoder.recode(self.abs_path, cache_path) events.event_pub.cached(self.path) if sessionid: events.event_pub.play(sessionid, '/cache/{0}'.format(self.path)) def start_recode(self, decoder, encoder, sessionid = None): t = threading.Thread(target = self.recode, args = (decoder, encoder, sessionid)) t.start() def json(self): cache_path = self.get_cache_path() d = DirectoryEntry.json(self) d.update({'cached': os.path.exists(cache_path)}) return d