#!/usr/bin/ruby

#####################################################################################
# ifetch-tools is a set of tools that can collect images from ip based cameras,
# monitor collection process, and provide an interface to view collected history.
# Copyright (C) 2005-2021 Richard Nelson
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

##############################################################################################
# Set some variables.
##############################################################################################
# Version number
VER = "0.18.5"

##############################################################################################
# Set some options to ensure memory management.
##############################################################################################
GC.enable

##############################################################################################
# Do the require and include stuff we need for our operations.
##############################################################################################
require 'webrick'
require 'drb'
require 'rmagick'
require 'net/http'
require 'net/http/digest_auth'
#require 'logger'
# Unremark the below two lines if you want to try with high-performance.
#require 'rubygems'
#require 'webrick/highperformanceserver'
include WEBrick

##############################################################################################
# Define the way we pull in an image from a camera.
#
# This def will pull the image from the cam now and return the response.
##############################################################################################
def pullimage(address,port,url,uid,pwd,auth_type)
	# Here we determine if we need to handle as basic auth or digest
	if auth_type == 'digest' then
		uri = URI.parse('http://'+address.to_s+':'+port.to_s+''+url.to_s)
		uri.user = uid
		uri.password = pwd
		response = Net::HTTP.start(address, port, open_timeout: 5, read_timeout: 5) do |http|
			req_img = Net::HTTP::Get.new uri.request_uri
			digest_auth = Net::HTTP::DigestAuth.new
			temp_response = http.request(req_img)
			# response is a 401 response with a WWW-Authenticate header
			auth = digest_auth.auth_header uri, temp_response['www-authenticate'], 'GET'
			# create a new request with the Authorization header
			req_img = Net::HTTP::Get.new uri.request_uri
			req_img.add_field 'Authorization', auth
			# re-issue request with Authorization
			http.request(req_img)
		end
	else
		response = Net::HTTP.start(address, port, open_timeout: 5, read_timeout: 5) do |http|
			req_img = Net::HTTP::Get.new(url)
			req_img.basic_auth uid, pwd
			http.request(req_img)
		end
	end
	# Now send response back
	return response.body
end

##############################################################################################
# This def will return an array of every camera status which has a .conf file.
##############################################################################################
def camerastatus(_camera_num)
	# Now test for the status of the cameras lock file.
	my_return= Array. new
	lock_file = File::open("/var/lock/ifetch-tools/"+_camera_num.to_s+".lock", 'w')
	if lock_file.flock(File::LOCK_EX|File::LOCK_NB) == 0 then
		my_return[0] = 0
		my_return[1] = "I, Collection offline!"
		# Really important here to close the file or you will get confused!
		lock_file.close
	else
		log_file_name = "/var/log/ifetch-tools/"+_camera_num.to_s+".txt"
		last_line = `/usr/bin/tail -n 1 #{log_file_name}`.split(/,/)
		my_return[0] = 1
		my_return[1] = last_line
	end
	return my_return
end

##############################################################################################
# Suck in the config settings from the ifetch-tools.conf file.
##############################################################################################
def getlocalvars(_location, _sub_string)
	begin
		if _sub_string == nil then
			eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
		else
			File.readlines('/etc/ifetch-tools/ifetch-tools.conf').each do |line|
				if line.include?('_sub_string') then
					puts line
					eval(line)
				end
			end
		end
	rescue
		puts _location+": Error encountered reading the conf file of: "+$!.to_s
		# Stop after the error feedback.
		exit
	end
end

##############################################################################################
# Class definitions below.
##############################################################################################

