#!/usr/bin/python
#******************************************************************************
# Module:
#	Background
#
# Description:
#	This script takes pictures from a feed source, directory or from the
#	Gnome2 backgrounds.xml file and displays them on the root window of
#	a Gnome desktop in slideshow fashion.
#
#	Example command line:
#		background.py		\
#			--feed=flickr	\
#			--url='http://api.flickr.com/services/feeds/photoset.gne?'
#				  'set=72157600024135086&nsid=79038663@N00&lang=en-us'	\
#			--delay=3		\
#			--save=/home/john/Set1
#
#		Yeah, you need to glue together the two parts of the URL that I
#		broke over the line.
#
#		This command will load all pictures from the url feed, saving them
#		to the /home/john/Set1 directory, display each of them on the
#		desktop, delay 3 seconds, then get the next.  The feed type is
#		Flickr.
#		
# Author:
#	John Stevens
#******************************************************************************

import gconf
import os
import random
import httplib

from 	optparse		import	OptionParser
from	time			import	sleep
from	urlparse		import	urlparse, urlunparse
from	xml.dom.minidom	import	parse, parseString
from	string			import	join, split
from	sys				import	exit
from	os				import	listdir, access, F_OK, stat, mkdir
from	stat			import	*

#	Get gconf client.
client = gconf.client_get_default()

def getData(
	nodes ):

	#	Get all text node data, concatenate it.
	data = ''
	for node in nodes:
		if node.nodeType == node.TEXT_NODE:
			data = data + node.data

	#	Return concatenated string.
	return data

#------------------------------------------------------------------------------
# Class:
#	Feed
#
# Description:
#	Base class for Feed classes (those classes prefixed by a type of
#	feed, ending in the word Feed).
#
#	This class provides the shared implementation of all feeds.
#------------------------------------------------------------------------------

class Feed:

	#..........................................................................
	# Method:
	#	__init__
	#
	# Parameters:
	#	self           - Reference to object instance.
	#	client         - GConf2 client.
	#	url            - Universal resource locator for picture feed.
	#	saveDirectory  - Directory to save images into.
	#	options        - Background picture options (scaled, centered, etc.).
	#	shading        - Solid color background shading type.
	#	primaryColor   - Solid color background primary color.
	#	secondaryColor - Solid color background primary color.
	#
	# Description:
	#	Initialize data attributes and invoke readFeed method to get feed
	#	description file.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def __init__(
		self,
		client,
		url,
		saveDirectory,
		options,
		shading,
		primaryColor,
		secondaryColor ):

		#	Save background values.
		self.client         = client
		self.url            = url
		self.saveDirectory  = saveDirectory
		self.options        = options
		self.shading        = shading
		self.primaryColor   = primaryColor
		self.secondaryColor = secondaryColor

		#	Create list to store feed URLs or file names.
		self.links = []

		#	Starting number of background in list.
		self.number = 0

		#	Set current to None.
		self.current     = None
		self.currentFile = None

		#	Load feed.
		self.readFeed()

		#	Does the directory to save into already exist?
		if access( self.saveDirectory, F_OK ):
			#	Yes.  Is it a directory?
			mode = os.stat( self.saveDirectory )[ ST_MODE ]
			if not S_ISDIR( mode ):
				#	No.  Issue error and quit.
				print "Can't use %s for save directory!" % self.saveDirectory
				exit( 1 )
		elif mkdir( self.saveDirectory ):
			print "Can't create %s for save directory!" % self.saveDirectory

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Read the feed and populate the links list with with either
	#	URLs or file names for background images.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def readFeed(
		self ):

		#	Sub-class responsibility.
		pass

	#..........................................................................
	# Method:
	#	nextBackground
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Select the next element from the links list, save that as the next
	#	background to set.
	#
	#	Override this to implement random ordering, reverse ordering, etc.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def nextBackground(
		self ):

		#	Next time around.
		if self.number >= len( self.links ):
			self.number = 0
			return True

		#	Get link number.
		link        = self.links[ self.number ]
		self.number = self.number + 1
		done        = False

		#	Save current file name.
		self.current = link

	#..........................................................................
	# Method:
	#	setBackground
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Set the current background on to the Gnome desktop.
	#
	#	Override this to change how the image is set onto the desktop.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def setBackground(
		self ):

		#	Set a background.
		self.client.set_string(
			"/desktop/gnome/background/picture_filename",
			self.currentFile )
		self.client.set_string(
			"/desktop/gnome/background/color_shading_type",
			self.shading )
		self.client.set_string(
			"/desktop/gnome/background/picture_options",
			self.options )
		self.client.set_string(
			"/desktop/gnome/background/primary_color",
			self.primaryColor )
		self.client.set_string(
			"/desktop/gnome/background/secondary_color",
			self.secondaryColor )

