# Pyggle: An OpenGiggle client and server in Python.                 vim:ts=4
# Copyright 2001 Adam Sampson <ats@offog.org>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

import socket, string, time, sys, whrandom
from SocketServer import *

def trimcrlf(s):
	"""Remove a crlf or just a lf from the end of a string."""
	if s[-2:] == "\r\n": return s[:-2]
	if s[-1:] == "\n": return s[:-1]
	return s

class GiggleClient:
	"""An OpenGiggle client.
Example of use:
g = Giggle.GiggleClient("giggle.opengiggle.org")
print g.giggle()
"""

	def __init__(self, server, port = 4700, clientid = "Pyggle 0.1"):
		"""Construct a GiggleClient. Invoke as
GiggleClient(server[, port[, clientid]])"""
		self.server = server
		self.port = port
		self.clientid = clientid

	def query(self):
		"""Return a tuple (serverstring, gigglestring)."""
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((self.server, self.port))
		s.send("GIGGLE/1.0 " + self.clientid + "\r\n")
		s.send("\r\n")
		r = ""
		while 1:
			d = s.recv(1024)
			if not d: break
			r = r + d
		rl = string.split(r, "\r\n")
		if len(rl) < 2:
			return ("", "")
		else:
			return (rl[0], rl[1])

	def giggle(self):
		"""Return a giggle."""
		(s, g) = self.query()
		return g

class GiggleRequestHandler(StreamRequestHandler):
	def readline(self):
		"""Read a line from the client. Return None if an error occurs."""
		try:
			return trimcrlf(self.rfile.readline())
		except IOError:
			self.server.log("Incomplete request from " + self.client_address[0])
			return None

	def handle(self):
		"""Handle a client request."""
		l = self.readline()
		if not l: return
		ls = string.split(l, " ", 1)
		if len(ls) < 2:
			client = "Unknown"
		else:
			client = ls[1]
		while 1:
			l = self.readline()
			if l is None: return
			if l == "": break
		self.server.log('"' + client + '" 1.0 ' + self.client_address[0])
		self.wfile.write(self.server.response())

class GiggleServer(ForkingMixIn, TCPServer):
	"""An OpenGiggle server."""

	def log(self, message):
		"""Write a timestamped message to the log file."""
		t = time.strftime("%d/%m/%Y %H:%M:%S", time.gmtime(time.time()))  
		self.logfile.write(t + " " + message + "\n")
		self.logfile.flush()

	def response(self):
		"""Return a giggle response."""
		# Reseed the generator, else each forked instance ends up with
		# the same state and you get the same giggle all the time.
		whrandom.seed()
		return "Pyggle 0.1\r\n" + whrandom.choice(self.giggles) + "\r\n"

	def __init__(self, gigglefile = "giggles.txt", logfile = "giggle.log",
		host = "localhost", port = 4700):
		"""Construct a GiggleServer. Invoke as 
GiggleServer([gigglefile[, logfile[, host[, port]]]])
Call the main() method to start serving giggle requests.
Specify logfile as None to log to stderr."""
		try:
			self.giggles = map(trimcrlf, open(gigglefile).readlines())
		except IOError:
			raise IOError, "unable to read giggles"
		if logfile:
			try:
				self.logfile = open(logfile, "a")
			except IOError:
				raise IOError, "unable to open log file"
		else:
			self.logfile = sys.stderr
		TCPServer.__init__(self, (host, port), GiggleRequestHandler)

	def main(self):
		"""Start the server."""
		self.log("Starting up")
		self.serve_forever()

if __name__ == "__main__":
	# If this file is invoked rather than imported, start a giggle server
	# on port 4701.
	g = GiggleServer("giggles.txt", "giggled.log", "localhost", 4701)
	g.main()