##############################################################################################
# CameraHistory generates the left frame of the system and setup some stuff to do the magic
# on the camera history.
##############################################################################################
class CameraHistory < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		# cameraName we are to work with.
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s

		# index is a page reference to where we are in the view.
		raise HTTPServerError, 'Error: index parameter not passed correctly.' if req.query['index'] == nil || /[0-9]+/.match(req.query['index']) == nil
		index = /[0-9]+/.match(req.query['index']).to_s # Crop out all the bad stuff.

		# navigation is to determine whether we populate the viewing pane of first view.
		raise HTTPServerError, 'Error: navigation parameter not passed correctly.' if req.query['navigation'] == nil || /[0-9]+/.match(req.query['navigation']) == nil
		navigation = /[0-1]/.match(req.query['navigation']).to_s # Crop out all the bad stuff.

		# basicView is for the Basic Archive View.
		raise HTTPServerError, 'Error: basicView parameter not passed correctly.' if req.query['basicView'] == nil || /[0-3]/.match(req.query['basicView']) == nil
		basicView = /[0-3]/.match(req.query['basicView']).to_s

		# Initialize some vars.
		myJumpNavigation = "" # Jump navigation string.
		myNavigation = "" # General navigation string.
		myTempRespond = "" # Set a local var to null.

		# 20090201 Nelson - I am now starting this on startup and only do the new object here.
		# Here is the performance boost for exchange of data between the collection daemon and the web interface
		#DRb.start_service(nil,nil,{:load_limit => LOADLIMIT})
		drb_port = "druby://localhost:"+(BASEPORT+cameraName.to_i).to_s
		#obj = DRbObject.new(nil, 'druby://localhost:9000')
		obj = DRbObject.new(nil, drb_port)

		# Define our array to handle the DRb exchange.
		imgSorted = Array.new

		# Now use obj, imgSorted is not as descriptive but remember that we expect the sequence to be pre sorted so in fact name is ok.
		imgSorted = obj.xchange_array
		# 20090201 Nelson - I am now stoping this on trap of close here.
		#DRb.stop_service() # Stop the DRb on the webrick session

		imgSortedLength = imgSorted.length
		totalPages = imgSortedLength / IMAGESPERPAGE # Get the total number of pages.
		#puts imgSortedLength

		# This is just a logic flow here for the first page while the system is populating.
		# Note, that the way we display images will cause drift in respect to amount of images and the time at the interface.
		#if totalPages != 0
		#totalPagesCounter = totalPages - 1
		#end

		# Ok here we are getting a number to reference so we do not have time drift.
		tmpIndex = index.to_i

		# Set a sentry for start over on count back to 0
		# We do not have to worry about backward count since that is addressed in the "< Previous | Next >" navigation on down.
		indexSentry = -1

		# Generate the response of images per page and guard against no images yet.
		if imgSortedLength > 0
			if tmpIndex+IMAGESPERPAGE >= imgSortedLength
				lastImgOnPage = imgSortedLength - 1
				indexSentry = tmpIndex+IMAGESPERPAGE - imgSortedLength - 1
			else
				lastImgOnPage = tmpIndex+IMAGESPERPAGE - 1
			end
		else
			lastImgOnPage = 0
			totalPages = -1
		end

		# This just keeps us from populating the navigation frame on the initial login to history and if no history collected yet.
		if navigation.to_i == 1 && imgSortedLength > 0
		# Ok now actually populate the page with the lastImgOnPage
			# Keep track of image file names when building frame for video export
			imgFileList = Array.new
			# Keep track of image file names for javascript playback
			imgPlaybackList = Array.new
			index.to_i.upto(lastImgOnPage) do |imgIndex|
				#imgTime, imgFile = imgSorted[5].to_s.split(/,/)
				imgTime, imgFile = imgSorted[imgIndex].split(/,/)
				#puts imgFile
				# Ok here we are getting a number to reference so we do not have time drift based on our new navigation.
				#imgTime, imgFile = imgSorted[imgIndex].to_s.split(/,/)
				myTempRespond = myTempRespond+'<BR>'+imgTime+'<BR><A HREF="/camerahistory?cameraName='+cameraName+'&index='+imgIndex.to_s+'&navigation=1&basicView=3"><IMG SRC="/data/'+imgFile+'?timehash='+imgTime+'" width='+HISTORYIMAGEWIDTH+' height='+HISTORYIMAGEHEIGHT+'></A><BR>'
				# Push the image on the array javascript playback
				imgPlaybackList.push("/data/#{imgFile}?timehash=#{imgTime}")
				# Push the image on the array where we are keeping image file names
				if File.exist?("/var/lib/ifetch-tools/#{imgFile}") && File.size("/var/lib/ifetch-tools/#{imgFile}")>0 then
					imgFileList.push("/var/lib/ifetch-tools/#{imgFile}")
				end
			end
			# 20081007 Nelson - removing this block to leave last page possibly short.
			### Now finish starting back at 0 on the page that ran over the end of the array
			if imgSortedLength >= IMAGESPERPAGE
				0.to_i.upto(indexSentry) do |imgIndex|
					#imgTime, imgFile = imgSorted[5].to_s.split(/,/)
					imgTime, imgFile = imgSorted[imgIndex].split(/,/)
					#puts imgFile
					# Ok here we are getting a number to reference so we do not have time drift based on our new navigation.
					#imgTime, imgFile = imgSorted[imgIndex].to_s.split(/,/)
					myTempRespond = myTempRespond+'<BR>'+imgTime+'<BR><A HREF="/camerahistory?cameraName='+cameraName+'&index='+imgIndex.to_s+'&navigation=1&basicView=3"><IMG SRC="/data/'+imgFile+'?timehash='+imgTime+'" width='+HISTORYIMAGEWIDTH+' height='+HISTORYIMAGEHEIGHT+'></A><BR>'
					# Push the image on the array javascript playback
					imgPlaybackList.push("/data/#{imgFile}?timehash=#{imgTime}")
					# Push the image on the array where we are keeping image file names
					if File.exist?("/var/lib/ifetch-tools/#{imgFile}") && File.size("/var/lib/ifetch-tools/#{imgFile}")>0 then
						imgFileList.push("/var/lib/ifetch-tools/#{imgFile}")
					end
				end
			end
		end

		# Ok here we do the Jump List as an array for just a bit.
		#myJumpNavigation = myJumpNavigation+'<OPTION STYLE="color : #ff6666" selected > *-- Navigation Jump --* '+"\n"
		myJumpHash = Hash.new
		myJumpHash["999999999999"] = '<OPTION STYLE="color : #ff6666" selected > *- Navigation Jump -*'+"</OPTION>\n"

		# Build the navi
		0.upto(totalPages) do |count|
			pageIndex = count*IMAGESPERPAGE
			# Bug 11618 squash - The below is since we index with 0 and not 1 we have to watch for even page count division in to our total images and adjust if need be.
			if pageIndex >= lastImgOnPage
				pageIndex = pageIndex-1
			end

			# Split out our info from the array element.
			imgTime, imgFile = imgSorted[pageIndex].split(/,/)
			#puts imgFile

			# tmpIndex will hold the index position of the page we are on.
			tmpIndex = File.basename(imgFile, ".jpg").split(/_/)[1].to_i

			# This is the elements in the jumping list. This could be sorted but for now I am leaving since I sort of like how if shows the scroll of image count in an abstract way.
			myJumpHash[imgTime] = '<OPTION value="/camerahistory?cameraName='+cameraName+'&index='+tmpIndex.to_s+'&navigation=1&basicView=3">'+imgTime+"</OPTION>\n"
		end

		# Now that we have built it lets prep it to display it like we want (sorted in this case).
		#myJumpNavigation = myJumpHash.sort.each {|key, value| puts "The key value is #{key} and the hash is #{value}" }
		myJumpHash.sort.reverse.each {|key, value| myJumpNavigation = "#{myJumpNavigation}#{value}" }
		#puts myJumpNavigation

		# Ok here is where we handle the < Previous | Next >" navigation.
		# Here is the code for a sinle page of images only or if this is the first time the user hits the hitsory.
		if index.to_i-IMAGESPERPAGE < 0 && index.to_i+IMAGESPERPAGE > imgSortedLength-1 || navigation.to_i == 0
			myNavigation = %{<FORM NAME="myform">
				<SELECT name="mylist" onChange="disp_text();">
				#{myJumpNavigation}
				</SELECT></FORM>Please select a time.<BR>
			}
		else
			# If we are here then there is more than one page of images.
			# Here is the move back page code
			if index.to_i-IMAGESPERPAGE >= 0
				myNavigation = %{<FORM NAME="myform">
					<SELECT name="mylist" onChange="disp_text();">
					#{myJumpNavigation}
					</SELECT></FORM>
					<div id="jump">< <A HREF="/camerahistory?cameraName=#{cameraName}&index=#{index.to_i-IMAGESPERPAGE}&navigation=1&basicView=3">Previous</A>
				}
			else
				myNavigation = %{<FORM NAME="myform">
					<SELECT name="mylist" onChange="disp_text();">
					#{myJumpNavigation}
					</SELECT></FORM>
					<div id="jump">< <A HREF="/camerahistory?cameraName=#{cameraName}&index=#{index.to_i-IMAGESPERPAGE+imgSortedLength-1}&navigation=1&basicView=3">Previous</A>
				}
			end
			# Here is the move forward between page code
			if index.to_i+IMAGESPERPAGE <= imgSortedLength-1
				myNavigation = %{#{myNavigation} | <A HREF="/camerahistory?cameraName=#{cameraName}&index=#{index.to_i+IMAGESPERPAGE}&navigation=1&basicView=3">Next</A> ></div>
				}
			else
				myNavigation = %{#{myNavigation} | <A HREF="/camerahistory?cameraName=#{cameraName}&index=#{indexSentry}&navigation=1&basicView=3">Next</A> ></div>
				}
			end
		end

		if basicView.to_i == 1
			# If we are here we are asking to build a page with images only.
			myTempRespond.gsub!(/(<img\b[^>]*)width=\d+ height=\d+/i, '\\1')
			myTempRespond.gsub!(/(<a) href="[^"]+"/i, '\\1')
			# FIXME - This one is ugly and depends on the output from the above. But it does appear to work.
			myTempRespond.gsub!(/a><br>/i,"A><p style=\"page-break-before: always\">")
			# Now generate the page with the correct substitution.
			res.body = eval(File.open("/usr/share/ifetch-tools/templates/CameraHistoryBasic") {|fh| fh.read})
		elsif basicView.to_i == 2
			# If we are here we need to call rmagick with to make a vidoe of the frame of images starting with the selected image.
			# Setup to make video from images
			tempResponse = Magick::ImageList.new(*imgFileList) do
				self.delay = $video_export_delay
			end
			# Write out temp video to a random directory in /tmp, set mime_type for default launch,
			# then destroy said temp area upon return for security.
			Dir.mktmpdir(nil, "/tmp") {|dir|
				# use the directory...
				tempResponse.write("#{dir}/tmpVideo."+$video_export_type)
				mtype = WEBrick::HTTPUtils::mime_type("#{dir}/tmpVideo."+$video_export_type, WEBrick::HTTPUtils::DefaultMimeTypes)
				res['Content-Type'] = mtype
				res['Content-Disposition'] = "inline; filename=\"video."+$video_export_type
				puts mtype
				res.body = File.open("#{dir}/tmpVideo."+$video_export_type,"rb") {|io| io.read}
			}
		# elsif basicView.to_i == 4
		#	# If we are here then we need to setup for javascript playback
		#	# Now generate the page with the correct substitution.
		#	# Unify history playback here while dropping ShowImage method
		#	# tmpIndex will hold the index position of the page we are on.
		#	res.body = eval(File.open("/usr/share/ifetch-tools/templates/CameraHistoryPlayback") {|fh| fh.read})
		else
			GC.start
			# Now generate the page with the correct substitution.
			res.body = eval(File.open("/usr/share/ifetch-tools/templates/CameraHistory") {|fh| fh.read})
		end
	end