#------------------------------------------------------------------------------
# Class:
#	DirectoryFeed
#
# Description:
#	Get the pictures to display from a directory.  These pictures must
#	be JPEGs.
#------------------------------------------------------------------------------

class DirectoryFeed( Feed ):

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Interprets the given URL as a directory path and loads the links
	#	list with all JPEG image files found in the directory.
	#
	#	In this specialization, the links list is a list of fully qualified
	#	file names.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def readFeed(
		self ):

		#	Get list of files.
		for item in listdir( self.url ):
			#	Get item link.
			( base,
			  extension ) = os.path.splitext(
			  	item )

			#	Is this a JPEG file?
			if extension == '.jpg':
				self.links.append(
					os.path.join(
						self.url,
						item ) )

	#..........................................................................
	# Method:
	#	nextBackground
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Delegate to the Feed class, then use the current links list
	#	element as the current file name.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def nextBackground(
		self ):

		#	Get next background.
		done = Feed.nextBackground(
			self )

		#	Set current file.
		self.currentFile = self.current

		#	Return done.
		return done

#------------------------------------------------------------------------------
# Class:
#	GnomeFeed
#
# Description:
#	Get the pictures to display from the Gnome backgrounds XML document.
#	The document type description (DTD) expected by this class parses
#	the XML document used by GNOME2 to store the list of background
#	images that "Desktop Background Images" uses.
#------------------------------------------------------------------------------

class GnomeFeed( Feed ):

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Parse the Gnome backgrounds.xml file, storing all backgrounds and
	#	their associated attributes in the links list.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def readFeed(
		self ):

		#	Set Gnome2 directory and backgrounds XML file.
		backgrounds = os.path.join(
			os.environ[ "HOME" ],
			".gnome2",
			"backgrounds.xml" )

		#	Open backgrounds XML file and parse it.
		document   = parse(
			open(
				backgrounds ))
		wallPapers = document.getElementsByTagName(
			"wallpapers" )[ 0 ].getElementsByTagName(
				"wallpaper" )

		#	Get all wall papers.
		for paper in wallPapers:
			#	Get deleted attribute.  If deleted is not false, skip
			#	this one as it has been deleted.
			if paper.getAttribute('deleted') != 'false':
				continue

			#	Get wallpaper name.
			node  = paper.getElementsByTagName(
				"name")[ 0 ]
			name  = getData(
				node.childNodes )

			#	Get wallpaper name.
			node     = paper.getElementsByTagName(
				"filename" )[ 0 ]
			fileName = getData(
				node.childNodes )

			#	Get options.
			node    = paper.getElementsByTagName(
				"options" )[ 0 ]
			options = getData(
				node.childNodes )

			#	Get shading type.
			node    = paper.getElementsByTagName(
				"shade_type" )[ 0 ]
			shading = getData(
				node.childNodes )

			#	Get primary background color.
			node         = paper.getElementsByTagName(
				"pcolor" )[ 0 ]
			primaryColor = getData(
				node.childNodes)

			#	Get secondary background color.
			node           = paper.getElementsByTagName(
				"scolor" )[ 0 ]
			secondaryColor = getData(
				node.childNodes )

			self.links.append(
				( name,
				  fileName,
				  options,
				  shading,
				  primaryColor,
				  secondaryColor ) );

	#..........................................................................
	# Method:
	#	nextBackground
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Delegate to the Feed class, then use the current links list
	#	element as a tuple containing all of the Gnome background
	#	attributes.  Set the object attributes from the current tuple.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def nextBackground(
		self ):

		#	Get link number.
		done = Feed.nextBackground(
			self )

		#	Set data attributes.
		( name,
		  self.currentFile,
		  self.options,
		  self.shading,
		  self.primaryColor,
		  self.secondaryColor ) = self.current

		#	Return done.
		return done

