#!/usr/bin/env python


# import common stuff
from Observable import Observer,Observable

from commandsplus import getstatusoutputerror
from threading import Thread
import select
import urllib
import logging
import sys
import time
import os
import re
import errno

__version__ = "0.1.0"
__author__ = "Manuel Amador (Rudd-O) <dragonfear@gmail.com>"

# set up a logger
logger = logging.getLogger("amaroK Proxy")

# useful functions

DBTYPE_OTHER = 0
DBTYPE_MYSQL = 1

def sql_escape_other(string):
	newstring = []
	for c in string:
		if c == "'": newstring.append("''")
		else: newstring.append(c)
	return "'" + "".join(newstring) + "'"

def sql_escape_mysql(string):
	newstring = []
	for c in string:
		if c == "\\": newstring.append("\\\\")
		elif c == "'": newstring.append("\\'")
		else: newstring.append(c)
	return "'" + "".join(newstring) + "'"

# end functions

# dcop amarok implementation - private

class DcopAmarokFailed(Exception):
	def __init__(self,command,status,output):
		Exception.__init__(self)
		self.status = status
		self.command = command
		self.output = output
	def __str__(self):
		return "DCOP amaroK failure: command %s failed with status '%s' and output %s"%(self.command,self.status,self.output)

def dcop_amarok_commandsplus(object_name,method_name,*args):
	command = ["dcop","amarok",object_name,method_name]
	def turnintostring(aparm):  # falta manipuleo de dicts y otros iterables
		if aparm is True: return "true"
		if aparm is False: return "false"
		if type(aparm) is tuple:
			return tuple([turnintostring(a) for a in aparm])
		if type(aparm) is list:
			return [turnintostring(a) for a in aparm]
		if type(aparm) is int:
			return str(aparm)
		if type(aparm) is float:
			return str(aparm)
		return aparm
	
	newargs = turnintostring(args)
	for arg in newargs: command.append(arg)
	logger.debug("running command: %s"%command)
	status,output,error=getstatusoutputerror(command)
	if not (status.exited and status.return_value == 0):
		raise DcopAmarokFailed(command,status,output+error)
	result = output.strip()
	return result


_dcop_master = None
def dcop_amarok_pydcopwrapper(object_name,method_name,*args):
	import pydcopwrapper
	if len(args) == 0: prettyargs = ""
	else: prettyargs = ",".join(["%r"%e for e in args])
	global _dcop_master
	if _dcop_master is None: _dcop_master = pydcopwrapper.Master()
	logger.debug("calling DCOP method: %s.%s.%s(%s)"%("amarok",object_name,method_name,prettyargs))
	try:
		return _dcop_master.call("amarok",object_name,method_name,*args)
	except RuntimeError,e:
		raise DcopAmarokFailed("%s.%s.%s(%s)"%("amarok",object_name,method_name,prettyargs),1,str(e))



# detection of most optimum dcop implementation
try:
	import pydcopwrapper
	dcop_amarok = dcop_amarok_pydcopwrapper
except ImportError:
	dcop_amarok = dcop_amarok_commandsplus

# adaptors for dcop implementations
def tf2bool(string):
	logger.debug("tf2bool: %s %s",string,type(string))
	if string == "false": return False
	if string == "true": return True
	if type(string) is bool: return string
	if type(string) is int: return string == 1
	assert(False) # no se llego aca!!!

def bool2tf(boolean):
	if dcop_amarok == dcop_amarok_pydcopwrapper:
		return boolean
	elif dcop_amarok == dcop_amarok_commandsplus:
		if boolean: return "true"
		return "false"
	else: assert(False)
	
def noneifempty(string):
	if len(string) == 0: return None
	return string
#end adaptors


#amarok object abstractions

# the base class is a pure virtual class
class amaroKObject:
	objectName = None
	
	def __init__(self,parent):
		self.parent = parent
		
	def call(self,method_name,*args):
		return dcop_amarok(self.objectName,method_name,*args)