end

##############################################################################################
# Start and Stop the collection process for a given camera.
##############################################################################################
class CameraStartStop < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera Monitor Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == MONITORUSER && pass == MONITORPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s
		# Now test for the status of the cameras lock file. If running we stop and if not we start.
		myTempRespond = ""
		lock_file = File::open("/var/lock/ifetch-tools/"+cameraName.to_s+".lock", 'w')
		if lock_file.flock(File::LOCK_EX|File::LOCK_NB) == 0 then
			# Camera not running action here
			# Really important here to close the file or you will get confused!
			lock_file.close
			system_call = "/usr/bin/ifetch /etc/ifetch-tools/cameras/"+cameraName.to_s+".conf&"
			pid = fork {
				Process.setsid
				(0...2).each do |i|
					begin
						#closing stdin, stdout and stderr 0,1,2
						IO.for_fd(i).close
					rescue Errno::EBADF
					end
				end
				pid2 = fork { exec(system_call) }
				Process.detach(pid2)
				exit!
			}
			Process.detach(pid)
			myTempRespond = "The servlet is attempting to start camera #{cameraName.to_s}"
		else
			# Camera running action here
			# Set the camera_pid to hold the pid of the running camera process.
			camera_pid = File.read("/var/run/ifetch-tools/"+cameraName.to_s+".pid").chomp
			system_call = "kill -s 9 #{camera_pid} &"
			Process.detach( fork { system(system_call) } )
			myTempRespond = "The servlet is attempting to stop camera #{cameraName.to_s} "
		end
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/CameraStartStop") {|fh| fh.read})
	end
