#!/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-2017 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.15.26d"

##############################################################################################
# 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)
	Timeout.timeout(10) do
		# 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
			Net::HTTP.start(address, port) do |http|
				req_img = Net::HTTP::Get.new uri.request_uri
				digest_auth = Net::HTTP::DigestAuth.new
				response = http.request(req_img)
				# response is a 401 response with a WWW-Authenticate header
				auth = digest_auth.auth_header uri, 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
				response = http.request req_img
				# Now send response back
				return response.body
			end
		else
			Net::HTTP.start(address, port) do |http|
				req_img = Net::HTTP::Get.new(url)
				#req_img.basic_auth 'username', 'password'
				req_img.basic_auth uid, pwd
				response = http.request(req_img)
				#puts "Code = #{response.code}"
				#puts "Message = #{response.message}"
				#response.each {|key, val| printf "%14s = %40.40s\n", key, val }
				## Nelson working		f.write(response.body)
				#Magick::Image.from_blob(response.body)[0];
				return response.body
			end
		end
	end
end

##############################################################################################
# Suck in the config settings from the ifetch-tools.conf file.
##############################################################################################
begin
	eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
rescue
	puts "Error encountered reading the conf file of: "+$!.to_s
	# Stop after the error feedback.
	exit
end


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

##############################################################################################
# Camera generates two frames and calls CameraHistory on the left frame of the system.
##############################################################################################
class Camera < 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
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/Camera") {|fh| fh.read})
	end
end