#the collection song object
class CollectionSong(object):
	
	def __init__(self,path,collectionObject):
		self.path = path
		self.collectionObject = collectionObject
		
	def getBaseName(self):
		return os.path.basename(self.path)
	
	# fixme if they request markup, they should escape markup tags from title and artist ( < > & are the culprits)
	def getShortName(self,markup=False):
		title = self.title
		artist = self.artist
		if markup: markup = "<b>%s</b>"
		else: markup = "%s"
		if title and artist: return "%s - %s"%(markup%artist,markup%title)
		elif title: return "%s"%(markup%title)
		elif artist: return "%s - %s"%(markup%artist,markup%"(no track name)")
		else: return "%s"%(markup%self.getBaseName())
		
	# ditto for the escaping here
	def getLongName(self,markup=False):
		title = self.title
		artist = self.artist
		if markup: markup = "<b>%s</b>"
		else: markup = "%s"
		if title and artist: return "%s by %s"%(markup%title,markup%artist)
		elif title: return "%s"%(markup%title)
		elif artist: return "%s by %s"%(markup%"(no track name)",markup%artist)
		else: return "%s"%(markup%self.getBaseName())
		
	def __get_artist(self):
		if not hasattr(self,"_artist"): self._artist = self.collectionObject._getArtist(self.path)
		return self._artist
	artist = property(__get_artist)
	
	def __get_album(self):
		if not hasattr(self,"_album"): self._album = self.collectionObject._getAlbum(self.path)
		return self._album
	album = property(__get_album)
	
	def __get_title(self):
		if not hasattr(self,"_title"): self._title = self.collectionObject._getTitle(self.path)
		return self._title
	title = property(__get_title)
	
	def __get_playcount(self):
		if not hasattr(self,"_playcount"): self._playcount = self.collectionObject._getPlaycount(self.path)
		return self._playcount
	def __set_playcount(self,playcount):
		self.collectionObject._setPlaycount(self.path,playcount)
		self._playcount = playcount
	playcount = property(__get_playcount,__set_playcount)
	
	def __get_score(self):
		if not hasattr(self,"_score"): self._score = self.collectionObject._getScore(self.path)
		return self._score
	def __set_score(self,score):
		self.collectionObject._setScore(self.path,score)
		self._score = score
	score = property(__get_score,__set_score)