end

##############################################################################################
# CArchive is dynamic to create an archive view of each camera that is listed from the
# conf dir at the time of the servlet creation. The tool will use the listing of files with
# the .conf extension.
##############################################################################################
class CArchive < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		myTempRespond = ""
		if req.query['cameraList'] == nil
			cameraList = Dir["/etc/ifetch-tools/cameras/*.conf"]
		else
			raise HTTPServerError, 'Error: cameraList parameter not passed correctly.' if /[0-9_]+/.match(req.query['cameraList']) == nil
			cameraList = /[0-9_]+/.match(req.query['cameraList']).to_s.split("_")
			#cameraList.reject { |item| item.nil? || item == '' }
			cameraList.map! do |cam_tmp|
				cam_tmp = '/etc/ifetch-tools/cameras/'+cam_tmp+'.conf'
				#myTempRespond = myTempRespond+"<br> "+cam_tmp+" -- "+cam_tmp.class.to_s
			end
		end
		###############################################################
		# Setup the image_ vars we are going to need
		image_alt = "no_image_alt"
		cameraList.each do |cam_tmp|
			#cam_tmp = '/etc/ifetch-tools/cameras/'+cam_tmp+'.conf'
			if File.exist?(cam_tmp) then
				# Camera collection being attempted
				###############################################################
				# First source in the locals from ifetch-tools.conf file
				getlocalvars('CArchive Class', 'image_')
				# Now let the camera.conf file overload the settings they want.
				begin
					eval(File.open(cam_tmp) {|fh| fh.read})
					rescue
						res.body = eval(puts "Encountered reading the camera.conf file of: "+$!.to_s)
						# Stop after the error feedback.
					break
				end
				# Add the inline image and link to the page.
				# If cam offline drop archive link
				cam_status = camerastatus(File.basename(cam_tmp, ".conf"))
				if cam_status[0] == 0 then
					myTempRespond = myTempRespond+'<img src="/snapshot?cameraName='+File.basename(cam_tmp, ".*")+'" class="camImage" id="'+File.basename(cam_tmp, ".conf")+'" alt="'+image_alt+'" width="'+HISTORYIMAGEWIDTH+'" height="'+HISTORYIMAGEHEIGHT+'" >'
				else
					myTempRespond = myTempRespond+'<a href="/camerahistory?cameraName='+File.basename(cam_tmp, ".conf").to_s+'&index=0&navigation=0&basicView=0" title="'+image_alt+'"><img src="/snapshot?cameraName='+File.basename(cam_tmp, ".*")+'" class="camImage" id="'+File.basename(cam_tmp, ".conf")+'" alt="'+image_alt+'" width="'+HISTORYIMAGEWIDTH+'" height="'+HISTORYIMAGEHEIGHT+'" ></a>'
				end
			else
				myTempRespond = myTempRespond+'*** Unable to locate '+cam_tmp+' file.! ***'
			end
		end
		GC.start
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/Archive") {|fh| fh.read})
	end
