summaryrefslogtreecommitdiff
path: root/directory.py
blob: a93e00cb5319b08bb9d045a674c6b39a2d5b01c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import os, mimetypes, recode, events

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 '<a href="/files/{path}">{name}</a><br />'.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_file(self):
		cache_file = os.path.join(config.get('cache_dir'), self.path)
		cache_file = os.path.splitext(cache_file)[0] + '.mp3'
		return cache_file

	def recode(self, decoder, encoder):
		decoder = recode.decoders[decoder]()
		encoder = recode.encoders[encoder]()
		recoder = recode.Recoder(decoder, encoder)

		cache_file = self.get_cache_file()
		cache_file_dir = os.path.dirname(cache_file)
		# check and create cache directory
		if not os.path.exists(cache_file_dir):
			os.mkdir(cache_file_dir)
		# check if file is cached
		if not os.path.exists(cache_file):
			events.event_pub.recoding(self.path)
			recoder.recode(self.abs_path, cache_file)
		events.event_pub.cached(self.path)

	def json(self):
		cache_file = self.get_cache_file()
		d = DirectoryEntry.json(self)
		d.update({'cached': os.path.exists(cache_file)})
		return d