import BaseHTTPServer
import copy
import cStringIO
import datetime
import glob
import operator
import os
import re
import select
import shutil
import sys
import threading
import urllib
import Tkinter

import feedgenerator

class FeedConfig:
	"""is a rough configuration class.

	We should probably get this information from an external file.
	For now, just a couple of class attributes should do.
	"""
	mediaDir = "/auto/sosainc"
	archiveDir = "/usr/media/burnarchive"
	mediaPatterns = ["*.ogg", "endederwelt*.mp3"]
	serverAddress = ""
	serverPort = 9091


class FeedConfig2:
	"""is a rough configuration class.

	We should probably get this information from an external file.
	For now, just a couple of class attributes should do.
	"""
	mediaDir = "/usr/media/work/Die Gefaehrten"
	archiveDir = "/home/media/junk"
	mediaPatterns = ["*.mp3"]
	serverAddress = ""
	serverPort = 9091



def ensureIsBelow(fqName, head):
	"""raises an IOError if fqName is not below head in the file system.

	Ok, the IOError is a bit questionable.  Also, if you link out
	of head, we'll still raise an error if the link target is not below
	head.
	"""
	if not os.path.abspath(fqName).startswith(head):
		raise IOError("%s is not below %s"%(fqName, head))


_flyingCars = datetime.date(2038, 1, 1)

class AutoFeederHandler(BaseHTTPServer.BaseHTTPRequestHandler):
	"""is a handler that provides an RSS feed for a media dir and
	serves media files from there.

	The main trick the thing plays is that it usually removes or moves
	files served.  It is meant as a companion to autoplayer that uses
	the information generated here to fill up its play queue.

	The class needs an attribute feedConfig containing a FeedConfig 
	instance.  You would usually provide one using the makeFeederHandler
	class method.
	"""
	def _getHostpart(self):
		addr, port = self.rfile._sock.getsockname()
		return "http://%s:%d"%(addr, port)

	def _sendContent(self, contentType, content, headonly, headers=[]):
		self.send_response(200)
		for key, val in headers:
			self.send_header(key, val)
		self.send_header("Content-type", contentType)
		self.send_header("Content-length", len(content))
		self.end_headers()
		if not headonly:
			self.wfile.write(content)

	def _sendFile(self, contentType, srcFile, headonly, chunksize=8192):
		self.send_response(200)
		self.send_header("Content-type", contentType)
		self.send_header("Content-length", os.path.getsize(srcFile))
		self.send_header("Date", datetime.datetime.fromtimestamp(
			os.path.getmtime(srcFile)).ctime())
		self.end_headers()
		if not headonly:
			f = open(srcFile)
			while 1:
				data = f.read(chunksize)
				if not data:
					break
				self.wfile.write(data)
			f.close()

	def _listDownloadableFiles(self):
		allFiles = []
		for pat in self.feedConfig.mediaPatterns:
			allFiles.extend(glob.glob(os.path.join(self.feedConfig.mediaDir, pat)))
		allFiles.sort()
		allFiles = [(os.path.getmtime(f), f) for f in allFiles]
		return allFiles

	def _getItems(self):
		items = []
		dateRe = re.compile("(2\d\d\d)([01]\d)([0123]\d)")
		for timestamp, fName in self._listDownloadableFiles():
			mat = dateRe.search(fName)
			if mat:
				items.append((datetime.date(int(mat.group(1)), int(mat.group(2)),
					int(mat.group(3))), fName))
			else: # Sort all others to the end.  Since they came in alphabetically
						# sorted from _listDownloadable and sort is stable, they are
						# still sorted alphabetically.
				items.append((_flyingCars, fName))
		items.sort()
		return items

	def _handleIndexRequest(self, headonly):
		response = feedgenerator.DefaultFeed(
			title=u"Autofeeder downloadable items",
			link=u"%s/rss"%self._getHostpart(),
			description=u"Items in the local download queue, probably"
				u" held here for a user's autoplayer.  Don't download this"
				u" if you are not that user.  Thanks.")
		for timestamp, fName in self._getItems():
			response.add_item(title=os.path.basename(fName), link="%s/media/%s"%(
					self._getHostpart(), urllib.quote(os.path.basename(fName))),
				pubdate=timestamp,
				description="Media file %s"%fName)
		outFile = cStringIO.StringIO()
		response.write(outFile, "utf-8")
		content = outFile.getvalue()
		self._sendContent("text/xml; charset=utf-8", content, headonly, headers=[
			("Date", datetime.datetime.now().ctime())])

	def _handleMoveRequest(self, headonly=False):
#		self._sendContent("text/plain", "", headonly)
#		return
		try:
			filePath = os.path.join(self.feedConfig.mediaDir, 
				urllib.unquote(os.path.basename(self.path)))
			ensureIsBelow(filePath, self.feedConfig.mediaDir)
			if self.feedConfig.archiveDir:
				shutil.move(filePath, os.path.join(self.feedConfig.archiveDir,
					os.path.basename(filePath)))
			else:
				os.unlink(filePath)
			self._sendContent("text/plain; charset=utf-8", "", headonly, headers=[
				("Date", datetime.datetime.now().ctime())])
		except IOError:
			self.send_error(404, "This media file is not in my media dir.")

	def _handleMediaRequest(self, headonly=False):
		filePath = os.path.join(self.feedConfig.mediaDir, 
			urllib.unquote(os.path.basename(self.path)))
		try:
			ensureIsBelow(filePath, self.feedConfig.mediaDir)
			f = open(filePath)
			f.read(10)
			f.close()
		except (IOError, os.error):
			self.send_error(404, "This media file is not in my media dir.")
		self._sendFile("audio/compressed", filePath, headonly)

	def do_GET(self, headonly=False):
		if self.path.startswith("/media"):
			self._handleMediaRequest(headonly)
		elif self.path.startswith("/move"):
			self._handleMoveRequest(headonly)
		elif self.path=="/rss":
			self._handleIndexRequest(headonly)
		else:
			self.send_error(404, "This is not a web server.  Or is it?")

	def do_HEAD(self):
		return self.do_GET(headonly=True)

	def makeFeederHandler(cls, feedConfig):
		handlerCls = copy.copy(cls)
		handlerCls.feedConfig = feedConfig
		return handlerCls
	makeFeederHandler = classmethod(makeFeederHandler)

	def log_message(self, format, *args):
		self.server.log_message(format, *args)


