Merge branch 'richardjm-daemon' into daemon

Conflicts:
	octoprint/server.py
master
Gina Häußge 2013-03-10 19:11:16 +01:00
commit 1b1f118646
3 changed files with 241 additions and 77 deletions

122
octoprint/daemon.py Normal file
View File

@ -0,0 +1,122 @@
"""Generic linux daemon base class for python 3.x."""
import sys, os, time, atexit, signal
class Daemon:
"""A generic daemon class.
Usage: subclass the daemon class and override the run() method."""
def __init__(self, pidfile): self.pidfile = pidfile
def daemonize(self):
"""Deamonize class. UNIX double fork mechanism."""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as err:
sys.stderr.write('fork #1 failed: {0}\n'.format(err))
sys.exit(1)
# decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as err:
sys.stderr.write('fork #2 failed: {0}\n'.format(err))
sys.exit(1)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
with open(self.pidfile,'w+') as f:
f.write(pid + '\n')
def delpid(self):
os.remove(self.pidfile)
def start(self):
"""Start the daemon."""
# Check for a pidfile to see if the daemon already runs
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
if pid:
message = "pidfile {0} already exist. " + \
"Daemon already running?\n"
sys.stderr.write(message.format(self.pidfile))
sys.exit(1)
# Start the daemon
self.daemonize()
self.run()
def stop(self):
"""Stop the daemon."""
# Get the pid from the pidfile
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
if not pid:
message = "pidfile {0} does not exist. " + \
"Daemon not running?\n"
sys.stderr.write(message.format(self.pidfile))
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
e = str(err.args)
if e.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print (str(err.args))
sys.exit(1)
def restart(self):
"""Restart the daemon."""
self.stop()
self.start()
def run(self):
"""You should override this method when you subclass Daemon.
It will be called after the process has been daemonized by
start() or restart()."""

View File

@ -17,24 +17,14 @@ import octoprint.timelapse as timelapse
import octoprint.gcodefiles as gcodefiles
import octoprint.util as util
BASEURL = "/ajax/"
SUCCESS = {}
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
BASEURL = "/ajax/"
app = Flask("octoprint")
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
@app.route("/")
def index():
return render_template(
"index.html",
webcamStream=settings().get(["webcam", "stream"]),
enableTimelapse=settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None,
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
)
# Only instantiated by the Server().run() method
# In order that threads don't start too early when running as a Daemon
printer = None
gcodeManager = None
#~~ Printer state
@ -53,11 +43,13 @@ class PrinterStateConnection(tornadio2.SocketConnection):
def on_open(self, info):
self._logger.info("New connection from client")
# Use of global here is smelly
printer.registerCallback(self)
gcodeManager.registerCallback(self)
def on_close(self):
self._logger.info("Closed client connection")
# Use of global here is smelly
printer.unregisterCallback(self)
gcodeManager.unregisterCallback(self)
@ -103,6 +95,18 @@ class PrinterStateConnection(tornadio2.SocketConnection):
with self._temperatureBacklogMutex:
self._temperatureBacklog.append(data)
# Did attempt to make webserver an encapsulated class but ended up with __call__ failures
@app.route("/")
def index():
return render_template(
"index.html",
webcamStream=settings().get(["webcam", "stream"]),
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
)
#~~ Printer control
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
@ -173,7 +177,7 @@ def setTargetTemperature():
return jsonify(SUCCESS)
elif request.values.has_key("temp"):
# set target temperature
# set target temperature
temp = request.values["temp"]
printer.command("M104 S" + temp)
@ -437,77 +441,86 @@ def performSystemAction():
return jsonify(SUCCESS)
#~~ startup code
class Server():
def run(self, host = "0.0.0.0", port = 5000, debug = False):
# Global as I can't work out a way to get it into PrinterStateConnection
global printer
global gcodeManager
def run(host = "0.0.0.0", port = 5000, debug = False):
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, FallbackHandler
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, FallbackHandler
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
router = tornadio2.TornadioRouter(PrinterStateConnection)
tornado_app = Application(router.urls + [
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
])
server = HTTPServer(tornado_app)
server.listen(port, address=host)
IOLoop.instance().start()
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug
def initLogging():
config = {
"version": 1,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
self._tornado_app = Application(self._router.urls + [
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
])
self._server = HTTPServer(self._tornado_app)
self._server.listen(port, address=host)
IOLoop.instance().start()
def initLogging(self):
self._config = {
"version": 1,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"level": "DEBUG",
"formatter": "simple",
"when": "D",
"backupCount": "1",
"filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log")
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"level": "DEBUG",
"formatter": "simple",
"when": "D",
"backupCount": "1",
"filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log")
}
},
"loggers": {
"octoprint.gcodefiles": {
"level": "DEBUG"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file"]
}
},
"loggers": {
"octoprint.gcodefiles": {
"level": "DEBUG"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file"]
}
}
logging.config.dictConfig(config)
logging.config.dictConfig(self._config)
def main():
from optparse import OptionParser
def start(self):
from optparse import OptionParser
defaultHost = settings().get(["server", "host"])
defaultPort = settings().get(["server", "port"])
self._defaultHost = settings().get(["server", "host"])
self._defaultPort = settings().get(["server", "port"])
parser = OptionParser(usage="usage: %prog [options]")
parser.add_option("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
parser.add_option("--host", action="store", type="string", default=defaultHost, dest="host",
help="Specify the host on which to bind the server, defaults to %s if not set" % (defaultHost))
parser.add_option("--port", action="store", type="int", default=defaultPort, dest="port",
help="Specify the port on which to bind the server, defaults to %s if not set" % (defaultPort))
(options, args) = parser.parse_args()
self._parser = OptionParser(usage="usage: %prog [options]")
self._parser.add_option("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
self._parser.add_option("--host", action="store", type="string", default=self._defaultHost, dest="host",
help="Specify the host on which to bind the server, defaults to %s if not set" % (self._defaultHost))
self._parser.add_option("--port", action="store", type="int", default=self._defaultPort, dest="port",
help="Specify the port on which to bind the server, defaults to %s if not set" % (self._defaultPort))
(options, args) = self._parser.parse_args()
initLogging()
run(host=options.host, port=options.port, debug=options.debug)
self.initLogging()
self.run(host=options.host, port=options.port, debug=options.debug)
if __name__ == "__main__":
main()
octoprint = Server()
octoprint.start()

29
run-as-daemon Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/python
import sys, time
from octoprint.daemon import Daemon
from octoprint.server import Server
class Main(Daemon):
def run(self):
octoprint = Server()
octoprint.start()
def main():
daemon = Main('/tmp/octoprint.pid')
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print "Unknown command"
sys.exit(2)
sys.exit(0)
else:
print "usage: %s start|stop|restart" % sys.argv[0]
sys.exit(2)
if __name__ == "__main__":
main()