end

##############################################################################################
# CFile returns newest image for a given camera uploading images to folder.
##############################################################################################
class CFile < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "image/jpeg" # Tell the browser how we are talking
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s
		#res.body = IO.read(Dir['/var/lib/ifetch-tools/htdocs/'+cameraName+'/*.jpg'].sort_by(&File.method(:ctime)).last.to_s)
		imgArray = Dir['/var/lib/ifetch-tools/rtsp/'+cameraName+'/*.jpg'].sort_by(&File.method(:ctime))
		res.body = IO.read(imgArray[imgArray.length - 1].to_s)
	end
end

##############################################################################################
# CLive is dynamic to create a live view of each camera that is listed from the
# conf dir at the time of the servlet creation. The tool will use the listing of files with
# the .conf extension.
##############################################################################################
class CLive < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		myTempRespond = ""
		if req.query['cameraList'] == nil
			cameraList = Dir["/etc/ifetch-tools/cameras/*.conf"]
		else
			raise HTTPServerError, 'Error: cameraList parameter not passed correctly.' if /[0-9_]+/.match(req.query['cameraList']) == nil
			cameraList = /[0-9_]+/.match(req.query['cameraList']).to_s.split("_")
			#cameraList.reject { |item| item.nil? || item == '' }
			cameraList.map! do |cam_tmp|
				cam_tmp = '/etc/ifetch-tools/cameras/'+cam_tmp+'.conf'
				#myTempRespond = myTempRespond+"<br> "+cam_tmp+" -- "+cam_tmp.class.to_s
			end
			#myTempRespond = myTempRespond+"<br> "+cameraList.length.to_s
			#tmpCameraList.split("_").sort.each do |camera_num|
			#	camera_num = '/etc/ifetch-tools/cameras/'+camera_num+'.conf'
			#	#myTempRespond = myTempRespond+"<br> "+camera_num
			#end
		end
		#cameraList = Dir["/etc/ifetch-tools/cameras/*.conf"]
		#cameraList.sort.each do |cam_tmp|
		#	cam_tmp = '/etc/ifetch-tools/cameras/'+cam_tmp+'.conf'
		#	myTempRespond = myTempRespond+"<br> "+cam_tmp+" -- "+cam_tmp.class.to_s
		#	#myTempRespond = myTempRespond+"<br> * "+cam_tmp
		#end
		###############################################################
		# Setup the image_ vars we are going to need
		image_alt = "no_image_alt"
		#myTempRespond = myTempRespond+"<br> Getting ready to spin the cameras!"
		cameraList.each do |cam_tmp|
			#cam_tmp = '/etc/ifetch-tools/cameras/'+cam_tmp+'.conf'
			if File.exist?(cam_tmp) then
				# Camera collection being attempted
				###############################################################
				# First source in the locals from ifetch-tools.conf file
				getlocalvars('CLive Class', 'image_')
				# Now let the camera.conf file overload the settings they want.
				begin
					eval(File.open(cam_tmp) {|fh| fh.read})
					rescue
						res.body = eval(puts "Encountered reading the camera.conf file of: "+$!.to_s)
						# Stop after the error feedback.
					break
				end
				# Add the inline image and link to the page.
				myTempRespond = myTempRespond+'<a href="/livesingle?cameraName='+File.basename(cam_tmp, ".conf")+'" title="'+image_alt+'"><img src="/snapshot?cameraName='+File.basename(cam_tmp, ".*")+'" class="camImage" id="'+File.basename(cam_tmp, ".conf")+'" alt="'+image_alt+'" width="'+HISTORYIMAGEWIDTH+'" height="'+HISTORYIMAGEHEIGHT+'" ></a>'
			else
				myTempRespond = myTempRespond+'*** Unable to locate '+cam_tmp+' file.! ***'
			end
		end
		GC.start
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/Live") {|fh| fh.read})
	end
end