# the collection object
class amaroKCollection(amaroKObject):
	objectName = "collection"
	
	def __init__(self,parent):
		amaroKObject.__init__(self,parent)
		self.__detectDatabase()
	
	def __detectDatabase(self):
		result = self.query("show tables;")
		if result:
			logger.debug("Detected MySQL as database type")
			self.sql_escape = sql_escape_mysql
			self.databaseType = DBTYPE_MYSQL
		else:
			logger.debug("Detected SQLite/PostgreSQL as database type")
			self.sql_escape = sql_escape_other
			self.databaseType = DBTYPE_OTHER
			
	def query(self,sql):
		retval = self.call("query",sql)
		if type(retval) is list: return retval
		return retval.splitlines() # returns a list of strings, or an empty list if nothing was returned
	
	def queryOne(self,sql):
		p = self.query(sql)
		if len(p) == 0: return None
		return p[0]
	
	def getSong(self,path):
		return CollectionSong(path,self)
	
	def _getArtist(self,path):
		return self.queryOne("select artist.name from artist inner join tags on tags.artist = artist.id where tags.url = %s;"%self.sql_escape(path))
	
	def _getAlbum(self,path):
		return self.queryOne("select album.name from album inner join tags on tags.album = album.id where tags.url = %s;"%self.sql_escape(path))
	
	def _getTitle(self,path):
		return self.queryOne("select title from tags where tags.url = %s;"%self.sql_escape(path))
	
	def _getPlaycount(self,path):
		try:
			playcount = self.queryOne("select playcounter from statistics where statistics.url = %s;"%self.sql_escape(path))
			if playcount is None: return 0
			return int(playcount)
		except ValueError,e: return 0
		
	def _setPlaycount(self,path,playcount):
		if self.query("select url from statistics where statistics.url = %s;"%self.sql_escape(path)):
			self.query("update statistics set playcounter = %d where statistics.url = %s;"%(playcount,self.sql_escape(path)))
		else:
			cdtime = int(round(time.time(),0))
			self.query("insert into statistics (url,createdate,accessdate,percentage,playcounter) values(%s,%d,%d,%s,%d);"%(self.sql_escape(path),cdtime,cdtime,'NULL',playcount))
	
	def _getScore(self,path):
		try:
			score = self.queryOne("select percentage from statistics where statistics.url = %s;"%self.sql_escape(path))
			if score is None: return None
			return float(score)
		except ValueError,e: return None
			
	def _setScore(self,path,score):
		if score is None: dascore = "NULL"
		else: dascore = "%f"%score
		if self.query("select url from statistics where statistics.url = %s;"%self.sql_escape(path)):
			self.query("update statistics set percentage = %s where statistics.url = %s;"%(dascore,self.sql_escape(path)))
		else:
			cdtime = int(round(time.time(),0))
			self.query("insert into statistics (url,createdate,accessdate,percentage,playcounter) values(%s,%d,%d,%s,%d);"%(self.sql_escape(path),cdtime,cdtime,dascore,0))
	
	def totalAlbums(self): return int(self.call("totalAlbums"))
	def totalArtists(self): return int(self.call("totalArtists"))
	def totalCompilations(self): return int(self.call("totalCompilations"))
	def totalGenres(self): return int(self.call("totalGenres"))
	def totalTracks(self): return int(self.call("totalTracks"))
	def scanCollection(self): self.call("scanCollection")
	def scanCollectionChanges(self): self.call("scanCollectionChanges")


