#
# get.py
#	Mixin to implement HTML GET operations
#
# Structure of paths in the server:
#  /
#	Main UI.  Top part is playlist, bottom is file/dir browser
#  /media/<prefix>/...
#	For each prefix of files, its contents is served by
#	way of its path under here.
import os, sys, stat, urllib, time
import utils
from chore.utils import uncharenc
from chore.handlers import sketchy

# Max search results when filtering
MAXSEARCH=20

# Turn names into plain ASCII ones, with remap record so they
#  can still be opened
remaps = {}
def plain_names(names):
    global remaps

    res = []
    for n in names:
        n2 = uncharenc(n)
        if n != n2:
            remaps[n2] = n
        res.append(urllib.quote(n2))
    return res

# The GET part of our handling
class GET_mixin(object):

    # Configure our WPlayer GET treatment
    def __init__(self):

	# GET handlers
	self.dispatchers.append( ("GET", self.send_path) )
	self.dispatchers.append( ("GET", self.search) )

    # "/"; main UI
    def send_top(self):
	return self.send_files("html/top.html")

    # Provide a directory listing.  It's JSON with lists
    #  of files and directories (sorted!) in the named path.
    def _send_dir(self, indexed, path):
	res = {"index": indexed}
	for path,dirs,files in os.walk(path):
	    break
	else:
	    # If the walk got nothing, provide empty lists
	    files = dirs = ()

	# Normalize to UTF-8
        files = plain_names(files)
        dirs = plain_names(dirs)

	# In order
	files.sort()
	dirs.sort()

	# Put into place
	res["files"] = files
	res["dirs"] = dirs

	# Back it goes, as a JSON-encoded buffer
	return self.send_json(res)

    # Look up prefix, get state for this prefix
    def get_cpath(self, elem):
	cpaths = self.server.approot.config["files"]
	for tup in cpaths:
	    if tup[0] == elem:
		return tup[1]
        return None

    # Run sox and stream ogg
    # This lets us transcode, specifically from flac, to things
    #  which don't play it (Firefox Android, nice job
    #  Mozilla) or where the network's too slow for it (DSL
    #  uplink).
    def send_ogg(self, path):

        # Sanity check usually done by send_files()
        # We also indulge in some paranoia on shell quoting
        if sketchy(path) or ('"' in path) or ('\\' in path):
            self.send_error(404)
            return None

        # What format are we coming from?
        mt = self.get_mtype(path) or (False, "BAD")
        if "flac" in mt[1]:
            mt = "flac"
        elif "wav" in mt[1]:
            mt = "wav"
        elif "ogg" in mt[1]:
            # Already ogg?
            return self.send_files(path)
        else:
            self.send_error(404)
            return None

        # Verify file exists
        try:
            f = open(path, "rb")
            st = os.fstat(f.fileno())
        except:
            self.send_error(404)
            return None
        f.close()

        # We serve the converted file by running a sub-process
        #  under a pipe, and pushing the bytes out to the user.
        # As a pipe, we don't know the overall length, nor
        #  can you seek over it.  Sorry.
        self.send_response(200)
        self.send_header("Content-type", "audio/ogg")
        self.send_header("Last-Mmodified", time.localtime(st.st_mtime))
        self.end_headers()

        # Here's the command chain, passed to a shell so we try
        #  to keep the filename from causing mischief.
        f = os.popen('sox -t %s - -t ogg - < "%s"' %
            (mt, path))
        try:
            while True:
                b = f.read(32768)
                if not b:
                    break
                self.wfile.write(b)
        finally:
            f.close()

        return None

    # GET dispatcher
    # See if it's a configured path, and serve file or dir
    def send_path(self):
        global remaps

	app = self.server
	webroot = app.approot
	cpaths = webroot.config["files"]
        # Get URL level quoting out of the way
	pp = [urllib.unquote(p) for p in self.paths]
        # If we had to flatten out characters, restore
        #  to filesystem name
        pp = [remaps.get(p, p) for p in pp]

	# We serve /media/prefix/blah/blah...
	if pp[0] != "media":
	    return False,None
	del pp[0]

	# Root; list configured prefixes themselves
	if not pp:
	    res = {"files": (), "index": False,
		"dirs": sorted(tup[0] for tup in cpaths)}
	    buf = self.send_json(res)
	    return True,buf

	# Find the next path element
        d = self.get_cpath(pp[0])
        if d is None:
	    # Nope, we don't dispatch this path
	    return False,None

	# Their requested path, starting from here
	prefix = d["path"]
        indexed = ('_index' in d)
	if len(pp) > 1:
	    path = os.path.join(prefix, os.path.join(*pp[1:]))
	else:
	    path = prefix

	# File?  Or dir?
        try:
            st = os.stat(path)
        except:
            self.send_error(404)
            return True,None
	if stat.S_ISDIR(st.st_mode):
	    print "GET dir", path
	    buf = self._send_dir(indexed, path)
	else:
            # get /path/to/foo.flac?asogg -> stream a sox
            #  conversion of foo.flac
            # This lets us stream to players without flac
            #  support, or where the flac bitrate is too
            #  high for the network path.
            if self.vals and ("asogg" in self.vals):
                print "GET converted file", path
                buf = self.send_ogg(path)
            else:
                print "GET file", path
                buf = self.send_files(path)
	return True,buf

    # A search request
    #
    # If there's an index for this part of the tree, search
    #  for up to MAXSEARCH results and provide links
    def search(self):
        pp = self.paths

        # /search/<path...>?q=<query>
        if (len(pp) < 2) or (pp[0] != "search"):
            return False,None
        s = self.vals.get('q')
        if not s:
            return False,None

        # Which tree are we accessing?  Get its file index
	pp = tuple(urllib.unquote(p) for p in self.paths)
        d = self.get_cpath(pp[1])
        if (d is None) or ('_index' not in d):
            return False,None

        # Refresh the cache if needed
        fn,ftm,entries = d['_index']
        try:
            newtm = os.stat(fn).st_mtime
            if newtm > ftm:
                sys.stderr.write("Reloading index %s\n" % (fn,))
                fn,ftm,entries = d['_index'] = utils.load_index(fn)
        except:
            # Just keep what we have, maybe somebody's moving
            #  the file right now?
            pass

        # Walk forward to part of tree which matches our
        #  current position

        # Sweep results
        matches = []
        s = s.lower()
        if len(pp) > 2:
            # Initial part to match, current folder
            prefix = os.path.join(*pp[2:])
            # (+1 to trim separating '/' after prefix)
            plen = len(prefix)+1
            reached = False
        else:
            # Searching from root of this index
            prefix = ''
            plen = 0
            reached = True

        # Walk index entries
        # tup[0] is the actual path name (mixed case), tup[1]
        #  is the version with case flattened to lower only.
        for tup in entries:
            # Still walking forward to reach prefix match
            l = tup[0]
            if not reached:
                if not l.startswith(prefix):
                    continue
                reached = True

            # We're past the part of the index which matches
            #  our prefix
            ll = tup[1]
            if prefix:
                if not l.startswith(prefix):
                    break
                l = l[plen:]
                ll = ll[plen:]

            # See if our search string matches here
            if s in ll:
                matches.append(l)
                if len(matches) >= MAXSEARCH:
                    break
        
        # Here's a list of matching stuff
        # Parcel into file/dir just like a regular directory listing
	res = {"index": True, "files": [], "dirs": []}
        tp = d["path"]
        for bn in matches:
            fn = os.path.join(tp, prefix, bn)
            try:
                st = os.stat(fn)
            except:
                # Ignore broken file paths
                continue
            try:
                bn = urllib.quote(bn.decode('utf-8', 'replace'))
            except:
                # Ignore f*cked encodings
                continue
            if stat.S_ISDIR(st.st_mode):
                res["dirs"].append(bn)
            else:
                res["files"].append(bn)

        # Here's a result in the style of _send_dir() above
        return True,self.send_json(res)