##############################################################################################
# CLiveSingle creates a live view of a single camera.
##############################################################################################
class CLiveSingle < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s
		myTempRespond = ""
		###############################################################
		# Setup the image_ vars we are going to need
		image_alt = "no_image_alt"
		image_sleep = "1"
		# First check camera status
		###############################################################
		# First source in the locals from ifetch-tools.conf file
		getlocalvars('CLiveSingle Class', 'image_')
		# Now let the camera.conf file overload the settings they want.
		begin
			eval(File.open("/etc/ifetch-tools/cameras/"+cameraName+".conf") {|fh| fh.read})
		rescue
			res.body = eval(puts "Encountered reading the camera.conf file of: "+$!.to_s)
			# Stop after the error feedback.
			#break
		end
		# Add the inline image and link to the page.
		# Put the camera image in the template return
		myTempRespond = '<img height="80%" src="/snapshot?cameraName='+cameraName+'" id="myImage" alt="'+image_alt+'"></a>'
		GC.start
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/LiveSingle") {|fh| fh.read})
	end
end

##############################################################################################
# Monitor will be used as the basis to check the status of each camera that is listed from the
# conf dir at the time of the servlet creation. The tool will use the listing of files with
# the .conf extension.
##############################################################################################
class CMonitor < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera Monitor Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == MONITORUSER && pass == MONITORPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		myTempRespond = ""

		# 20061207 not as clean as I would like it but this seems to do the trick
		camera_array = Array.new
		cam_count = 0
		Dir["/etc/ifetch-tools/cameras/*.conf"].each do |cam_tmp|
			camera_array[cam_count] = File.basename(cam_tmp, ".conf").to_i
			cam_count = cam_count+1
		end
		camera_array.sort.each do |camera_num|
			# Now test for the status of the cameras lock file.
			# Table col 1 camera number, 2 - pid / mem, 3 - actions, 4 - status, 5 - log file, 6 - information
			cam_status = camerastatus(camera_num)
			if cam_status[0] == 0 then
				myTempRespond =	 myTempRespond+'<TR><TD><CENTER>'+camera_num.to_s+'</CENTER></TD>
				<TD><CENTER> - / - </CENTER></TD>
				<TD><A HREF="/camerastartstop?cameraName='+camera_num.to_s+'"><CENTER><IMG SRC="/start.jpg"></CENTER></A></TD>
				<TD><CENTER><IMG SRC="/grey.jpg"></CENTER></TD>
				<TD><A HREF="/log/'+camera_num.to_s+'.txt" target="_blank"><CENTER><IMG SRC="/log.jpg"></CENTER></A></TD>
				<TD>Camera ifetch is not running. </TD></TR>'
				# Really important here to close the file or you will get confused!
			else
				# Set the camera_pid to hold the pid of the running camera process.
				camera_pid = File.read("/var/run/ifetch-tools/"+camera_num.to_s+".pid").chomp
				# Put the camera pid and camera number in the table
				myTempRespond = myTempRespond+'<TD><CENTER><A HREF="/camerahistory?cameraName='+camera_num.to_s+'&index=0&navigation=0&basicView=0"> '+camera_num.to_s+'</A></CENTER></TD>
				<TD><CENTER>'+camera_pid+' - '+%x[pmap -x #{camera_pid} | tail -1][10,40].strip+'</CENTER></TD>
				<TD><CENTER><A HREF="/camerastartstop?cameraName='+camera_num.to_s+'"><IMG SRC="/stop.jpg"></A></CENTER></TD>'

				if cam_status[1][0] == "I" then
					myTempRespond = myTempRespond+'<TD><CENTER><IMG SRC="/green.jpg"></CENTER></TD><TD><A HREF="/log/'+camera_num.to_s+'.txt" target="_blank"><CENTER><IMG SRC="/log.jpg"></A></CENTER></TD><TD> '+cam_status[1][1]+" </TD></TR>"
				else
					myTempRespond = myTempRespond+'<TD><CENTER><IMG SRC="/red.jpg"></CENTER></TD><TD><A HREF="/log/'+camera_num.to_s+'.txt" target="_blank"><CENTER><IMG SRC="/log.jpg"></A><CENTER></TD><TD> '+cam_status[1][1]+" </TD></TR>"
				end
			end
		end
		GC.start
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/Monitor") {|fh| fh.read})
	end
end

