# Copyright (C) 2011 Nicolo' Barbon
#
# This file is part of Calise.
#
# Calise 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 3 of the License, or
# any later version.
#
# Calise 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 Calise. If not, see <http://www.gnu.org/licenses/>.
import os
import time
import threading
import logging
from wsgiref.simple_server import make_server
from cgi import parse_qs, escape
import socket
from calise import objects
from calise.infos import __LowerName__
# Execute func and log out as DEBUG information the return code
def debug_exec(func, logger):
r = func()
logger.debug("Function '%s' returned %d" % (func.__name__, r))
def strToBool(string):
retVal = None
if string.lower() in ["0","false","no"]:
retVal = False
elif string.lower() in ["1","true","yes"]:
retVal = True
return retVal
# Main execution Thread
class whatsmyname(threading.Thread):
def __init__(self, settings):
self.logger = logging.getLogger('calise.root')
self.logger.info("Starting main Thread...")
self.objectClass = objects.objects(settings)
self.cbs = self.objectClass.getCbs()
# control "flags"
self.stop = False
self.pause = False
# exec-options
# TODO: move weather to settings
self.weather = True
# threading.Thread initialization
threading.Thread.__init__(self)
def run(self):
self.logger.info("Main Thread successfully started")
while self.stop is False:
self.objectClass.resetComers()
self.cycle_sleeptime = self.objectClass.executer(
wcheck=self.weather)
self.objectClass.append_data()
self.event_logger()
# the cycle below sleeps Nth time for 1 sec and meanwhile checks
# if the stop flag has been set, in that case breaks (and so
# terminates the thread)
while self.cycle_sleeptime > time.time():
# if %pause% then sleep indefinitely (until NOT %pause%)
if self.pause is None:
self.pause = True
while self.pause and not self.stop:
time.sleep(1)
self.pause = False
else:
time.sleep(1)
# if %stop% then close thread
if self.stop is True:
break
def event_logger(self):
objc = self.objectClass
if len(objc.oldies) == 1:
if objc.oldies[-1]["cbs"] != self.cbs:
self.logger.info(
"Backlight step changed from %d to %d"
% (self.cbs, objc.oldies[-1]["cbs"]))
del self.cbs
self.logger.info(
"Time of the day is \"%s\"" % (objc.oldies[-1]["css"]))
self.logger.info(
"Sleeptime set to %.2f" % (objc.oldies[-1]["slp"]))
elif len(objc.oldies) > 1:
if objc.oldies[-1]["cbs"] != objc.oldies[-2]["cbs"]:
self.logger.info(
"Backlight step changed from %d to %d"
% (objc.oldies[-2]["cbs"], objc.oldies[-1]["cbs"]))
if objc.oldies[-1]["css"] != objc.oldies[-2]["css"]:
self.logger.info(
"Time of the day changed from \"%s\" to \"%s\""
% (objc.oldies[-2]["css"], objc.oldies[-1]["css"]))
if objc.oldies[-1]["slp"] != objc.oldies[-2]["slp"]:
self.logger.info(
"Sleeptime between captures changed from %.2f to %.2f"
% (objc.oldies[-2]["slp"], objc.oldies[-1]["slp"]))
return 0
# Create a listening server and indefinitely check for inputs
# Should be executed as thread
class DaemonLike():
def __init__(self, settings, dport=30780, dhost="localhost"):
# Init vars
self.th = None
self.stop = False
self.settings = settings
# Init functions
self.httpd = make_server(dhost, dport, self.application)
self.logger = logging.getLogger(".".join([__LowerName__, "server"]))
# Start functions
self.logger.info("Process started with PID %d" % os.getpid())
self.logger.info("Daemon listening on port %d" % dport)
debug_exec(self.start_thread, self.logger)
# wsgiref.simple_server basic application implementation
def application(self, environ, start_response):
# Returns a dictionary containing lists as values
d = parse_qs(environ['QUERY_STRING'])
# Escape and check value format (to avoid code injection)
for key in d.keys():
if type(d[key]) != list:
d[key] = [d[key]]
for x in range(len(d[key])):
d[key][x] = escape(d[key][x])
# Simple parse & response (None if not an "output" request)
d = self.gurreato_parser(d)
# Server-answer section
response_body = str(d)
status = '200 OK'
response_headers = [('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]
# start thread execution
# returns True if thread started correctly, elsewhere returns False
def start_thread(self):
if self.th is not None:
if self.th.isAlive():
return 1
self.th = whatsmyname(self.settings)
self.th.start()
return 0
# stop thread execution
# returns True if thread stopped correctly or there is no thread at all
def stop_thread(self):
if self.th is not None:
if self.th.isAlive():
self.th.stop = True
self.th.join(
self.th.objectClass.arguments["capnum"] * \
self.th.objectClass.arguments["capint"] * 3)
if self.th.isAlive():
self.th = None
return 1
return 0
# pause thread execution
def pause_thread(self):
if self.th is not None:
if self.th.isAlive() and self.th.pause is False:
self.th.pause = None
while self.th.pause is not True:
time.sleep(0.1)
return 0
return 1
# resume thread execution
def resume_thread(self):
if self.th is not None:
if self.th.isAlive() and self.th.pause is True:
self.th.pause = None
while self.th.pause is not False:
time.sleep(0.1)
return 0
return 1
# check thread execution
# If alive but paused returns 2, else if Alive and not paused, 0.
def check_thread(self):
if self.th is not None:
if self.th.isAlive():
if self.th.pause is True:
return 2
else:
return 0
else:
return 3
return 1
# Set thread variable's value (one per call)
# every and only settings stated there can be changed during execution
# TODO: simplify removing actual key names if you can
def set_thread(self, idx, value):
value = value[0]
if idx in ["weather"]:
self.th.weather = strToBool(value)
elif idx in ["latitude"]:
self.settings["latitude"] = float(value)
self.th.objectClass.arguments["latitude"] = float(value)
elif idx in ["longitude"]:
self.settings["longitude"] = float(value)
self.th.objectClass.arguments["longitude"] = float(value)
elif idx in ["capnum"]:
self.settings["capnum"] = int(value)
self.th.objectClass.arguments["capnum"] = int(value)
elif idx in ["capint"]:
self.settings["capint"] = float(value)
self.th.objectClass.arguments["capint"] = float(value)
return 0
# Manual camera capture
# If an existing running thread is found it will be paused and then
# resumed else, a temporary thread is initialized (but not started)
def manual_capture(self):
r = self.check_thread()
if r in (0, 2):
if r == 0:
self.pause_thread()
self.th.objectClass.resetComers()
old = self.th.objectClass.oldies[-1]
w = self.th.objectClass.writeStep(standalone=True)
ts = self.th.objectClass.newcomers["cts"] - old["cts"]
self.th.objectClass.newcomers["nss"] = old["nss"] - ts
self.th.objectClass.newcomers["slp"] = old["slp"] - ts
self.th.objectClass.newcomers["css"] = old["css"]
self.th.objectClass.append_data()
self.th.event_logger()
if r == 0:
self.resume_thread()
else:
if r == 1:
self.th = whatsmyname()
w = self.th.objectClass.writeStep(standalone=True)
self.th.event_logger()
self.logger.debug("Function objects.writeStep returned %d" % w)
return 0
# Parse args and executes corresponding operations, also logs processing
# data and returns messages according to the requests
def gurreato_parser(self, getd):
retMsg = []
# Kill sequence
if getd.keys().count("kill"):
self.logger.debug("Client requested kill. Stopping thread...")
retCode = self.stop_thread()
if retCode > 0:
self.logger.error("Failed to stop thread.")
retMsg.append(("error", "Failed to stop thread."))
else:
self.logger.info("Thread stopped successfully.")
self.logger.info("Sending kill to server.")
retMsg.append(("info", "Kill sent succesfully."))
#os.kill(os.getpid(), 15)
self.httpd.socket.close()
# Restart sequence
elif getd.keys().count("restart"):
self.logger.debug("Client requested kill. Stopping thread...")
debug_exec(self.stop_thread, self.logger)
debug_exec(self.start_thread, self.logger)
# Pause sequence
elif getd.keys().count("pause"):
self.logger.debug("Client requested pause. Pausing thread...")
retCode = self.pause_thread()
if retCode > 0:
self.logger.error("Failed to pause thread.")
retMsg.append(("error", "Failed to pause thread."))
else:
self.logger.info("Thread paused successfully.")
retMsg.append(("info", "Pause sent succesfully."))
# Resume sequence
elif getd.keys().count("resume"):
self.logger.debug("Client requested resume. Resuming thread...")
retCode = self.resume_thread()
if retCode > 0:
self.logger.error("Failed to resume thread.")
retMsg.append(("error", "Failed to resume thread."))
else:
self.logger.info("Thread resumed successfully.")
retMsg.append(("info", "Resume sent succesfully."))
# Check sequence
elif getd.keys().count("check"):
debug_exec(self.check_thread, self.logger)
# Other execution commands
elif getd.keys().count("capture"):
debug_exec(self.manual_capture, self.logger)
# Output triggered by "dump", output everything in dictionary format
if getd.keys().count("dump") and self.th is not None:
vals = self.th.objectClass.dumpValues()
retMsg += [(k, vals[k]) for k in vals.keys()]
elif getd.keys().count("dumpall") and self.th is not None:
vl = self.th.objectClass.dumpValues(allv=True)
# Format multiple values as list of values for every key
vals = {}
for idx in range(len(vl)):
for key in vl[idx].keys():
if vals.keys().count(key):
vals[key] += [vl[idx][key]]
else:
vals[key] = [vl[idx][key]]
retMsg += [(k, vals[k]) for k in vals.keys()]
if getd.keys().count("dumpsettings") and self.th is not None:
args = self.th.objectClass.arguments
retMsg += [(k, args[k]) for k in args.keys()]
# Settings set commands
settingsKeys = [
"weather",
"latitude", "longitude",
"capnum", "capint"]
if sum([x in settingsKeys for x in getd.keys()]) > 0:
if self.check_thread() in (0, 2):
for key in getd.keys():
self.set_thread(key, getd[key])
return retMsg
def runrunrun(self):
try:
self.httpd.serve_forever()
except socket.error as err:
if err.errno != 9:
raise