#the player object
class amaroKPlayer(amaroKObject,Observable):
	objectName = "player"
	def __init__(self,parent):
		amaroKObject.__init__(self,parent)
		Observable.__init__(self)
	def dynamicModeStatus(self):
		return tf2bool(self.call("dynamicModeStatus"))
	def equalizerEnabled(self):
		return tf2bool(self.call("equalizerEnabled"))
	def isPlaying(self):
		return tf2bool(self.call("isPlaying"))
	def randomModeStatus(self):
		return tf2bool(self.call("randomModeStatus"))
	def repeatPlaylistStatus(self):
		return tf2bool(self.call("repeatPlaylistStatus"))
	def repeatTrackStatus(self):
		return tf2bool(self.call("repeatTrackStatus"))
	def getVolume(self): # int 0-100
		return int(self.call("getVolume"))
	def sampleRate(self): # int 0-100
		result = int(self.call("sampleRate"))
		if result == -2: return None
		return result
	def score(self): # returns 0 if nothing playing
		return int(self.call("score"))
	def status(self): # returns 0 if nothing playing, 1 if paused, 2 if playing
		return int(self.call("status"))
	def trackCurrentTime(self): # returns 0 if nothing playing
		return int(self.call("trackCurrentTime"))
	def trackPlayCounter(self): # returns 0 if nothing playing
		return int(self.call("trackPlayCounter"))
	def trackTotalTime(self): # returns 0 if nothing playing
		return int(self.call("trackTotalTime"))
	def album(self): # returns None if wasnt playing or album was empty
		return noneifempty(self.call("album"))
	def artist(self): # returns None if wasnt playing or artist was empty
		return noneifempty(self.call("artist"))
	def bitrate(self): # returns None if wasnt playing or no bitrate, otherwise an integer number for kbps
		result = noneifempty(self.call("bitrate"))
		if result is None: return None
		if result == "?": return None
		if result[-5:] == " kbps": result = result[:-5]
		return int(result) # to account for " kbps"
	def comment(self): # returns None if wasnt playing or artist was empty
		return noneifempty(self.call("comment"))
	def coverImage(self): # returns None if no cover, path to cover image if exists
		result = noneifempty(self.call("coverImage"))
		if "@nocover.png" in result: return None
		return result
	def currentTime(self): # returns current time encoded in mm:ss format
		return noneifempty(self.call("currentTime"))
	def encodedURL(self): # returns current time encoded in mm:ss format
		return noneifempty(self.call("encodedURL"))
	def engine(self): # returns none if empty
		return noneifempty(self.call("engine"))
	def genre(self): # returns none if empty
		return noneifempty(self.call("genre"))
	def lyrics(self): # returns none if empty
		return noneifempty(self.call("lyrics"))
	def lyricsByPath(self,path): # returns none if empty
		return noneifempty(self.call("lyricsByPath",path))
	def nowPlaying(self): # returns none if empty
		return noneifempty(self.call("nowPlaying"))
	def path(self): # returns none if empty
		return noneifempty(self.call("path"))
	def setContextStyle(self,styleName): # pass a style name, return a verbose description directly from amaroK, or none if nothing was returned
		return noneifempty(self.call("setContextStyle",styleName))
	def title(self): # returns none if empty
		return noneifempty(self.call("title"))
	def totalTime(self): # returns none if empty
		return noneifempty(self.call("totalTime"))
	def track(self): # returns none if empty
		result = noneifempty(self.call("track"))
		if result is None: return None
		return int(result)
	def year(self): # returns none if empty
		result = noneifempty(self.call("year"))
		if result is None: return None
		return int(result)
	def configEqualizer(self):
		self.call("configEqualizer")
	def enableDynamicMode(self,enable): #enable is boolean
		self.call("enableDynamicMode",bool2tf(enable))
	def enableOSD(self,enable): #enable is boolean
		self.call("enableOSD",bool2tf(enable))
	def enableRandomMode(self,enable): #enable is boolean
		self.call("enableRandomMode",bool2tf(enable))
	def enableRepeatPlaylist(self,enable): #enable is boolean
		self.call("enableRepeatPlaylist",bool2tf(enable))
	def enableRepeatTrack(self,enable): #enable is boolean
		self.call("enableRepeatTrack",bool2tf(enable))
	def mediaDeviceMount(self):
		self.call("mediaDeviceMount")
	def mediaDeviceUmount(self):
		self.call("mediaDeviceUmount")
	def mute(self):
		self.call("mute")
	def next(self):
		self.call("next")
	def pause(self):
		self.call("pause")
	def play(self):
		self.call("play")
	def playPause(self):
		self.call("playPause")
	def prev(self):
		self.call("prev")
	def queueForTransfer(self,path): # returns none if empty
		self.call("queueForTransfer",path)
	def seek(self,seconds):
		self.call("seek",seconds)
	def seekRelative(self,seconds):
		self.call("seekRelative",seconds)
	def setEqualizerEnabled(self,enable): #enable is boolean
		self.call("setEqualizerEnabled",bool2tf(enable))
	def setEqualizer(self,band1,band2,band3,band4,band5,band6,band7,band8,band9,band10,band11): # bands are 11, and each can range from -100 to 100, where 0 is defeat
		band1,band2,band3,band4,band5,band6,band7,band8,band9,band10,band11 = [ s for s in [band1,band2,band3,band4,band5,band6,band7,band8,band9,band10,band11] ]
		self.call("setEqualizer",band1,band2,band3,band4,band5,band6,band7,band8,band9,band10,band11)
	def setEqualizerPreset(self,presetName): # presetName is an existing preset name in amaroK
		self.call("setEqualizer",presetName)
	def setLyricsByPath(self,path,lyrics): # lyrics is a string
		self.call("setLyricsByPath",path,lyrics)
	def setScoreByPath(self,path,score): # score is an int ranging from 0 to 100
		self.call("setScoreByPath",path,score)
	def setScore(self,score): # score is an int ranging from 0 to 100
		self.call("setScore",score)
	def setVolume(self,volume): # volume is an int ranging from 0 to 100
		self.call("setVolume",volume)
	def setVolume(self,volume): # volume is an int ranging from 0 to 100
		self.call("setVolume",volume)
	def showOSD(self):
		self.call("showOSD")
	def stop(self):
		self.call("stop")
	def transferDeviceFiles(self):
		self.call("transferDeviceFiles")
	def volumeDown(self):
		self.call("volumeDown")
	def volumeUp(self):
		self.call("volumeUp")
	
	
	