##############################################################################################
# CSnapshot pulls a live snapshot of a given camera.
##############################################################################################
class CSnapshot < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "image/jpeg" # Tell the browser how we are talking
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s
		cam_status = camerastatus(cameraName)
		if cam_status[0] == 0 then
			res.body = IO.read('/usr/share/ifetch-tools/htdocs/ccoffline.jpg')
		else
			###############################################################
			# Setup the image_ vars we are going to need
			image_addr = nil
			image_addr_port = nil
			image_auth_type='basic'
			image_uid = "no_image_uid"
			image_url = "no_image_url"
			image_pwd = "no_image_pwd"
			###############################################################
			# First source in the locals from ifetch-tools.conf file
			getlocalvars('CSnapshot Class', 'image_')
			# Now let the camera.conf file overload the settings they want.
			begin
				eval(File.open("/etc/ifetch-tools/cameras/"+cameraName+".conf") {|fh| fh.read})
			rescue
				res.body = eval(puts "Encountered reading the camera.conf file of: "+$!.to_s)
				# Stop after the error feedback.
				#break
			end
			# Add the inline image and link to the page.
			# Put the camera image in the template return
			if cam_status[1][0] == "I" then
				# return the current camera snapshot image.
				res.body = pullimage(image_addr,image_addr_port,image_url,image_uid,image_pwd,image_auth_type)
			else
				res.body = IO.read('/usr/share/ifetch-tools/htdocs/loading.gif')
			end
		end
	end
end