##############################################################################################
# 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" target="showframe"><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" target="showframe"><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=0">'+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>
					<A HREF="/camerahistory?cameraName=#{cameraName}&index=#{index.to_i-IMAGESPERPAGE}&navigation=1&basicView=0"> < Previous </A>
				}
			else
				myNavigation = %{<FORM NAME="myform">
					<SELECT name="mylist" onChange="disp_text();">
					#{myJumpNavigation}
					</SELECT></FORM>
					<A HREF="/camerahistory?cameraName=#{cameraName}&index=#{index.to_i-IMAGESPERPAGE+imgSortedLength-1}&navigation=1&basicView=0"> < 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=0"> Next > </A><BR>
				}
			else
				myNavigation = %{#{myNavigation}| <A HREF="/camerahistory?cameraName=#{cameraName}&index=#{indexSentry}&navigation=1&basicView=0"> Next > </A><BR>
				}
			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 == 3
			# 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 = ""
		image_addr = "127.0.0.1"
		image_addr_port = "80"
		image_alt = "no_image_alt"
		image_uid = "no_image_uid"
		image_url = "no_image_url"
		image_pwd = "no_image_pwd"
		# 20160408 Rework the live and archive dynamic view to utilize the global ifetch-tools.conf file.
		Dir["/etc/ifetch-tools/cameras/*.conf"].sort.each do |cam_tmp|
			###############################################################
			# First suck in the config settings from the ifetch-tools.conf file.
			begin
				eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
			rescue
				puts "Error encountered reading the conf file of: "+$!.to_s
				# Stop after the error feedback.
				break
			end
			###############################################################
			# 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="/camera?cameraName='+image_addr.split('.').last.to_s+'" title="'+image_alt+'"><img src="/snapshot?cameraName='+File.basename(cam_tmp, ".*")+'" alt="'+image_alt+'" width="'+HISTORYIMAGEWIDTH+'" heigth="'+HISTORYIMAGEHEIGHT+'" ></a>'
		end
		GC.start
		res.body = eval(File.open("/usr/share/ifetch-tools/templates/Archive") {|fh| fh.read})
	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 = ""
		image_addr = "127.0.0.1"
		image_addr_port = "80"
		image_alt = "no_image_alt"
		image_uid = "no_image_uid"
		image_url = "no_image_url"
		image_pwd = "no_image_pwd"
		# 20160408 Rework the live and archive dynamic view to utilize the global ifetch-tools.conf file.
		Dir["/etc/ifetch-tools/cameras/*.conf"].sort.each do |cam_tmp|
			###############################################################
			# First suck in the config settings from the ifetch-tools.conf file.
			begin
				eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
			rescue
				puts "Error encountered reading the conf file of: "+$!.to_s
				# Stop after the error feedback.
				break
			end
			###############################################################
			# 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, ".*")+'" title="'+image_alt+'"><img src="/snapshot?cameraName='+File.basename(cam_tmp, ".*")+'" alt="'+image_alt+'" width="'+HISTORYIMAGEWIDTH+'" heigth="'+HISTORYIMAGEHEIGHT+'" ></a>'
		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 = ""
		image_addr = "127.0.0.1"
		image_addr_port = "80"
		image_alt = "no_image_alt"
		image_sleep = "1"
		image_uid = "no_image_uid"
		image_url = "no_image_url"
		image_pwd = "no_image_pwd"
		# 20160408 Rework the live and archive dynamic view to utilize the global ifetch-tools.conf file.
		###############################################################
		# First suck in the config settings from the ifetch-tools.conf file.
		begin
			eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
		rescue
			puts "Error encountered reading the conf file of: "+$!.to_s
			# Stop after the error feedback.
			#break
		end
		###############################################################
		# 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
		# Put the camera image in the template return
		myTempRespond = '<img 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.
			lock_file = File::open("/var/lock/ifetch-tools/"+camera_num.to_s+".lock", 'w')
			# Table col 1 camera number, 2 - pid / mem, 3 - actions, 4 - status, 5 - log file, 6 - mdpp, 7 - information
			if lock_file.flock(File::LOCK_EX|File::LOCK_NB) == 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><A HREF="/log/'+camera_num.to_s+'.mdpp.txt" target="_blank"><CENTER><IMG SRC="/mdpplog.jpg"></A></CENTER></TD>
				<TD>Camera ifetch is not running. </TD></TR>'
				# Really important here to close the file or you will get confused!
				lock_file.close
			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="/camera?cameraName='+camera_num.to_s+'"> '+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>'
			# Set the logFileName
			logFileName = "/var/log/ifetch-tools/"+camera_num.to_s+".txt"
			# Remarked out the backtick with tail for a pure ruby try. Not for sure about performance but want to try.
			# logFileLastLine = `/usr/bin/tail -n 1 #{logFileName}`.split(/,/)
				# Put the notes of what the log file says in monitor.
				tempForLastLine=[]
				File.open(logFileName, "r") { |f|
					while f.gets
						tempForLastLine.push $_
						tempForLastLine.shift if $. > 1
					end
				}

				#tempForLastLine.each{|e| print e}
				logFileLastLine = tempForLastLine[0].split(/,/)

				# Below is for diagnostics
				#myTempRespond = myTempRespond+logFileLastLine[0]+logFileName
				if logFileLastLine[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><A HREF="/log/'+camera_num.to_s+'.mdpp.txt" target="_blank"><CENTER><IMG SRC="/mdpplog.jpg"></A></CENTER></TD><TD> '+logFileLastLine[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><A HREF="/log/'+camera_num.to_s+'.mdpp.txt" target="_blank"><CENTER><IMG SRC="/mdpplog.jpg"></A></CENTER></TD><TD> '+logFileLastLine[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
		myTempRespond = ""
		image_addr = "127.0.0.1"
		image_addr_port = "80"
		image_alt = "no_image_alt"
		image_auth_type="basic"
		image_uid = "no_image_uid"
		image_url = "no_image_url"
		image_pwd = "no_image_pwd"
		# 20160408 Rework the live and archive dynamic view to utilize the global ifetch-tools.conf file.
		###############################################################
		# First suck in the config settings from the ifetch-tools.conf file.
		begin
			eval(File.open("/etc/ifetch-tools/ifetch-tools.conf") {|fh| fh.read})
		rescue
			puts "Error encountered reading the conf file of: "+$!.to_s
			# Stop after the error feedback.
			#break
		end
		###############################################################
		# 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
		# return the current camera snapshot image.
		res.body = pullimage(image_addr,image_addr_port,image_url,image_uid,image_pwd,image_auth_type)
	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.

##############################################################################################
# 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("/live", CLive)
s.mount("/livesingle", CLiveSingle)
s.mount("/monitor", CMonitor)
s.mount("/camerastartstop", CameraStartStop)
s.mount("/startallcameras", StartAllCameras)

# History Operations
s.mount("/camera", Camera)
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