from xml.dom import minidom
import locale


# the fields to be shown via http
P_FIELDS = ("URL","Path","Artist", "Title", "Album", "TrackNo", "Length", "Genre",  "Score" )

class PlaylistTrack(object):
	"""Class that holds the information of one track in the current playlist"""
	__slots__ = P_FIELDS
	
	def __init__(self, **kwargs):
		for key,value in kwargs.iteritems():
			setattr(self, key, value)


class Playlist:
	"""The Playlist class represents the Playlist as one object. It holds all
	needed information and does most of the work"""
	
	def __init__(self,filename):
		self.filename = filename
		self.tracks = []
		self.mtime = None
		self.reread()

	def has_changed(self):
		if self.mtime != os.stat(self.filename)[-2]: return True
		return False
	
	def reread(self):
		if self.has_changed():
			self.mtime = os.stat(self.filename)[-2]
			logger.debug("Playlist changed: rereading")
			self._buildDoc()
	
	def _buildDoc(self):
		"""Build the DOM-doc and calls the _extractTracks-Method"""
		self.doc = minidom.parse(self.filename)
		self._extractTracks()
	
	def _extractTracks(self):
		self.tracks = []
		append = self.tracks.append
		for item in self.doc.getElementsByTagName("item"):
			curTrack = PlaylistTrack()
			for elem in P_FIELDS:
				try: # this is silently swallowing unicodeencodeerrors FIXME
					value = item.getElementsByTagName(elem)[0].firstChild.nodeValue
				except:
					value = None
				setattr(curTrack, elem, value)
			url = item.getAttribute("url")
			setattr(curTrack,"URL",url)
			if url.startswith("file://"):
				path = urllib.unquote(url[7:].encode("ascii")) # stupid workaround, shit!
				setattr(curTrack,"Path",path)
			else:
				setattr(curTrack,"Path",None)
			append(curTrack)


def encodepathforamarok(path):
	
	scheme = None
	rest = None
	separator = "://"
	for position in range(len(path)):
		threechars = path[position:position+3]
		if threechars == separator: # we found the separator
			scheme = path[0:position]
			rest = path[position+4:]
			break
	
	if scheme is None: return "file://" + urllib.pathname2url(os.path.abspath(path))
	return path


