commit
1b1f118646
|
@ -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()."""
|
||||||
|
|
|
@ -17,24 +17,14 @@ import octoprint.timelapse as timelapse
|
||||||
import octoprint.gcodefiles as gcodefiles
|
import octoprint.gcodefiles as gcodefiles
|
||||||
import octoprint.util as util
|
import octoprint.util as util
|
||||||
|
|
||||||
BASEURL = "/ajax/"
|
|
||||||
SUCCESS = {}
|
SUCCESS = {}
|
||||||
|
|
||||||
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
|
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
|
||||||
|
BASEURL = "/ajax/"
|
||||||
app = Flask("octoprint")
|
app = Flask("octoprint")
|
||||||
gcodeManager = gcodefiles.GcodeManager()
|
# Only instantiated by the Server().run() method
|
||||||
printer = Printer(gcodeManager)
|
# In order that threads don't start too early when running as a Daemon
|
||||||
|
printer = None
|
||||||
@app.route("/")
|
gcodeManager = None
|
||||||
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 state
|
#~~ Printer state
|
||||||
|
|
||||||
|
@ -53,11 +43,13 @@ class PrinterStateConnection(tornadio2.SocketConnection):
|
||||||
|
|
||||||
def on_open(self, info):
|
def on_open(self, info):
|
||||||
self._logger.info("New connection from client")
|
self._logger.info("New connection from client")
|
||||||
|
# Use of global here is smelly
|
||||||
printer.registerCallback(self)
|
printer.registerCallback(self)
|
||||||
gcodeManager.registerCallback(self)
|
gcodeManager.registerCallback(self)
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self._logger.info("Closed client connection")
|
self._logger.info("Closed client connection")
|
||||||
|
# Use of global here is smelly
|
||||||
printer.unregisterCallback(self)
|
printer.unregisterCallback(self)
|
||||||
gcodeManager.unregisterCallback(self)
|
gcodeManager.unregisterCallback(self)
|
||||||
|
|
||||||
|
@ -103,6 +95,18 @@ class PrinterStateConnection(tornadio2.SocketConnection):
|
||||||
with self._temperatureBacklogMutex:
|
with self._temperatureBacklogMutex:
|
||||||
self._temperatureBacklog.append(data)
|
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
|
#~~ Printer control
|
||||||
|
|
||||||
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
|
@app.route(BASEURL + "control/connectionOptions", methods=["GET"])
|
||||||
|
@ -437,26 +441,34 @@ def performSystemAction():
|
||||||
return jsonify(SUCCESS)
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
#~~ startup code
|
#~~ 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.wsgi import WSGIContainer
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.web import Application, FallbackHandler
|
from tornado.web import Application, FallbackHandler
|
||||||
|
|
||||||
|
gcodeManager = gcodefiles.GcodeManager()
|
||||||
|
printer = Printer(gcodeManager)
|
||||||
|
|
||||||
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
|
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
|
||||||
app.debug = debug
|
app.debug = debug
|
||||||
|
|
||||||
router = tornadio2.TornadioRouter(PrinterStateConnection)
|
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
|
||||||
tornado_app = Application(router.urls + [
|
|
||||||
|
self._tornado_app = Application(self._router.urls + [
|
||||||
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
|
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
|
||||||
])
|
])
|
||||||
server = HTTPServer(tornado_app)
|
self._server = HTTPServer(self._tornado_app)
|
||||||
server.listen(port, address=host)
|
self._server.listen(port, address=host)
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
|
||||||
def initLogging():
|
def initLogging(self):
|
||||||
config = {
|
self._config = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"formatters": {
|
"formatters": {
|
||||||
"simple": {
|
"simple": {
|
||||||
|
@ -489,25 +501,26 @@ def initLogging():
|
||||||
"handlers": ["console", "file"]
|
"handlers": ["console", "file"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logging.config.dictConfig(config)
|
logging.config.dictConfig(self._config)
|
||||||
|
|
||||||
def main():
|
def start(self):
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
defaultHost = settings().get(["server", "host"])
|
self._defaultHost = settings().get(["server", "host"])
|
||||||
defaultPort = settings().get(["server", "port"])
|
self._defaultPort = settings().get(["server", "port"])
|
||||||
|
|
||||||
parser = OptionParser(usage="usage: %prog [options]")
|
self._parser = OptionParser(usage="usage: %prog [options]")
|
||||||
parser.add_option("-d", "--debug", action="store_true", dest="debug",
|
self._parser.add_option("-d", "--debug", action="store_true", dest="debug",
|
||||||
help="Enable debug mode")
|
help="Enable debug mode")
|
||||||
parser.add_option("--host", action="store", type="string", default=defaultHost, dest="host",
|
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" % (defaultHost))
|
help="Specify the host on which to bind the server, defaults to %s if not set" % (self._defaultHost))
|
||||||
parser.add_option("--port", action="store", type="int", default=defaultPort, dest="port",
|
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" % (defaultPort))
|
help="Specify the port on which to bind the server, defaults to %s if not set" % (self._defaultPort))
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = self._parser.parse_args()
|
||||||
|
|
||||||
initLogging()
|
self.initLogging()
|
||||||
run(host=options.host, port=options.port, debug=options.debug)
|
self.run(host=options.host, port=options.port, debug=options.debug)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
octoprint = Server()
|
||||||
|
octoprint.start()
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue