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"])
@ -437,26 +441,34 @@ 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
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug
router = tornadio2.TornadioRouter(PrinterStateConnection)
tornado_app = Application(router.urls + [
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
self._tornado_app = Application(self._router.urls + [
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
])
server = HTTPServer(tornado_app)
server.listen(port, address=host)
self._server = HTTPServer(self._tornado_app)
self._server.listen(port, address=host)
IOLoop.instance().start()
def initLogging():
config = {
def initLogging(self):
self._config = {
"version": 1,
"formatters": {
"simple": {
@ -489,25 +501,26 @@ def initLogging():
"handlers": ["console", "file"]
}
}
logging.config.dictConfig(config)
logging.config.dictConfig(self._config)
def main():
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",
self._parser = OptionParser(usage="usage: %prog [options]")
self._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.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()