#the playlist object
class amarokPlaylist(amaroKObject):
	objectName = "playlist"
	
	def getTracks(self): # i should lock this
		if hasattr(self,"_playlistObject"):
			self.saveCurrentPlaylist()
			self._playlistObject.reread()
		else:
			self._playlistObject = Playlist(self.getPlaylistPath())
		return self._playlistObject.tracks
		
	def getPlaylistPath(self):
		if not hasattr(self,"_playlistPath"): self._playlistPath = self.saveCurrentPlaylist()
		return self._playlistPath
	
	def getActiveIndex(self): # none if no active index, an integer otherwise, 0-based
		result = int(self.call("getActiveIndex"))
		if result == -1: return None
		return result
	def getTotalTrackCount(self): # none if no active index, an integer otherwise
		return int(self.call("getTotalTrackCount"))
	def saveCurrentPlaylist(self):
		return self.call("saveCurrentPlaylist")
	def addMedia(self,path):
		self.call("addMedia",encodepathforamarok(path))
	def clearPlaylist(self):
		self.call("clearPlaylist")
	def playByIndex(self,index): # index is an integer, 0-based
		self.call("playByIndex",index)
	def playMedia(self,path):
		self.call("playMedia",encodepathforamarok(path))
	def popupMessage(self,message):
		self.call("popupMessage",message)
	def removeCurrentTrack(self):
		self.call("removeCurrentTrack")
	def repopulate(self):
		self.call("repopulate")
	def saveM3u(self,path,relativePaths):
		self.call("saveM3u",path,bool2tf(relativePaths))
	def setStopAfterCurrent(self,stop):
		self.call("setStopAfterCurrent",bool2tf(stop))
	def shortStatusMessage(self,message):
		self.call("shortStatusMessage",message)
	def shufflePlaylist(self):
		self.call("shufflePlaylist")
	def togglePlaylist(self):
		self.call("togglePlaylist")
	

#the script manager object
# FIXME add functions for functions taking and returning QSTringList-based parameters
class amaroKScriptManager(amaroKObject):
	objectName = "script"
	
	def runScript(self,scriptName):
		self.call("runScript",scriptName)
	def stopScript(self,scriptName):
		self.call("stopScript",scriptName)
	def addCustomMenuItem(self,submenu,itemTitle):
		self.call("addCustomMenuItem",submenu,itemTitle)
	def removeCustomMenuItem(self,submenu,itemTitle):
		self.call("removeCustomMenuItem",submenu,itemTitle)


# the smart playlist collection
S_FIELDS = ("sqlquery","name")

class SmartPlaylist(object):
	__slots__ = S_FIELDS

	def __init__(self, **kwargs):
		for key,value in kwargs.iteritems():
			setattr(self, key, value)

	def __str__(self):
		return "Smart playlist %s: %s"%(self.name,self.sqlquery)


class amaroKSmartPlaylistCollection(amaroKObject):
	def __init__(self,parent):
		"""The Constructor takes no arguments."""
		amaroKObject.__init__(self,parent)
		self.filename = os.path.join(os.path.dirname(parent.playlist.getPlaylistPath())
						,"smartplaylistbrowser_save.xml")
		self.playlists = []
		self.mtime = None
		self.reread()

	def __getitem__(self,playlist_name):
		for a in self.playlists:
			if a.name == playlist_name: return a
		raise KeyError,playlist_name
			
	def keys(self):
		return [ a.name for a in self.playlists ]
	
	def values(self):
		return [ a for a in self.playlists ]
	
	def has_changed(self):
		if self.mtime != os.stat(self.filename)[-2]: return True
		return False

	def reread(self):
		if self.has_changed():
		    import logging
		    logger.debug("Smart playlist save changed: rereading")
		    self.mtime = os.stat(self.filename)[-2]
		    self._buildDoc()

	def _buildDoc(self):
		"""Build the DOM-doc and calls the _extractPlaylists-Method"""
		self.doc = minidom.parse(self.filename)
		self._extractPlaylists()

	def _extractPlaylists(self):
		self.playlists = []
		append = self.playlists.append
		for item in self.doc.getElementsByTagName("smartplaylist"):
			curPlaylist = SmartPlaylist()
			for elem in S_FIELDS:
				try:
					value = item.getElementsByTagName(elem)[0].firstChild.nodeValue
				except:
					continue
				if elem == "sqlquery":
					value = re.sub("ORDER BY .*","",value)
					value = re.sub("SELECT .* FROM ","SELECT tags.url FROM ",value)
					value = re.sub(";","",value)
					setattr(curPlaylist, elem, value.encode(locale.getpreferredencoding()))
				else:
					print elem,value
					setattr(curPlaylist, elem, value.encode(locale.getpreferredencoding()))
			name = item.getAttribute("name").encode(locale.getpreferredencoding())
			setattr(curPlaylist,"name",name)
			append(curPlaylist)