#------------------------------------------------------------------------------
# Class:
#	WebFeed
#
# Description:
#	Get the pictures to display from a Web Feed.  This is still abstract,
#	as it requires sub-classing to match the DTD of the XML feed file.
#	See: Rss2Feed and FlickrFeed.
#------------------------------------------------------------------------------

class WebFeed( Feed ):

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Delegate to the Feed class, then if the file is not already present
	#	in the temporary directory, load the image data from the URL
	#	currently stored in the current data attribute from the web-based
	#	feed server.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def nextBackground(
		self ):

		#	Get link number.
		done = Feed.nextBackground(
			self )

		#	Parse URL.
		list = urlparse(
			self.current )
		( path,
		  fileName ) = os.path.split(
		  	list[ 2 ] )

		#	Get background file name.
		self.currentFile = os.path.join(
			self.saveDirectory,
			fileName )

		#	If file does not already exist, get it.
		#	TODO: add in update checking code.
		if not access( self.currentFile, F_OK ):
			#	Create connection to feed server.
			connection = httplib.HTTPConnection(
				list[ 1 ] )
			request    = connection.request(
				"GET",
				list[ 2 ] )
			response   = connection.getresponse()

			#	Create picture file.
			file = open(
				self.currentFile,
				"w" )

			#	Read data and store in picture file.
			data = response.read( 512 )
			while len( data ) > 0:
				file.write(
					data )
				data = response.read( 512 )

			#	Close picture file and connection.
			file.close()
			connection.close()

		#	Return done.
		return done

#------------------------------------------------------------------------------
# Class:
#	FlickrFeed
#
# Description:
#	Get the pictures to display from a flickr feed.  Unfortunately, as far
#	as I can tell, the Flickr feed format is not standard, so this class
#	has specialized code to parse the XML feed description.
#------------------------------------------------------------------------------

class FlickrFeed( WebFeed ):

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Read the feed XML file from the web-based feed server.  Parse
	#	the feed file assuming that it is marked up in flickr format.
	#
	#	Store image URLs in links list.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def readFeed(
		self ):

		#	Get host and path.
		list = urlparse(
			self.url )
		host = list[ 1 ]
		l = [ '', '' ]
		for i in xrange(2, len( list )):
			l.append(
				list[ i ] )
		path = urlunparse(
			l )

		#	Load XML feed.
		connection = httplib.HTTPConnection(
			host )
		request    = connection.request(
			"GET",
			path )
		response   = connection.getresponse()
		xmlData    = response.read()
		connection.close()

		#	Get feed data.
		document = parseString(
			xmlData )
		rss      = document.getElementsByTagName(
			"feed" )
		if len( rss ) == 0:
			#	This is a Flickr RSS2 feed.
			rss = document.getElementsByTagName(
				"channel" )[ 0 ]

			#	Get entries.
			items = rss.getElementsByTagName(
				"item" )

			#	For all entries, do:
			for item in items:
				#	Get item link.
				linkList = item.getElementsByTagName(
					"media:content" )

				#	Does a link have an attribute that indicates
				#	it is a picture?
				for linkNode in linkList:
					#	Get link type.
					linkType = linkNode.getAttribute(
						"type" )

					#	Is this a jpeg image?
					if linkType == "image/jpeg":
						link = linkNode.getAttribute(
							"url" )
						self.links.append(
							link )
		else:
			#	This is a "normal" Flickr feed.  Get entries.
			rss   = rss[ 0 ]
			items = rss.getElementsByTagName(
				"entry" )

			#	For all entries, do:
			for item in items:
				#	Get item link.
				linkList = item.getElementsByTagName(
					"link" )

				#	Does a link have an attribute that indicates
				#	it is a picture?
				for linkNode in linkList:
					#	Get link type.
					linkType = linkNode.getAttribute(
						"type" )

					#	Is this a jpeg image?
					if linkType == "image/jpeg":
						link = linkNode.getAttribute(
							"href" )
						self.links.append(
							link )

		#	Discard document.
		del document

#------------------------------------------------------------------------------
# Class:
#	Rss2Feed
#
# Description:
#	Get the pictures to display from a standard RSS2 feed.  The document
#	type description (DTD) expected by this class parses XML feeds that
#	work in a variety of feed readers, so is probably more standard.
#------------------------------------------------------------------------------