##############################################################################################
# Display an image and a time stamp. of the system.
##############################################################################################
class ShowImage < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera History Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == HISTORYUSER && pass == HISTORYPASS
		}
		res['Content-Type'] = "text/html" # Tell the browser how we are talking
		raise HTTPServerError, 'Error: cameraName parameter not passed correctly.' if req.query['cameraName'] == nil || /[0-9]+/.match(req.query['cameraName']) == nil
		cameraName = /[0-9]+/.match(req.query['cameraName']).to_s
		imgFile = /([\/A-Za-z0-9_]+)+(.jpg|.png)/.match(req.query['imageName'])[0] # Crop out all the bad stuff.

		if imgFile == "images/ifetch.png" || imgFile == "images/missed.jpg"
			tmpIndex = 0
			theDate = "Welcome to camera number #{cameraName} history."
			pageToBuild = %{<IMG SRC=/data/#{imgFile}>}
			pageToBuildBasic = %{/data/#{imgFile}}
			pageToBuildVideo = %{/data/#{imgFile}}
		elsif imgFile != nil
			# tmpIndex will hold the index position of the page we are on.
			tmpIndex = File.basename(imgFile, ".jpg").split(/_/)[1].to_i
			theDate = File.mtime("/var/lib/ifetch-tools/#{imgFile}")
			pageToBuild = %{<A HREF="/camerahistory?cameraName=#{cameraName}&index=#{tmpIndex}&navigation=1&basicView=0" target="shownav"><IMG SRC="/data/#{imgFile}?timehash=#{theDate}"></A>}
			pageToBuildBasic = %{/camerahistory?cameraName=#{cameraName}&index=#{tmpIndex}&navigation=1&basicView=1}
			pageToBuildVideo = %{/camerahistory?cameraName=#{cameraName}&index=#{tmpIndex}&navigation=1&basicView=2}
		else
			pageToBuild = %{Error in image passing to ShowImage could be a security issue.}
		end
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/ShowImage") {|fh| fh.read})
	end
end

##############################################################################################
# Attempt to start all cameras.
##############################################################################################
class StartAllCameras < HTTPServlet::AbstractServlet
	def do_GET(req, res)
		HTTPAuth.basic_auth(req, res, 'Camera Monitor Realm') {|user, pass|
			# this block returns true if
			# authentication token is valid
			user == MONITORUSER && pass == MONITORPASS
		}
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/StartAllCameras") {|fh| fh.read})
		start_all_cameras
	end
end

##############################################################################################
# The below def should attempt to start every camera conf it can find.
##############################################################################################
def start_all_cameras
	Dir["/etc/ifetch-tools/cameras/*.conf"].each do |cam_tmp|
		cameraName = File.basename(cam_tmp, ".conf").to_i
		pid = fork {
			Process.setsid
			(0...2).each do |i|
				begin
					#closing stdin, stdout and stderr 0,1,2
					IO.for_fd(i).close
				rescue Errno::EBADF
				end
			end
			system_call = "/usr/bin/ifetch /etc/ifetch-tools/cameras/"+cameraName.to_s+".conf&"
			pid2 = fork { exec(system_call) }
			Process.detach(pid2)
			exit!
		}
		Process.detach(pid)
	end
end

##############################################################################################
# The below def should attempt to stop every camera pid it can find upon a shutdown request.
##############################################################################################
def stop_all_cameras
	Dir["/var/lock/ifetch-tools/*.pid"].each do |cam_tmp|
		cameraName = File.basename(cam_tmp, ".pid").to_i
		# Camera running action here
		# Set the camera_pid to hold the pid of the running camera process.
		camera_pid = File.read("/var/run/ifetch-tools/"+cameraName.to_s+".pid").chomp
		system_call = "kill -s 9 #{camera_pid} &"
		Process.detach( fork { system(system_call) } )
		puts "Shutdown signal received, attempting stop on"+cameraName.to_s+".pid"
	end
end

##############################################################################################
# The below should daemonize this code.
# 20090124 Nelson - Remarking out since I want init.d to catch the pid correct.
##############################################################################################
#exit if fork			# Parent exits, child continues.
#Process.setsid			# Become session leader.
#exit if fork			# Zap session leader. See [1].
#Dir.chdir "/"			# Release old working directory.
##File.umask 0000			# Ensure sensible umask. Adjust as needed.
#STDIN.reopen "/dev/null"	# Free file descriptors and
#STDOUT.reopen "/dev/null", "a"	# point them somewhere sensible.
#STDERR.reopen STDOUT		# STDOUT/ERR should better go to a logfile.

##############################################################################################
# Setup local vars
##############################################################################################
getlocalvars("Main Block", nil)

##############################################################################################
# Enable the logging operations we want below.
##############################################################################################
# Set up the log information
server_log = WEBrick::Log::new("/var/log/ifetch-tools/wwwifetch-server.txt", WEBrick::Log::DEBUG)
access_log = WEBrick::BasicLog::new("/var/log/ifetch-tools/wwwifetch-access.txt")
referer_log = WEBrick::BasicLog::new("/var/log/ifetch-tools/wwwifetch-referer.txt")
agent_log = WEBrick::BasicLog::new("/var/log/ifetch-tools/wwwifetch-agent.txt")
custom_log = WEBrick::BasicLog::new("/var/log/ifetch-tools/wwwifetch-custom.txt")

##############################################################################################
# Setup for layered approach for authentication.
##############################################################################################
authenticate_history = Proc.new do |req, res|
	HTTPAuth.basic_auth(req, res, 'History Level Authentication Required') do |user, pass|
		#user == 'foo' && password == 'bar'
		user == HISTORYUSER && pass == HISTORYPASS
	end
end

##############################################################################################
# Start the DRb service for use later.
##############################################################################################
DRb.start_service(nil,nil,{:load_limit => LOADLIMIT})


##############################################################################################
# Create the instance of the web server.
##############################################################################################
s = WEBrick::HTTPServer.new(
	:Port => 2000,
	#:DocumentRoot => "/usr/share/ifetch-tools/htdocs",
	:DocumentRoot => "/dev/null",
	:Logger         => server_log,
	:AccessLog      => [
	[ access_log, WEBrick::AccessLog::COMMON_LOG_FORMAT  ],
	[ referer_log,   WEBrick::AccessLog::REFERER_LOG_FORMAT ],
	[ agent_log,     WEBrick::AccessLog::AGENT_LOG_FORMAT   ],
	[ custom_log, "%a %U %T" ]  # peeraddr, Request-URI, process time
	]
	)

##############################################################################################
# Create some mount points for servlet navigation
##############################################################################################
# Monitor Operations
s.mount("/archive", CArchive)
s.mount("/snapshot", CSnapshot)
s.mount("/cfile", CFile)
s.mount("/live", CLive)
s.mount("/livesingle", CLiveSingle)
s.mount("/monitor", CMonitor)
s.mount("/camerastartstop", CameraStartStop)
s.mount("/startallcameras", StartAllCameras)

# History Operations
s.mount("/camerahistory", CameraHistory)
s.mount("/showimage", ShowImage)
# Below creates symlink to default area if no custom /var/lib/ifetch-tools/htdocs folder or symlink is found.
if File.exist?("/var/lib/ifetch-tools/htdocs") then
	puts "Symlink or folder exists for /var/lib/ifetch-tools/htdocs."
else
	puts "No symlink or folder exists for /var/lib/ifetch-tools/htdocs."
	puts "Attempting to create symlink to /usr/share/ifetch-tools/htdocs."
	File.symlink("/usr/share/ifetch-tools/htdocs", "/var/lib/ifetch-tools/htdocs")
end

s.mount('/', HTTPServlet::FileHandler, "/var/lib/ifetch-tools/htdocs/",
	:FancyIndexing => true,
	:HandlerCallback => authenticate_history # Hook up the authentication proc.
)

# The below is for .deb operations and good locations.
s.mount('/log/', WEBrick::HTTPServlet::FileHandler, '/var/log/ifetch-tools/')
s.mount('/data/', WEBrick::HTTPServlet::FileHandler, '/var/lib/ifetch-tools/')
# Added the below in an attempt to refer default images.
s.mount('/data/images/', WEBrick::HTTPServlet::FileHandler, '/usr/share/ifetch-tools/htdocs/')

# Catch the INT sig to shutdown
trap("INT"){
	puts "Shutdown signal received, stop all cameras."
	stop_all_cameras # Stop all running camera collection processes
	DRb.stop_service() # Stop the DRb on the webrick session
	s.shutdown
}

##############################################################################################
# Start the cameras
##############################################################################################
start_all_cameras

##############################################################################################
# Launch webrick
##############################################################################################
s.start