class Logshower(Tkinter.Frame):
	"""is a widget containing a scrolling display of up to maxLines lines.
	"""
	def __init__(self, master, maxLines=70):
		Tkinter.Frame.__init__(self, master)
		self.maxLines = maxLines
		self.display = Tkinter.Text(self,
			borderwidth=0, wrap=Tkinter.WORD, relief=Tkinter.FLAT)
		self.display.bind("<Key>", lambda ev: "break")
		self.scroller = Tkinter.Scrollbar(self, command=self.display.yview)
		self.display.config(yscrollcommand=self.scroller.set)
		self.display.grid(row=0, column=0, sticky=Tkinter.NW+Tkinter.SW)
		self.scroller.grid(row=0, column=1, sticky=Tkinter.NW+Tkinter.SW)
		self.columnconfigure(0, weight=1)
		self.rowconfigure(0, weight=1)
	
	def addLine(self, line):
		self.display.insert(Tkinter.END, "\n"+line)
		self.display.see(Tkinter.END)
		numberOfLines = int(self.display.index(Tkinter.END).split(".")[0])
		if numberOfLines>self.maxLines:
			self.display.delete("0.0", 
				"%d.0"%(numberOfLines-self.maxLines))


class Statusbar(Tkinter.Label):
	def __init__(self, master):
		Tkinter.Label.__init__(self, master, justify=Tkinter.LEFT, 
			relief=Tkinter.SUNKEN, anchor=Tkinter.W)
		self.afterId = None

	def _clearMsg(self):
		self.afterId = None
		self.config(text="")

	def showMsg(self, msg, timeout=None):
		if self.afterId:
			self.after_cancel(self.afterId)
		self.config(text=msg)
		if timeout:
			self.afterId = self.after(timeout*1000, self._clearMsg)


class ServerGui(Tkinter.Tk):
	"""is a simple GUI for a httpd.
	"""
	LOG = 1
	BAR = 2
	def __init__(self):
		Tkinter.Tk.__init__(self)
		self.title("AutoFeeder")
		self._buildWidgets()
		self.bind("q", lambda ev: self.quit())
		self.focus()
	
	def _buildWidgets(self):
		self._buildMenubar()
		self.logshower = Logshower(self)
		self.logshower.grid(row=1, column=1, sticky=Tkinter.SW+Tkinter.NE)
		self.statusbar = Statusbar(self)
		self.statusbar.grid(row=2, column=1, sticky=Tkinter.NW+Tkinter.SE)
		self.rowconfigure(1, weight=1)
		self.columnconfigure(1, weight=1)

	def _buildMenubar(self):
		menubar = Tkinter.Menu(self)
		filemenu  = Tkinter.Menu(menubar, tearoff=0)
		filemenu.add_command(label="Quit", command=self.quit)
		menubar.add_cascade(label="File", menu=filemenu)
		self.config(menu=menubar)

	def _logMessage(self, message):
		self.logshower.addLine(message)

	def _changeStatusbar(self, message, timeout=None):
		self.statusbar.showMsg(message, timeout)

	def send(self, type, *payload):
		self.methods[type](self, *payload)

	methods = {
		LOG: _logMessage,
		BAR: _changeStatusbar,
	}


def getServer(config, handler):
	server_address = (config.serverAddress, config.serverPort)
	return BaseHTTPServer.HTTPServer(server_address, handler)


class GuiServerAdapter:
	def __init__(self, ui, config, httpd):
		self.ui = ui
		self.stop = False
		self.httpd = httpd
		self.httpd.log_message = self.logToUi
		self.serverThread = threading.Thread(target=self.runServer)
		self.serverThread.setDaemon(True)
		self.serverThread.start()
		self.ui.send(self.ui.BAR, "Listening on %s:%d"%server_address, 20)

	def logToUi(self, msg, *args):
		self.ui.send(self.ui.LOG, msg%args)

	def runServer(self):
		while not self.stop:
			rdyFds = reduce(operator.add,
				select.select([self.httpd.fileno()], [self.httpd.fileno()],
					[self.httpd.fileno()], 0.1))
			if rdyFds:
				self.httpd.handle_request()
	
	def stopServer(self):
		self.ui.send(self.ui.BAR, "Shutting down", 20)
		self.stop = True
		self.serverThread.join(10)


if __name__=="__main__":
	config = FeedConfig()
	server = getServer(config, AutoFeederHandler.makeFeederHandler(config))
	if len(sys.argv)>1:
		gui = ServerGui()
		server = GuiServerAdapter(gui, config, server)
		gui.geometry("600x150")
		gui.mainloop()
		server.stopServer()
	else:
		server.log_message = lambda msg, *args: sys.stderr.write((msg+"\n")%args)
		server.serve_forever()

