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