# the monitor object
# FIXME the engine state change events should be broadcast FROM THE PLAYER, not from here into amaroKProxy
class amaroKMonitor(amaroKObject,Thread):
	def __init__(self,parent,commChannel):
		amaroKObject.__init__(self,parent)
		Thread.__init__(self)
		self.setDaemon(True)
		self.commChannel = commChannel
	
	def run(self):
		self.parent.broadcastEvent("amaroKAvailable")
		po = select.poll()
		po.register(self.commChannel,select.POLLIN|select.POLLHUP|select.POLLERR|select.POLLPRI)
		while True:
			try: readyfds = po.poll(1000) # wait one second
			except select.error,e:
				if e.args[0] == 4: continue # we were stopped, and now we're cont'ed
				else: raise
			if not readyfds: continue # spin because nothing good came out of this wait instance
			try: line = self.commChannel.readline().strip()
			except IOError,e:
				if e.errno == 5: line = None # we are exiting
				elif e.errno == 4: continue # we were stopped, and now we're cont'ed
				else: raise
			if not line:
				# amarok sent us no event, probably because we received
				# a signal, so we just bail out of the loop
# 				self.logger.debug( "Empty message, bailing")
				self.parent.broadcastEvent("amaroKExit")
				break
			# amarok sent us an event, here we do something useful
# 				self.logger.debug( "Processing: %s"%line)
			
			if line.startswith("customMenuClicked:"):
				self.parent.broadcastEvent("customMenuClicked",line[19:])
			elif line == "configure":
				self.parent.broadcastEvent("configure")
			elif line == "trackChange":
				self.parent.player.broadcastEvent("trackChange")
			elif line == "engineStateChange: empty":
				self.parent.player.broadcastEvent("stopped")
			elif line == "engineStateChange: playing":
				self.parent.player.broadcastEvent("playing")
			elif line == "engineStateChange: paused":
				self.parent.player.broadcastEvent("paused")
			else:
				self.parent.broadcastEvent("unknownEvent",line)


class amaroKContextBrowser(amaroKObject,Thread,Observable):
	objectName = "html-widget2"
	
	def __init__(self,parent):
		Thread.__init__(self)
		self.setDaemon(True)
		amaroKObject.__init__(self,parent)
		Observable.__init__(self)
		self.filename = os.path.join(os.path.dirname(parent.playlist.getPlaylistPath())
						,"contextbrowser.html")
	
	def getFilePath(self):
		return self.filename
	
	def run(self):
		self.mtime = os.stat(self.filename).st_mtime
		while True:
			mtime = os.stat(self.filename).st_mtime
			if mtime != self.mtime:
				time.sleep(1) # to avoid races
				self.broadcastEvent("contentsChanged")
				self.mtime = mtime
			time.sleep(1)

	def evalJS(self,javascript):
		self.call("evalJS",javascript)

class amaroKProxy(Observable):
	collection = None
	player = None
	playlist = None
	script = None
	smartPlaylistCollection = None
	monitor = None
	contextBrowser = None
	
	def __init__(self):
		Observable.__init__(self)
		
		self.collection = amaroKCollection(self)
		self.player = amaroKPlayer(self)
		self.playlist = amarokPlaylist(self)
		self.script = amaroKScriptManager(self)
		self.smartPlaylistCollection = amaroKSmartPlaylistCollection(self)
		self.monitor = amaroKMonitor(self,sys.stdin)
		self.contextBrowser = amaroKContextBrowser(self)
		
	def startMonitoring(self):
		self.monitor.start()
		self.contextBrowser.start()
		
	def isAlive(self):
		return self.monitor.isAlive()

__all__ = ["amaroKProxy","CollectionSong","amaroKCollection","DcopAmarokFailed","DBTYPE_OTHER","DBTYPE_MYSQL"]