class Rss2Feed( WebFeed ):

	#..........................................................................
	# Method:
	#	readFeed
	#
	# Parameters:
	#	self - Reference to object instance.
	#
	# Description:
	#	Read the feed XML file from the web-based feed server.  Parse
	#	the feed file assuming that it is marked up in standard RSS2
	#	format.
	#
	#	Store image URLs in links list.
	#
	# Returns:
	#	Nothing.
	#..........................................................................

	def readFeed(
		self ):

		#	Reload XML feed.
		list = urlparse(
			self.url )
		connection = httplib.HTTPConnection(
			list[ 1 ] )
		request    = connection.request(
			"GET",
			list[ 2 ] )
		response   = connection.getresponse()
		xmlData    = response.read()
		connection.close()

		#	Get feed data.
		document = parseString(
			xmlData )
		rss      = document.getElementsByTagName(
			"rss" )[ 0 ]
		channel  = rss.getElementsByTagName(
			"channel" )[ 0 ]
		items    = channel.getElementsByTagName(
			"item" )

		#	For all items, do:
		for item in items:
			#	Does this have media nodes?
			node = item.getElementsByTagName(
				"media:content" ) [ 0 ]
			if node != None:
				#	Get link type.
				linkType = node.getAttribute(
					"type" )

				#	Is this a jpeg image?
				if linkType == "image/jpeg":
					#	Yes.  Get attribute and append to list.
					link = node.getAttribute(
						"url" )
					self.links.append(
						link )
			else:
				#	Get item link.
				linkNode = item.getElementsByTagName(
					"link" )[ 0 ]
				link     = getData(
					linkNode.childNodes )

				#	Append link to list.
				self.links.append(
					link )

		#	Discard document.
		del document

#==============================================================================
#	Begin program.
#==============================================================================

#	Parse command line options.
parser = OptionParser()

#	Add options.
parser.add_option(
	"-f",
	"--feed",
	dest="feedType",
	help="Set feed type (flickr, rss2, gnome, directory)",
	default="rss2" )
parser.add_option(
	"-d",
	"--delay",
	dest="delay",
	help="Set slideshow delay in seconds",
	default=10 )
parser.add_option(
	"-r",
	"--repeat",
	dest="repeatTimes",
	help="Number of times to repeat (0 for forever)",
	default=1 )
parser.add_option(
	"-s",
	"--save",
	dest="saveDirectory",
	help="Location to save feed images",
	default="/var/tmp" )
parser.add_option(
	"-u",
	"--url",
	dest="url",
	help="Set URL of feed" )

( options,
  args ) = parser.parse_args()

#	Build a backgrounds list.
if options.feedType == "rss2":
	backgrounds = Rss2Feed(
		client,
		options.url,
		options.saveDirectory,
		"scaled",
		"horizontal",
		"#0017FF",
		"#000000" )
elif options.feedType == "flickr":
	backgrounds = FlickrFeed(
		client,
		options.url,
		options.saveDirectory,
		"scaled",
		"horizontal",
		"#0017FF",
		"#000000" )
elif options.feedType == "gnome":
	backgrounds = GnomeFeed(
		client,
		options.url,
		options.saveDirectory,
		"scaled",
		"horizontal",
		"#0017FF",
		"#000000" )
elif options.feedType == "directory":
	backgrounds = DirectoryFeed(
		client,
		options.url,
		options.saveDirectory,
		"scaled",
		"horizontal",
		"#0017FF",
		"#000000" )
else:
	print "Illegal feed type."
	exit( 1 )

#	Number of times to loop.
repeatTimes = int( options.repeatTimes )

#	Get feed pictures, put on desktop, delay, get next, ad infinitum.
delayTime = int( options.delay )
if repeatTimes == 0:
	while 1:
		#	Get next background image.
		backgrounds.nextBackground()

		#	Set a background.
		backgrounds.setBackground()

		#	Sleep.
		sleep( delayTime )
else:
	for i in xrange(0, repeatTimes):
		#	Get next background image.
		while not backgrounds.nextBackground():
			#	Set a background.
			backgrounds.setBackground()

			#	Sleep.
			sleep( delayTime )
