2012-12-25 10:55:00 +00:00
# coding=utf-8
2012-12-28 19:37:40 +00:00
__author__ = " Gina Häußge <osd@foosel.net> "
2012-12-31 12:18:54 +00:00
__license__ = ' GNU Affero General Public License http://www.gnu.org/licenses/agpl.html '
2012-12-25 10:55:00 +00:00
2013-01-30 19:56:17 +00:00
from werkzeug . utils import secure_filename
2013-09-01 16:12:30 +00:00
from werkzeug . datastructures import Headers
2013-08-25 13:03:27 +00:00
from sockjs . tornado import SockJSRouter , SockJSConnection
2013-09-01 16:12:30 +00:00
from flask import Flask , request , render_template , jsonify , send_from_directory , url_for , current_app , session , abort , make_response , Response
2013-04-01 12:24:47 +00:00
from flask . ext . login import LoginManager , login_user , logout_user , login_required , current_user
from flask . ext . principal import Principal , Permission , RoleNeed , Identity , identity_changed , AnonymousIdentity , identity_loaded , UserNeed
2012-12-25 10:55:00 +00:00
2013-08-10 19:59:05 +00:00
from functools import wraps
2012-12-25 19:49:10 +00:00
import os
2013-01-11 23:00:58 +00:00
import threading
2013-02-03 20:14:22 +00:00
import logging , logging . config
2013-03-10 16:04:05 +00:00
import subprocess
2013-08-18 16:33:21 +00:00
import netaddr
2013-01-06 15:51:04 +00:00
2013-02-03 20:14:22 +00:00
from octoprint . printer import Printer , getConnectionOptions
2013-04-07 20:06:17 +00:00
from octoprint . settings import settings , valid_boolean_trues
2013-06-16 20:42:18 +00:00
import octoprint . timelapse
2013-01-30 19:56:17 +00:00
import octoprint . gcodefiles as gcodefiles
import octoprint . util as util
2013-03-17 21:28:08 +00:00
import octoprint . users as users
2012-12-25 19:49:10 +00:00
2013-05-21 03:04:21 +00:00
import octoprint . events as events
2013-01-04 17:38:50 +00:00
SUCCESS = { }
2013-03-10 13:14:37 +00:00
BASEURL = " /ajax/ "
2013-05-18 17:47:31 +00:00
APIBASEURL = " /api/ "
2013-03-17 21:28:08 +00:00
2013-01-18 22:23:50 +00:00
app = Flask ( " octoprint " )
2013-03-10 13:40:53 +00:00
# Only instantiated by the Server().run() method
# In order that threads don't start too early when running as a Daemon
2013-06-16 20:42:18 +00:00
printer = None
timelapse = None
2013-08-25 13:03:27 +00:00
debug = False
2013-06-16 20:42:18 +00:00
2013-03-10 13:14:37 +00:00
gcodeManager = None
2013-03-17 21:28:08 +00:00
userManager = None
2013-05-26 22:56:57 +00:00
eventManager = None
2013-08-10 19:59:05 +00:00
loginManager = None
2012-12-25 10:55:00 +00:00
2013-04-01 12:24:47 +00:00
principals = Principal ( app )
admin_permission = Permission ( RoleNeed ( " admin " ) )
user_permission = Permission ( RoleNeed ( " user " ) )
2012-12-25 19:49:10 +00:00
#~~ Printer state
2012-12-25 10:55:00 +00:00
2013-08-25 13:03:27 +00:00
class PrinterStateConnection ( SockJSConnection ) :
def __init__ ( self , printer , gcodeManager , userManager , eventManager , session ) :
SockJSConnection . __init__ ( self , session )
2013-01-11 23:00:58 +00:00
2013-02-03 20:14:22 +00:00
self . _logger = logging . getLogger ( __name__ )
2013-01-11 23:00:58 +00:00
self . _temperatureBacklog = [ ]
self . _temperatureBacklogMutex = threading . Lock ( )
self . _logBacklog = [ ]
self . _logBacklogMutex = threading . Lock ( )
self . _messageBacklog = [ ]
self . _messageBacklogMutex = threading . Lock ( )
2013-03-19 20:07:47 +00:00
self . _printer = printer
self . _gcodeManager = gcodeManager
self . _userManager = userManager
2013-05-26 22:56:57 +00:00
self . _eventManager = eventManager
2013-03-19 20:07:47 +00:00
2013-08-25 13:03:27 +00:00
def _getRemoteAddress ( self , info ) :
forwardedFor = info . headers . get ( " X-Forwarded-For " )
if forwardedFor is not None :
return forwardedFor . split ( " , " ) [ 0 ]
return info . ip
2013-01-06 15:51:04 +00:00
def on_open ( self , info ) :
2013-08-25 13:03:27 +00:00
self . _logger . info ( " New connection from client: %s " % self . _getRemoteAddress ( info ) )
2013-06-16 20:42:18 +00:00
self . _printer . registerCallback ( self )
self . _gcodeManager . registerCallback ( self )
2013-01-06 15:51:04 +00:00
2013-05-26 22:56:57 +00:00
self . _eventManager . fire ( " ClientOpened " )
2013-06-16 19:50:50 +00:00
self . _eventManager . subscribe ( " MovieDone " , self . _onMovieDone )
2013-05-26 22:56:57 +00:00
2013-01-06 15:51:04 +00:00
def on_close ( self ) :
2013-02-03 20:14:22 +00:00
self . _logger . info ( " Closed client connection " )
2013-06-16 20:42:18 +00:00
self . _printer . unregisterCallback ( self )
self . _gcodeManager . unregisterCallback ( self )
2013-01-06 15:51:04 +00:00
2013-05-26 22:56:57 +00:00
self . _eventManager . fire ( " ClientClosed " )
2013-06-16 19:50:50 +00:00
self . _eventManager . unsubscribe ( " MovieDone " , self . _onMovieDone )
2013-05-26 22:56:57 +00:00
2013-01-06 21:10:59 +00:00
def on_message ( self , message ) :
pass
2013-01-10 22:40:00 +00:00
def sendCurrentData ( self , data ) :
2013-01-11 23:00:58 +00:00
# add current temperature, log and message backlogs to sent data
with self . _temperatureBacklogMutex :
temperatures = self . _temperatureBacklog
self . _temperatureBacklog = [ ]
with self . _logBacklogMutex :
logs = self . _logBacklog
self . _logBacklog = [ ]
with self . _messageBacklogMutex :
messages = self . _messageBacklog
self . _messageBacklog = [ ]
data . update ( {
" temperatures " : temperatures ,
" logs " : logs ,
" messages " : messages
} )
2013-08-25 13:03:27 +00:00
self . _emit ( " current " , data )
2013-01-10 22:40:00 +00:00
def sendHistoryData ( self , data ) :
2013-08-25 13:03:27 +00:00
self . _emit ( " history " , data )
2013-01-06 20:19:39 +00:00
2013-02-03 20:14:22 +00:00
def sendUpdateTrigger ( self , type ) :
2013-08-25 13:03:27 +00:00
self . _emit ( " updateTrigger " , type )
2013-02-03 20:14:22 +00:00
2013-06-21 18:50:57 +00:00
def sendFeedbackCommandOutput ( self , name , output ) :
2013-08-25 13:03:27 +00:00
self . _emit ( " feedbackCommandOutput " , { " name " : name , " output " : output } )
2013-06-21 18:50:57 +00:00
2013-01-11 23:00:58 +00:00
def addLog ( self , data ) :
with self . _logBacklogMutex :
self . _logBacklog . append ( data )
def addMessage ( self , data ) :
with self . _messageBacklogMutex :
self . _messageBacklog . append ( data )
def addTemperature ( self , data ) :
with self . _temperatureBacklogMutex :
self . _temperatureBacklog . append ( data )
2013-06-16 19:50:50 +00:00
def _onMovieDone ( self , event , payload ) :
self . sendUpdateTrigger ( " timelapseFiles " )
2013-08-25 13:03:27 +00:00
def _emit ( self , type , payload ) :
self . send ( { type : payload } )
2013-08-10 19:59:05 +00:00
def restricted_access ( func ) :
"""
If you decorate a view with this , it will ensure that first setup has been
done for OctoPrint ' s Access Control plus that any conditions of the
login_required decorator are met .
If OctoPrint ' s Access Control has not been setup yet (indicated by the " firstRun "
flag from the settings being set to True and the userManager not indicating
that it ' s user database has been customized from default), the decorator
will cause a HTTP 403 status code to be returned by the decorated resource .
Otherwise the result of calling login_required will be returned .
"""
@wraps ( func )
def decorated_view ( * args , * * kwargs ) :
if settings ( ) . getBoolean ( [ " server " , " firstRun " ] ) and ( userManager is None or not userManager . hasBeenCustomized ( ) ) :
return make_response ( " OctoPrint isn ' t setup yet " , 403 )
return login_required ( func ) ( * args , * * kwargs )
return decorated_view
2013-03-10 13:14:37 +00:00
# Did attempt to make webserver an encapsulated class but ended up with __call__ failures
@app.route ( " / " )
def index ( ) :
2013-06-29 11:43:45 +00:00
branch = None
commit = None
try :
branch , commit = util . getGitInfo ( )
except :
pass
2013-08-25 13:03:27 +00:00
global debug
2013-03-10 13:14:37 +00:00
return render_template (
2013-04-13 19:40:28 +00:00
" index.jinja2 " ,
ajaxBaseUrl = BASEURL ,
2013-03-10 13:14:37 +00:00
webcamStream = settings ( ) . get ( [ " webcam " , " stream " ] ) ,
enableTimelapse = ( settings ( ) . get ( [ " webcam " , " snapshot " ] ) is not None and settings ( ) . get ( [ " webcam " , " ffmpeg " ] ) is not None ) ,
2013-03-10 18:11:16 +00:00
enableGCodeVisualizer = settings ( ) . get ( [ " feature " , " gCodeVisualizer " ] ) ,
2013-08-29 19:16:35 +00:00
enableTemperatureGraph = settings ( ) . get ( [ " feature " , " temperatureGraph " ] ) ,
2013-03-18 21:27:23 +00:00
enableSystemMenu = settings ( ) . get ( [ " system " ] ) is not None and settings ( ) . get ( [ " system " , " actions " ] ) is not None and len ( settings ( ) . get ( [ " system " , " actions " ] ) ) > 0 ,
2013-05-20 17:18:03 +00:00
enableAccessControl = userManager is not None ,
2013-06-29 11:41:39 +00:00
enableSdSupport = settings ( ) . get ( [ " feature " , " sdSupport " ] ) ,
2013-08-10 19:59:05 +00:00
firstRun = settings ( ) . getBoolean ( [ " server " , " firstRun " ] ) and ( userManager is None or not userManager . hasBeenCustomized ( ) ) ,
2013-08-25 13:03:27 +00:00
debug = debug ,
2013-06-29 11:41:39 +00:00
gitBranch = branch ,
gitCommit = commit
2013-03-10 13:14:37 +00:00
)
2013-08-11 14:57:39 +00:00
@app.route ( " /robots.txt " )
def robotsTxt ( ) :
return send_from_directory ( app . static_folder , " robots.txt " )
2012-12-25 19:49:10 +00:00
#~~ Printer control
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " control/connection/options " , methods = [ " GET " ] )
2012-12-28 19:37:40 +00:00
def connectionOptions ( ) :
return jsonify ( getConnectionOptions ( ) )
2012-12-25 10:55:00 +00:00
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " control/connection " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-28 19:37:40 +00:00
def connect ( ) :
2013-04-12 21:08:14 +00:00
if " command " in request . values . keys ( ) and request . values [ " command " ] == " connect " :
port = None
baudrate = None
if " port " in request . values . keys ( ) :
port = request . values [ " port " ]
if " baudrate " in request . values . keys ( ) :
baudrate = request . values [ " baudrate " ]
if " save " in request . values . keys ( ) :
settings ( ) . set ( [ " serial " , " port " ] , port )
settings ( ) . setInt ( [ " serial " , " baudrate " ] , baudrate )
settings ( ) . save ( )
2013-06-29 17:57:46 +00:00
if " autoconnect " in request . values . keys ( ) :
settings ( ) . setBoolean ( [ " serial " , " autoconnect " ] , True )
settings ( ) . save ( )
2013-04-12 21:08:14 +00:00
printer . connect ( port = port , baudrate = baudrate )
elif " command " in request . values . keys ( ) and request . values [ " command " ] == " disconnect " :
printer . disconnect ( )
return jsonify ( SUCCESS )
2012-12-25 19:49:10 +00:00
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " control/command " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-25 19:49:10 +00:00
def printerCommand ( ) :
2013-02-23 17:08:59 +00:00
if " application/json " in request . headers [ " Content-Type " ] :
data = request . json
2013-01-27 17:28:11 +00:00
2013-02-23 17:08:59 +00:00
parameters = { }
if " parameters " in data . keys ( ) : parameters = data [ " parameters " ]
2013-01-27 17:28:11 +00:00
2013-02-23 17:08:59 +00:00
commands = [ ]
if " command " in data . keys ( ) : commands = [ data [ " command " ] ]
elif " commands " in data . keys ( ) : commands = data [ " commands " ]
commandsToSend = [ ]
for command in commands :
commandToSend = command
if len ( parameters ) > 0 :
commandToSend = command % parameters
commandsToSend . append ( commandToSend )
printer . commands ( commandsToSend )
2013-01-27 17:28:11 +00:00
2012-12-25 19:49:10 +00:00
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " control/job " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-12 21:08:14 +00:00
def printJobControl ( ) :
if " command " in request . values . keys ( ) :
if request . values [ " command " ] == " start " :
printer . startPrint ( )
elif request . values [ " command " ] == " pause " :
printer . togglePausePrint ( )
elif request . values [ " command " ] == " cancel " :
printer . cancelPrint ( )
2012-12-25 19:49:10 +00:00
return jsonify ( SUCCESS )
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " control/temperature " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-26 14:03:34 +00:00
def setTargetTemperature ( ) :
2013-04-12 21:08:14 +00:00
if " temp " in request . values . keys ( ) :
# set target temperature
2013-01-30 19:56:17 +00:00
temp = request . values [ " temp " ]
2012-12-26 14:03:34 +00:00
printer . command ( " M104 S " + temp )
2013-04-12 21:08:14 +00:00
if " bedTemp " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# set target bed temperature
bedTemp = request . values [ " bedTemp " ]
printer . command ( " M140 S " + bedTemp )
return jsonify ( SUCCESS )
@app.route ( BASEURL + " control/jog " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-26 14:03:34 +00:00
def jog ( ) :
if not printer . isOperational ( ) or printer . isPrinting ( ) :
2013-02-16 19:28:09 +00:00
# do not jog when a print job is running or we don't have a connection
2012-12-26 14:03:34 +00:00
return jsonify ( SUCCESS )
2013-02-16 19:28:09 +00:00
( movementSpeedX , movementSpeedY , movementSpeedZ , movementSpeedE ) = settings ( ) . get ( [ " printerParameters " , " movementSpeed " , [ " x " , " y " , " z " , " e " ] ] )
2013-01-30 19:56:17 +00:00
if " x " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# jog x
x = request . values [ " x " ]
2013-02-16 19:28:09 +00:00
printer . commands ( [ " G91 " , " G1 X %s F %d " % ( x , movementSpeedX ) , " G90 " ] )
2013-01-30 19:56:17 +00:00
if " y " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# jog y
y = request . values [ " y " ]
2013-02-16 19:28:09 +00:00
printer . commands ( [ " G91 " , " G1 Y %s F %d " % ( y , movementSpeedY ) , " G90 " ] )
2013-01-30 19:56:17 +00:00
if " z " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# jog z
z = request . values [ " z " ]
2013-02-16 19:28:09 +00:00
printer . commands ( [ " G91 " , " G1 Z %s F %d " % ( z , movementSpeedZ ) , " G90 " ] )
2013-01-30 19:56:17 +00:00
if " homeXY " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# home x/y
printer . command ( " G28 X0 Y0 " )
2013-01-30 19:56:17 +00:00
if " homeZ " in request . values . keys ( ) :
2012-12-26 14:03:34 +00:00
# home z
printer . command ( " G28 Z0 " )
2013-02-09 23:40:06 +00:00
if " extrude " in request . values . keys ( ) :
# extrude/retract
length = request . values [ " extrude " ]
2013-02-16 19:28:09 +00:00
printer . commands ( [ " G91 " , " G1 E %s F %d " % ( length , movementSpeedE ) , " G90 " ] )
2012-12-26 14:03:34 +00:00
return jsonify ( SUCCESS )
2013-01-27 10:12:28 +00:00
@app.route ( BASEURL + " control/custom " , methods = [ " GET " ] )
def getCustomControls ( ) :
2013-02-16 19:28:09 +00:00
customControls = settings ( ) . get ( [ " controls " ] )
2013-01-30 19:56:17 +00:00
return jsonify ( controls = customControls )
2013-01-27 10:12:28 +00:00
2013-05-26 16:53:43 +00:00
@app.route ( BASEURL + " control/sd " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-05-26 16:53:43 +00:00
def sdCommand ( ) :
if not settings ( ) . getBoolean ( [ " feature " , " sdSupport " ] ) or not printer . isOperational ( ) or printer . isPrinting ( ) :
return jsonify ( SUCCESS )
if " command " in request . values . keys ( ) :
command = request . values [ " command " ]
if command == " init " :
printer . initSdCard ( )
elif command == " refresh " :
printer . refreshSdFiles ( )
elif command == " release " :
printer . releaseSdCard ( )
return jsonify ( SUCCESS )
2012-12-25 19:49:10 +00:00
#~~ GCODE file handling
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " gcodefiles " , methods = [ " GET " ] )
2012-12-25 19:49:10 +00:00
def readGcodeFiles ( ) :
2013-05-20 17:18:03 +00:00
files = gcodeManager . getAllFileData ( )
sdFileList = printer . getSdFiles ( )
if sdFileList is not None :
for sdFile in sdFileList :
files . append ( {
" name " : sdFile ,
" size " : " n/a " ,
" bytes " : 0 ,
" date " : " n/a " ,
" origin " : " sd "
} )
2013-07-20 14:01:17 +00:00
return jsonify ( files = files , free = util . getFormattedSize ( util . getFreeBytes ( settings ( ) . getBaseFolder ( " uploads " ) ) ) )
2012-12-25 19:49:10 +00:00
2013-02-03 21:01:11 +00:00
@app.route ( BASEURL + " gcodefiles/<path:filename> " , methods = [ " GET " ] )
2013-02-01 22:13:44 +00:00
def readGcodeFile ( filename ) :
2013-09-01 16:12:30 +00:00
path = os . path . join ( settings ( ) . getBaseFolder ( " uploads " ) , filename )
return _streamBinaryFile ( path , mimeType = " text/plain " , asAttachment = True )
2013-02-01 22:13:44 +00:00
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " gcodefiles/upload " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-25 19:49:10 +00:00
def uploadGcodeFile ( ) :
2013-01-30 19:56:17 +00:00
if " gcode_file " in request . files . keys ( ) :
2013-01-04 12:11:00 +00:00
file = request . files [ " gcode_file " ]
2013-07-13 14:30:41 +00:00
sd = " target " in request . values . keys ( ) and request . values [ " target " ] == " sd " ;
currentFilename = None
currentSd = None
currentJob = printer . getCurrentJob ( )
if currentJob is not None and " filename " in currentJob . keys ( ) and " sd " in currentJob . keys ( ) :
currentFilename = currentJob [ " filename " ]
currentSd = currentJob [ " sd " ]
futureFilename = gcodeManager . getFutureFilename ( file )
if futureFilename is None :
return make_response ( " Can not upload file %s , wrong format? " % file . filename , 400 )
if futureFilename == currentFilename and sd == currentSd and printer . isPrinting ( ) or printer . isPaused ( ) :
# trying to overwrite currently selected file, but it is being printed
return make_response ( " Trying to overwrite file that is currently being printed: %s " % currentFilename , 403 )
2013-02-04 21:24:32 +00:00
filename = gcodeManager . addFile ( file )
2013-07-13 14:30:41 +00:00
if filename is None :
return make_response ( " Could not upload the file %s " % file . filename , 500 )
absFilename = gcodeManager . getAbsolutePath ( filename )
if sd :
printer . addSdFile ( filename , absFilename )
if currentFilename == filename and currentSd == sd :
# reload file as it was updated
if sd :
printer . selectFile ( filename , sd , False )
else :
printer . selectFile ( absFilename , sd , False )
2013-05-26 22:56:57 +00:00
2013-05-21 03:04:21 +00:00
global eventManager
2013-05-26 22:56:57 +00:00
eventManager . fire ( " Upload " , filename )
2013-02-04 21:24:32 +00:00
return jsonify ( files = gcodeManager . getAllFileData ( ) , filename = filename )
2012-12-25 19:49:10 +00:00
2013-05-18 17:47:31 +00:00
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " gcodefiles/load " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-25 19:49:10 +00:00
def loadGcodeFile ( ) :
2013-01-30 19:56:17 +00:00
if " filename " in request . values . keys ( ) :
2013-03-30 17:21:49 +00:00
printAfterLoading = False
2013-04-07 20:06:17 +00:00
if " print " in request . values . keys ( ) and request . values [ " print " ] in valid_boolean_trues :
2013-03-30 17:21:49 +00:00
printAfterLoading = True
2013-05-26 22:56:57 +00:00
2013-05-31 20:41:53 +00:00
sd = False
2013-05-20 17:18:03 +00:00
if " target " in request . values . keys ( ) and request . values [ " target " ] == " sd " :
filename = request . values [ " filename " ]
2013-05-31 20:41:53 +00:00
sd = True
2013-05-20 17:18:03 +00:00
else :
filename = gcodeManager . getAbsolutePath ( request . values [ " filename " ] )
2013-05-31 20:41:53 +00:00
printer . selectFile ( filename , sd , printAfterLoading )
2013-03-26 05:09:36 +00:00
return jsonify ( SUCCESS )
2012-12-28 19:37:40 +00:00
@app.route ( BASEURL + " gcodefiles/delete " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2012-12-25 19:49:10 +00:00
def deleteGcodeFile ( ) :
2013-01-30 19:56:17 +00:00
if " filename " in request . values . keys ( ) :
2012-12-26 23:04:12 +00:00
filename = request . values [ " filename " ]
2013-07-13 14:30:41 +00:00
sd = " target " in request . values . keys ( ) and request . values [ " target " ] == " sd "
2013-07-07 21:27:22 +00:00
2013-07-13 14:30:41 +00:00
currentJob = printer . getCurrentJob ( )
2013-07-07 21:27:22 +00:00
currentFilename = None
currentSd = None
if currentJob is not None and " filename " in currentJob . keys ( ) and " sd " in currentJob . keys ( ) :
currentFilename = currentJob [ " filename " ]
currentSd = currentJob [ " sd " ]
2013-07-13 14:30:41 +00:00
if currentFilename is not None and filename == currentFilename and not ( printer . isPrinting ( ) or printer . isPaused ( ) ) :
printer . unselectFile ( )
if not ( currentFilename == filename and currentSd == sd and ( printer . isPrinting ( ) or printer . isPaused ( ) ) ) :
if currentSd :
printer . deleteSdFile ( filename )
else :
gcodeManager . removeFile ( filename )
2012-12-25 19:49:10 +00:00
return readGcodeFiles ( )
2013-05-26 16:53:43 +00:00
@app.route ( BASEURL + " gcodefiles/refresh " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-05-26 16:53:43 +00:00
def refreshFiles ( ) :
printer . updateSdFiles ( )
return jsonify ( SUCCESS )
2013-05-18 17:47:31 +00:00
#-- very simple api routines
@app.route ( APIBASEURL + " load " , methods = [ " POST " ] )
def apiLoad ( ) :
2013-06-23 14:57:38 +00:00
logger = logging . getLogger ( __name__ )
2013-05-18 17:47:31 +00:00
2013-06-23 17:48:12 +00:00
if not settings ( ) . get ( [ " api " , " enabled " ] ) :
2013-06-23 14:57:38 +00:00
abort ( 401 )
2013-05-18 17:47:31 +00:00
2013-06-23 14:57:38 +00:00
if not " apikey " in request . values . keys ( ) :
abort ( 401 )
if request . values [ " apikey " ] != settings ( ) . get ( [ " api " , " key " ] ) :
abort ( 403 )
if not " file " in request . files . keys ( ) :
abort ( 400 )
# Perform an upload
file = request . files [ " file " ]
filename = gcodeManager . addFile ( file )
if filename is None :
logger . warn ( " Upload via API failed " )
abort ( 500 )
# Immediately perform a file select and possibly print too
printAfterSelect = False
if " print " in request . values . keys ( ) and request . values [ " print " ] in valid_boolean_trues :
printAfterSelect = True
filepath = gcodeManager . getAbsolutePath ( filename )
if filepath is not None :
printer . selectFile ( filepath , False , printAfterSelect )
2013-05-18 18:21:33 +00:00
return jsonify ( SUCCESS )
2013-05-18 17:47:31 +00:00
2013-06-23 19:24:19 +00:00
@app.route ( APIBASEURL + " state " , methods = [ " GET " ] )
def apiPrinterState ( ) :
if not settings ( ) . get ( [ " api " , " enabled " ] ) :
abort ( 401 )
if not " apikey " in request . values . keys ( ) :
abort ( 401 )
if request . values [ " apikey " ] != settings ( ) . get ( [ " api " , " key " ] ) :
abort ( 403 )
currentData = printer . getCurrentData ( )
currentData . update ( {
" temperatures " : printer . getCurrentTemperatures ( )
} )
return jsonify ( currentData )
2013-01-04 12:11:00 +00:00
#~~ timelapse handling
2013-01-03 14:25:20 +00:00
@app.route ( BASEURL + " timelapse " , methods = [ " GET " ] )
2013-01-04 12:11:00 +00:00
def getTimelapseData ( ) :
2013-06-16 20:42:18 +00:00
global timelapse
2013-01-03 14:25:20 +00:00
type = " off "
additionalConfig = { }
2013-06-16 20:42:18 +00:00
if timelapse is not None and isinstance ( timelapse , octoprint . timelapse . ZTimelapse ) :
2013-01-03 14:25:20 +00:00
type = " zchange "
2013-06-16 20:42:18 +00:00
elif timelapse is not None and isinstance ( timelapse , octoprint . timelapse . TimedTimelapse ) :
2013-01-03 14:25:20 +00:00
type = " timed "
additionalConfig = {
2013-06-16 20:42:18 +00:00
" interval " : timelapse . interval ( )
2013-01-03 14:25:20 +00:00
}
2013-06-16 20:42:18 +00:00
files = octoprint . timelapse . getFinishedTimelapses ( )
2013-01-04 12:11:00 +00:00
for file in files :
file [ " url " ] = url_for ( " downloadTimelapse " , filename = file [ " name " ] )
2013-01-03 14:25:20 +00:00
return jsonify ( {
2013-06-16 19:50:50 +00:00
" type " : type ,
" config " : additionalConfig ,
" files " : files
2013-01-03 14:25:20 +00:00
} )
2013-01-04 12:11:00 +00:00
@app.route ( BASEURL + " timelapse/<filename> " , methods = [ " GET " ] )
def downloadTimelapse ( filename ) :
2013-01-30 19:56:17 +00:00
if util . isAllowedFile ( filename , set ( [ " mpg " ] ) ) :
2013-09-01 16:12:30 +00:00
path = os . path . join ( settings ( ) . getBaseFolder ( " timelapse " ) , filename )
return _streamBinaryFile ( path , mimeType = " video/mpeg " , asAttachment = True )
2013-01-04 12:11:00 +00:00
@app.route ( BASEURL + " timelapse/<filename> " , methods = [ " DELETE " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-01-04 12:11:00 +00:00
def deleteTimelapse ( filename ) :
2013-01-30 19:56:17 +00:00
if util . isAllowedFile ( filename , set ( [ " mpg " ] ) ) :
2013-01-04 12:11:00 +00:00
secure = os . path . join ( settings ( ) . getBaseFolder ( " timelapse " ) , secure_filename ( filename ) )
if os . path . exists ( secure ) :
os . remove ( secure )
return getTimelapseData ( )
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " timelapse " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-01-03 14:25:20 +00:00
def setTimelapseConfig ( ) :
2013-06-16 20:42:18 +00:00
global timelapse
2013-01-04 12:11:00 +00:00
if request . values . has_key ( " type " ) :
type = request . values [ " type " ]
2013-06-16 20:42:18 +00:00
if type in [ " zchange " , " timed " ] :
# valid timelapse type, check if there is an old one we need to stop first
if timelapse is not None :
timelapse . unload ( )
timelapse = None
2013-01-04 12:11:00 +00:00
if " zchange " == type :
2013-06-16 20:42:18 +00:00
timelapse = octoprint . timelapse . ZTimelapse ( )
2013-01-04 12:11:00 +00:00
elif " timed " == type :
interval = 10
if request . values . has_key ( " interval " ) :
try :
interval = int ( request . values [ " interval " ] )
except ValueError :
pass
2013-06-16 20:42:18 +00:00
timelapse = octoprint . timelapse . TimedTimelapse ( interval )
2013-01-04 12:11:00 +00:00
return getTimelapseData ( )
2013-01-03 14:25:20 +00:00
2013-01-01 20:04:00 +00:00
#~~ settings
@app.route ( BASEURL + " settings " , methods = [ " GET " ] )
def getSettings ( ) :
s = settings ( )
2013-02-17 21:30:34 +00:00
[ movementSpeedX , movementSpeedY , movementSpeedZ , movementSpeedE ] = s . get ( [ " printerParameters " , " movementSpeed " , [ " x " , " y " , " z " , " e " ] ] )
2013-06-29 18:20:27 +00:00
connectionOptions = getConnectionOptions ( )
2013-01-01 20:04:00 +00:00
return jsonify ( {
2013-05-18 17:47:31 +00:00
" api " : {
2013-06-24 15:48:13 +00:00
" enabled " : s . getBoolean ( [ " api " , " enabled " ] ) ,
2013-05-18 17:47:31 +00:00
" key " : s . get ( [ " api " , " key " ] )
} ,
2013-03-02 11:42:35 +00:00
" appearance " : {
" name " : s . get ( [ " appearance " , " name " ] ) ,
" color " : s . get ( [ " appearance " , " color " ] )
} ,
2013-02-17 21:30:34 +00:00
" printer " : {
" movementSpeedX " : movementSpeedX ,
" movementSpeedY " : movementSpeedY ,
" movementSpeedZ " : movementSpeedZ ,
" movementSpeedE " : movementSpeedE ,
} ,
" webcam " : {
" streamUrl " : s . get ( [ " webcam " , " stream " ] ) ,
" snapshotUrl " : s . get ( [ " webcam " , " snapshot " ] ) ,
" ffmpegPath " : s . get ( [ " webcam " , " ffmpeg " ] ) ,
2013-03-08 23:23:52 +00:00
" bitrate " : s . get ( [ " webcam " , " bitrate " ] ) ,
2013-06-18 22:42:43 +00:00
" watermark " : s . getBoolean ( [ " webcam " , " watermark " ] ) ,
2013-06-20 15:41:10 +00:00
" flipH " : s . getBoolean ( [ " webcam " , " flipH " ] ) ,
" flipV " : s . getBoolean ( [ " webcam " , " flipV " ] )
2013-02-17 21:30:34 +00:00
} ,
" feature " : {
" gcodeViewer " : s . getBoolean ( [ " feature " , " gCodeVisualizer " ] ) ,
2013-08-29 19:16:35 +00:00
" temperatureGraph " : s . getBoolean ( [ " feature " , " temperatureGraph " ] ) ,
2013-05-20 17:18:03 +00:00
" waitForStart " : s . getBoolean ( [ " feature " , " waitForStartOnConnect " ] ) ,
2013-03-16 17:25:39 +00:00
" alwaysSendChecksum " : s . getBoolean ( [ " feature " , " alwaysSendChecksum " ] ) ,
2013-05-20 17:18:03 +00:00
" sdSupport " : s . getBoolean ( [ " feature " , " sdSupport " ] )
2013-02-17 21:30:34 +00:00
} ,
2013-06-29 17:57:46 +00:00
" serial " : {
2013-06-29 18:20:27 +00:00
" port " : connectionOptions [ " portPreference " ] ,
" baudrate " : connectionOptions [ " baudratePreference " ] ,
" portOptions " : connectionOptions [ " ports " ] ,
" baudrateOptions " : connectionOptions [ " baudrates " ] ,
2013-06-30 20:18:29 +00:00
" autoconnect " : s . getBoolean ( [ " serial " , " autoconnect " ] ) ,
2013-07-04 19:26:53 +00:00
" timeoutConnection " : s . getFloat ( [ " serial " , " timeout " , " connection " ] ) ,
" timeoutDetection " : s . getFloat ( [ " serial " , " timeout " , " detection " ] ) ,
" timeoutCommunication " : s . getFloat ( [ " serial " , " timeout " , " communication " ] ) ,
2013-06-30 20:18:29 +00:00
" log " : s . getBoolean ( [ " serial " , " log " ] )
2013-06-29 17:57:46 +00:00
} ,
2013-02-17 21:30:34 +00:00
" folder " : {
" uploads " : s . getBaseFolder ( " uploads " ) ,
" timelapse " : s . getBaseFolder ( " timelapse " ) ,
" timelapseTmp " : s . getBaseFolder ( " timelapse_tmp " ) ,
" logs " : s . getBaseFolder ( " logs " )
2013-02-23 01:09:30 +00:00
} ,
" temperature " : {
2013-02-23 17:47:01 +00:00
" profiles " : s . get ( [ " temperature " , " profiles " ] )
2013-03-10 16:04:05 +00:00
} ,
" system " : {
2013-05-21 03:04:21 +00:00
" actions " : s . get ( [ " system " , " actions " ] ) ,
" events " : s . get ( [ " system " , " events " ] )
2013-08-27 19:31:12 +00:00
} ,
" terminalFilters " : s . get ( [ " terminalFilters " ] )
2013-01-01 20:04:00 +00:00
} )
@app.route ( BASEURL + " settings " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( 403 )
2013-01-01 20:04:00 +00:00
def setSettings ( ) :
2013-02-17 21:30:34 +00:00
if " application/json " in request . headers [ " Content-Type " ] :
data = request . json
s = settings ( )
2013-05-18 17:47:31 +00:00
if " api " in data . keys ( ) :
2013-06-24 15:48:13 +00:00
if " enabled " in data [ " api " ] . keys ( ) : s . set ( [ " api " , " enabled " ] , data [ " api " ] [ " enabled " ] )
2013-05-18 17:47:31 +00:00
if " key " in data [ " api " ] . keys ( ) : s . set ( [ " api " , " key " ] , data [ " api " ] [ " key " ] , True )
2013-03-02 10:56:32 +00:00
if " appearance " in data . keys ( ) :
if " name " in data [ " appearance " ] . keys ( ) : s . set ( [ " appearance " , " name " ] , data [ " appearance " ] [ " name " ] )
if " color " in data [ " appearance " ] . keys ( ) : s . set ( [ " appearance " , " color " ] , data [ " appearance " ] [ " color " ] )
2013-02-17 21:30:34 +00:00
if " printer " in data . keys ( ) :
if " movementSpeedX " in data [ " printer " ] . keys ( ) : s . setInt ( [ " printerParameters " , " movementSpeed " , " x " ] , data [ " printer " ] [ " movementSpeedX " ] )
if " movementSpeedY " in data [ " printer " ] . keys ( ) : s . setInt ( [ " printerParameters " , " movementSpeed " , " y " ] , data [ " printer " ] [ " movementSpeedY " ] )
if " movementSpeedZ " in data [ " printer " ] . keys ( ) : s . setInt ( [ " printerParameters " , " movementSpeed " , " z " ] , data [ " printer " ] [ " movementSpeedZ " ] )
if " movementSpeedE " in data [ " printer " ] . keys ( ) : s . setInt ( [ " printerParameters " , " movementSpeed " , " e " ] , data [ " printer " ] [ " movementSpeedE " ] )
if " webcam " in data . keys ( ) :
if " streamUrl " in data [ " webcam " ] . keys ( ) : s . set ( [ " webcam " , " stream " ] , data [ " webcam " ] [ " streamUrl " ] )
2013-03-08 22:24:30 +00:00
if " snapshotUrl " in data [ " webcam " ] . keys ( ) : s . set ( [ " webcam " , " snapshot " ] , data [ " webcam " ] [ " snapshotUrl " ] )
2013-03-08 23:29:07 +00:00
if " ffmpegPath " in data [ " webcam " ] . keys ( ) : s . set ( [ " webcam " , " ffmpeg " ] , data [ " webcam " ] [ " ffmpegPath " ] )
2013-02-17 21:30:34 +00:00
if " bitrate " in data [ " webcam " ] . keys ( ) : s . set ( [ " webcam " , " bitrate " ] , data [ " webcam " ] [ " bitrate " ] )
2013-03-08 23:23:52 +00:00
if " watermark " in data [ " webcam " ] . keys ( ) : s . setBoolean ( [ " webcam " , " watermark " ] , data [ " webcam " ] [ " watermark " ] )
2013-06-20 15:41:10 +00:00
if " flipH " in data [ " webcam " ] . keys ( ) : s . setBoolean ( [ " webcam " , " flipH " ] , data [ " webcam " ] [ " flipH " ] )
if " flipV " in data [ " webcam " ] . keys ( ) : s . setBoolean ( [ " webcam " , " flipV " ] , data [ " webcam " ] [ " flipV " ] )
2013-02-17 21:30:34 +00:00
if " feature " in data . keys ( ) :
if " gcodeViewer " in data [ " feature " ] . keys ( ) : s . setBoolean ( [ " feature " , " gCodeVisualizer " ] , data [ " feature " ] [ " gcodeViewer " ] )
2013-08-29 19:16:35 +00:00
if " temperatureGraph " in data [ " feature " ] . keys ( ) : s . setBoolean ( [ " feature " , " temperatureGraph " ] , data [ " feature " ] [ " temperatureGraph " ] )
2013-02-17 21:30:34 +00:00
if " waitForStart " in data [ " feature " ] . keys ( ) : s . setBoolean ( [ " feature " , " waitForStartOnConnect " ] , data [ " feature " ] [ " waitForStart " ] )
2013-03-16 00:57:05 +00:00
if " alwaysSendChecksum " in data [ " feature " ] . keys ( ) : s . setBoolean ( [ " feature " , " alwaysSendChecksum " ] , data [ " feature " ] [ " alwaysSendChecksum " ] )
2013-05-20 17:18:03 +00:00
if " sdSupport " in data [ " feature " ] . keys ( ) : s . setBoolean ( [ " feature " , " sdSupport " ] , data [ " feature " ] [ " sdSupport " ] )
2013-02-17 21:30:34 +00:00
2013-06-29 17:57:46 +00:00
if " serial " in data . keys ( ) :
if " autoconnect " in data [ " serial " ] . keys ( ) : s . setBoolean ( [ " serial " , " autoconnect " ] , data [ " serial " ] [ " autoconnect " ] )
2013-06-29 18:28:37 +00:00
if " port " in data [ " serial " ] . keys ( ) : s . set ( [ " serial " , " port " ] , data [ " serial " ] [ " port " ] )
2013-06-29 18:20:27 +00:00
if " baudrate " in data [ " serial " ] . keys ( ) : s . setInt ( [ " serial " , " baudrate " ] , data [ " serial " ] [ " baudrate " ] )
2013-07-04 19:26:53 +00:00
if " timeoutConnection " in data [ " serial " ] . keys ( ) : s . setFloat ( [ " serial " , " timeout " , " connection " ] , data [ " serial " ] [ " timeoutConnection " ] )
if " timeoutDetection " in data [ " serial " ] . keys ( ) : s . setFloat ( [ " serial " , " timeout " , " detection " ] , data [ " serial " ] [ " timeoutDetection " ] )
if " timeoutCommunication " in data [ " serial " ] . keys ( ) : s . setFloat ( [ " serial " , " timeout " , " communication " ] , data [ " serial " ] [ " timeoutCommunication " ] )
2013-06-29 17:57:46 +00:00
2013-06-30 20:18:29 +00:00
oldLog = s . getBoolean ( [ " serial " , " log " ] )
if " log " in data [ " serial " ] . keys ( ) : s . setBoolean ( [ " serial " , " log " ] , data [ " serial " ] [ " log " ] )
if oldLog and not s . getBoolean ( [ " serial " , " log " ] ) :
# disable debug logging to serial.log
logging . getLogger ( " SERIAL " ) . debug ( " Disabling serial logging " )
logging . getLogger ( " SERIAL " ) . setLevel ( logging . CRITICAL )
elif not oldLog and s . getBoolean ( [ " serial " , " log " ] ) :
# enable debug logging to serial.log
logging . getLogger ( " SERIAL " ) . setLevel ( logging . DEBUG )
logging . getLogger ( " SERIAL " ) . debug ( " Enabling serial logging " )
2013-02-17 21:30:34 +00:00
if " folder " in data . keys ( ) :
if " uploads " in data [ " folder " ] . keys ( ) : s . setBaseFolder ( " uploads " , data [ " folder " ] [ " uploads " ] )
if " timelapse " in data [ " folder " ] . keys ( ) : s . setBaseFolder ( " timelapse " , data [ " folder " ] [ " timelapse " ] )
if " timelapseTmp " in data [ " folder " ] . keys ( ) : s . setBaseFolder ( " timelapse_tmp " , data [ " folder " ] [ " timelapseTmp " ] )
if " logs " in data [ " folder " ] . keys ( ) : s . setBaseFolder ( " logs " , data [ " folder " ] [ " logs " ] )
2013-02-23 01:09:30 +00:00
if " temperature " in data . keys ( ) :
2013-02-23 17:47:01 +00:00
if " profiles " in data [ " temperature " ] . keys ( ) : s . set ( [ " temperature " , " profiles " ] , data [ " temperature " ] [ " profiles " ] )
2013-02-23 01:09:30 +00:00
2013-08-27 19:31:12 +00:00
if " terminalFilters " in data . keys ( ) :
s . set ( [ " terminalFilters " ] , data [ " terminalFilters " ] )
2013-03-10 16:04:05 +00:00
if " system " in data . keys ( ) :
if " actions " in data [ " system " ] . keys ( ) : s . set ( [ " system " , " actions " ] , data [ " system " ] [ " actions " ] )
2013-05-21 03:04:21 +00:00
if " events " in data [ " system " ] . keys ( ) : s . set ( [ " system " , " events " ] , data [ " system " ] [ " events " ] )
2013-02-17 21:30:34 +00:00
s . save ( )
2013-01-01 20:04:00 +00:00
return getSettings ( )
2013-08-10 19:59:05 +00:00
@app.route ( BASEURL + " setup " , methods = [ " POST " ] )
def firstRunSetup ( ) :
global userManager
if not settings ( ) . getBoolean ( [ " server " , " firstRun " ] ) :
abort ( 403 )
if " ac " in request . values . keys ( ) and request . values [ " ac " ] in valid_boolean_trues and \
" user " in request . values . keys ( ) and " pass1 " in request . values . keys ( ) and \
" pass2 " in request . values . keys ( ) and request . values [ " pass1 " ] == request . values [ " pass2 " ] :
# configure access control
settings ( ) . setBoolean ( [ " accessControl " , " enabled " ] , True )
userManager . addUser ( request . values [ " user " ] , request . values [ " pass1 " ] , True , [ " user " , " admin " ] )
settings ( ) . setBoolean ( [ " server " , " firstRun " ] , False )
elif " ac " in request . values . keys ( ) and not request . values [ " ac " ] in valid_boolean_trues :
# disable access control
settings ( ) . setBoolean ( [ " accessControl " , " enabled " ] , False )
settings ( ) . setBoolean ( [ " server " , " firstRun " ] , False )
userManager = None
loginManager . anonymous_user = users . DummyUser
principals . identity_loaders . appendleft ( users . dummy_identity_loader )
settings ( ) . save ( )
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
#~~ user settings
@app.route ( BASEURL + " users " , methods = [ " GET " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( 403 )
2013-04-12 21:08:14 +00:00
def getUsers ( ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
return jsonify ( { " users " : userManager . getAllUsers ( ) } )
@app.route ( BASEURL + " users " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( 403 )
2013-04-12 21:08:14 +00:00
def addUser ( ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
if " application/json " in request . headers [ " Content-Type " ] :
data = request . json
name = data [ " name " ]
password = data [ " password " ]
active = data [ " active " ]
roles = [ " user " ]
if " admin " in data . keys ( ) and data [ " admin " ] :
roles . append ( " admin " )
try :
userManager . addUser ( name , password , active , roles )
except users . UserAlreadyExists :
2013-04-13 19:40:28 +00:00
abort ( 409 )
2013-04-12 21:08:14 +00:00
return getUsers ( )
@app.route ( BASEURL + " users/<username> " , methods = [ " GET " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-12 21:08:14 +00:00
def getUser ( username ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
if current_user is not None and not current_user . is_anonymous ( ) and ( current_user . get_name ( ) == username or current_user . is_admin ( ) ) :
user = userManager . findUser ( username )
if user is not None :
return jsonify ( user . asDict ( ) )
else :
abort ( 404 )
2013-04-12 21:08:14 +00:00
else :
2013-04-13 19:40:28 +00:00
abort ( 403 )
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " users/<username> " , methods = [ " PUT " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( 403 )
2013-04-12 21:08:14 +00:00
def updateUser ( username ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
user = userManager . findUser ( username )
if user is not None :
if " application/json " in request . headers [ " Content-Type " ] :
data = request . json
# change roles
roles = [ " user " ]
if " admin " in data . keys ( ) and data [ " admin " ] :
roles . append ( " admin " )
userManager . changeUserRoles ( username , roles )
# change activation
if " active " in data . keys ( ) :
userManager . changeUserActivation ( username , data [ " active " ] )
return getUsers ( )
else :
2013-04-13 19:40:28 +00:00
abort ( 404 )
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " users/<username> " , methods = [ " DELETE " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( http_exception = 403 )
2013-04-12 21:08:14 +00:00
def removeUser ( username ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
2013-04-12 21:08:14 +00:00
try :
userManager . removeUser ( username )
return getUsers ( )
except users . UnknownUser :
2013-04-13 19:40:28 +00:00
abort ( 404 )
2013-04-12 21:08:14 +00:00
@app.route ( BASEURL + " users/<username>/password " , methods = [ " PUT " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-12 21:08:14 +00:00
def changePasswordForUser ( username ) :
2013-04-13 19:40:28 +00:00
if userManager is None :
return jsonify ( SUCCESS )
if current_user is not None and not current_user . is_anonymous ( ) and ( current_user . get_name ( ) == username or current_user . is_admin ( ) ) :
if " application/json " in request . headers [ " Content-Type " ] :
data = request . json
if " password " in data . keys ( ) and data [ " password " ] :
try :
userManager . changeUserPassword ( username , data [ " password " ] )
except users . UnknownUser :
return app . make_response ( ( " Unknown user: %s " % username , 404 , [ ] ) )
return jsonify ( SUCCESS )
else :
return app . make_response ( ( " Forbidden " , 403 , [ ] ) )
2013-04-12 21:08:14 +00:00
2013-03-10 16:04:05 +00:00
#~~ system control
@app.route ( BASEURL + " system " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-04-13 19:40:28 +00:00
@admin_permission.require ( 403 )
2013-03-10 16:04:05 +00:00
def performSystemAction ( ) :
logger = logging . getLogger ( __name__ )
if request . values . has_key ( " action " ) :
action = request . values [ " action " ]
availableActions = settings ( ) . get ( [ " system " , " actions " ] )
for availableAction in availableActions :
if availableAction [ " action " ] == action :
logger . info ( " Performing command: %s " % availableAction [ " command " ] )
try :
2013-03-10 16:17:50 +00:00
subprocess . check_output ( availableAction [ " command " ] , shell = True )
2013-03-10 16:04:05 +00:00
except subprocess . CalledProcessError , e :
logger . warn ( " Command failed with return code %i : %s " % ( e . returncode , e . message ) )
return app . make_response ( ( " Command failed with return code %i : %s " % ( e . returncode , e . message ) , 500 , [ ] ) )
except Exception , ex :
logger . exception ( " Command failed " )
return app . make_response ( ( " Command failed: %r " % ex , 500 , [ ] ) )
return jsonify ( SUCCESS )
2013-03-17 21:28:08 +00:00
#~~ Login/user handling
@app.route ( BASEURL + " login " , methods = [ " POST " ] )
def login ( ) :
2013-04-14 13:04:39 +00:00
if userManager is not None and " user " in request . values . keys ( ) and " pass " in request . values . keys ( ) :
2013-03-17 21:28:08 +00:00
username = request . values [ " user " ]
password = request . values [ " pass " ]
2013-06-25 18:09:40 +00:00
if " remember " in request . values . keys ( ) and request . values [ " remember " ] == " true " :
2013-03-18 21:27:23 +00:00
remember = True
else :
remember = False
user = userManager . findUser ( username )
if user is not None :
2013-03-19 22:06:48 +00:00
if user . check_password ( users . UserManager . createPasswordHash ( password ) ) :
2013-03-18 21:27:23 +00:00
login_user ( user , remember = remember )
2013-04-01 12:24:47 +00:00
identity_changed . send ( current_app . _get_current_object ( ) , identity = Identity ( user . get_id ( ) ) )
2013-04-13 19:40:28 +00:00
return jsonify ( user . asDict ( ) )
2013-03-18 21:27:23 +00:00
return app . make_response ( ( " User unknown or password incorrect " , 401 , [ ] ) )
elif " passive " in request . values . keys ( ) :
user = current_user
if user is not None and not user . is_anonymous ( ) :
2013-06-25 17:07:43 +00:00
identity_changed . send ( current_app . _get_current_object ( ) , identity = Identity ( user . get_id ( ) ) )
2013-04-13 19:40:28 +00:00
return jsonify ( user . asDict ( ) )
2013-08-18 16:33:21 +00:00
elif settings ( ) . getBoolean ( [ " accessControl " , " autologinLocal " ] ) \
and settings ( ) . get ( [ " accessControl " , " autologinAs " ] ) is not None \
2013-08-18 20:50:52 +00:00
and settings ( ) . get ( [ " accessControl " , " localNetworks " ] ) is not None :
2013-08-18 16:33:21 +00:00
autologinAs = settings ( ) . get ( [ " accessControl " , " autologinAs " ] )
2013-08-18 20:50:52 +00:00
localNetworks = netaddr . IPSet ( [ ] )
for ip in settings ( ) . get ( [ " accessControl " , " localNetworks " ] ) :
localNetworks . add ( ip )
2013-08-18 16:33:21 +00:00
try :
remoteAddr = util . getRemoteAddress ( request )
2013-08-18 20:50:52 +00:00
if netaddr . IPAddress ( remoteAddr ) in localNetworks :
2013-08-18 16:33:21 +00:00
user = userManager . findUser ( autologinAs )
if user is not None :
login_user ( user )
identity_changed . send ( current_app . _get_current_object ( ) , identity = Identity ( user . get_id ( ) ) )
return jsonify ( user . asDict ( ) )
except :
logger = logging . getLogger ( __name__ )
2013-08-18 20:50:52 +00:00
logger . exception ( " Could not autologin user %s for networks %r " % ( autologinAs , localNetworks ) )
2013-04-14 13:04:39 +00:00
return jsonify ( SUCCESS )
2013-03-18 21:27:23 +00:00
@app.route ( BASEURL + " logout " , methods = [ " POST " ] )
2013-08-10 19:59:05 +00:00
@restricted_access
2013-03-18 21:27:23 +00:00
def logout ( ) :
2013-04-01 12:24:47 +00:00
# Remove session keys set by Flask-Principal
2013-04-26 20:26:19 +00:00
for key in ( ' identity.id ' , ' identity.auth_type ' ) :
2013-04-01 12:24:47 +00:00
del session [ key ]
identity_changed . send ( current_app . _get_current_object ( ) , identity = AnonymousIdentity ( ) )
2013-03-18 21:27:23 +00:00
logout_user ( )
2013-04-01 12:24:47 +00:00
2013-03-18 21:27:23 +00:00
return jsonify ( SUCCESS )
2013-03-17 21:28:08 +00:00
2013-04-01 12:24:47 +00:00
@identity_loaded.connect_via ( app )
def on_identity_loaded ( sender , identity ) :
2013-04-14 13:33:11 +00:00
user = load_user ( identity . id )
2013-04-01 12:24:47 +00:00
if user is None :
2013-04-14 13:04:39 +00:00
return
2013-04-01 12:24:47 +00:00
identity . provides . add ( UserNeed ( user . get_name ( ) ) )
if user . is_user ( ) :
identity . provides . add ( RoleNeed ( " user " ) )
if user . is_admin ( ) :
identity . provides . add ( RoleNeed ( " admin " ) )
2013-03-18 21:27:23 +00:00
def load_user ( id ) :
if userManager is not None :
return userManager . findUser ( id )
2013-04-14 13:04:39 +00:00
return users . DummyUser ( )
2013-03-17 21:28:08 +00:00
2013-09-01 16:12:30 +00:00
def _streamBinaryFile ( filename , mimeType = None , asAttachment = False , attachmentFilename = None ) :
if not os . path . exists ( filename ) or not os . path . isfile ( filename ) :
return app . make_response ( ( " No such file: %s " % filename , 404 , [ ] ) )
def generator ( path ) :
with open ( path , " rb " ) as f :
while True :
data = f . read ( 4096 )
if not data :
break
yield data
headers = Headers ( )
if asAttachment :
if attachmentFilename is None :
attachmentFilename = os . path . basename ( filename )
headers . add ( " Content-Disposition " , " attachment " , filename = attachmentFilename )
if mimeType is None :
mimeType = " application/octet-stream "
return Response ( generator ( filename ) , mimetype = mimeType , headers = headers )
2013-01-01 20:04:00 +00:00
#~~ startup code
2013-03-10 13:14:37 +00:00
class Server ( ) :
2013-08-10 19:59:05 +00:00
def __init__ ( self , configfile = None , basedir = None , host = " 0.0.0.0 " , port = 5000 , debug = False , allowRoot = False ) :
2013-03-11 20:00:43 +00:00
self . _configfile = configfile
self . _basedir = basedir
self . _host = host
self . _port = port
self . _debug = debug
2013-08-10 19:59:05 +00:00
self . _allowRoot = allowRoot
2013-03-11 20:00:43 +00:00
2013-05-21 03:04:21 +00:00
2013-03-11 20:00:43 +00:00
def run ( self ) :
2013-08-10 19:59:05 +00:00
if not self . _allowRoot :
self . _checkForRoot ( )
2013-03-10 13:14:37 +00:00
# Global as I can't work out a way to get it into PrinterStateConnection
global printer
global gcodeManager
2013-03-18 21:27:23 +00:00
global userManager
2013-05-21 03:04:21 +00:00
global eventManager
2013-08-10 19:59:05 +00:00
global loginManager
2013-08-25 13:03:27 +00:00
global debug
2013-05-21 03:04:21 +00:00
2013-03-10 13:14:37 +00:00
from tornado . wsgi import WSGIContainer
from tornado . httpserver import HTTPServer
from tornado . ioloop import IOLoop
from tornado . web import Application , FallbackHandler
2013-08-25 13:03:27 +00:00
debug = self . _debug
2013-03-11 20:00:43 +00:00
# first initialize the settings singleton and make sure it uses given configfile and basedir if available
self . _initSettings ( self . _configfile , self . _basedir )
# then initialize logging
self . _initLogging ( self . _debug )
2013-03-18 21:27:23 +00:00
logger = logging . getLogger ( __name__ )
2013-03-11 20:00:43 +00:00
2013-05-26 22:56:57 +00:00
eventManager = events . eventManager ( )
2013-03-10 13:14:37 +00:00
gcodeManager = gcodefiles . GcodeManager ( )
2013-05-26 22:56:57 +00:00
printer = Printer ( gcodeManager )
# setup system and gcode command triggers
events . SystemCommandTrigger ( printer )
events . GcodeCommandTrigger ( printer )
2013-06-16 19:50:50 +00:00
if self . _debug :
events . DebugEventListener ( )
2013-03-10 13:14:37 +00:00
2013-03-18 21:27:23 +00:00
if settings ( ) . getBoolean ( [ " accessControl " , " enabled " ] ) :
userManagerName = settings ( ) . get ( [ " accessControl " , " userManager " ] )
try :
clazz = util . getClass ( userManagerName )
userManager = clazz ( )
except AttributeError , e :
logger . exception ( " Could not instantiate user manager %s , will run with accessControl disabled! " % userManagerName )
2013-03-17 21:28:08 +00:00
app . secret_key = " k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV "
2013-08-10 19:59:05 +00:00
loginManager = LoginManager ( )
loginManager . session_protection = " strong "
loginManager . user_callback = load_user
2013-03-18 21:27:23 +00:00
if userManager is None :
2013-08-10 19:59:05 +00:00
loginManager . anonymous_user = users . DummyUser
2013-04-14 13:04:39 +00:00
principals . identity_loaders . appendleft ( users . dummy_identity_loader )
2013-08-10 19:59:05 +00:00
loginManager . init_app ( app )
2013-03-17 21:28:08 +00:00
2013-03-11 20:00:43 +00:00
if self . _host is None :
self . _host = settings ( ) . get ( [ " server " , " host " ] )
if self . _port is None :
self . _port = settings ( ) . getInt ( [ " server " , " port " ] )
2013-03-18 21:27:23 +00:00
logger . info ( " Listening on http:// %s : %d " % ( self . _host , self . _port ) )
2013-03-11 20:00:43 +00:00
app . debug = self . _debug
2013-03-10 13:14:37 +00:00
2013-08-25 13:03:27 +00:00
self . _router = SockJSRouter ( self . _createSocketConnection , " /sockjs " )
2013-03-10 13:14:37 +00:00
self . _tornado_app = Application ( self . _router . urls + [
( " .* " , FallbackHandler , { " fallback " : WSGIContainer ( app ) } )
] )
self . _server = HTTPServer ( self . _tornado_app )
2013-03-11 20:00:43 +00:00
self . _server . listen ( self . _port , address = self . _host )
2013-05-21 03:04:21 +00:00
2013-05-21 20:59:13 +00:00
eventManager . fire ( " Startup " )
2013-06-26 14:19:32 +00:00
if settings ( ) . getBoolean ( [ " serial " , " autoconnect " ] ) :
2013-06-29 18:28:37 +00:00
( port , baudrate ) = settings ( ) . get ( [ " serial " , " port " ] ) , settings ( ) . getInt ( [ " serial " , " baudrate " ] )
connectionOptions = getConnectionOptions ( )
if port in connectionOptions [ " ports " ] :
printer . connect ( port , baudrate )
2013-08-18 20:50:52 +00:00
try :
IOLoop . instance ( ) . start ( )
except :
logger . fatal ( " Now that is embarrassing... Something really really went wrong here. Please report this including the stacktrace below in OctoPrint ' s bugtracker. Thanks! " )
logger . exception ( " Stacktrace follows: " )
2013-03-10 13:14:37 +00:00
2013-08-25 13:03:27 +00:00
def _createSocketConnection ( self , session ) :
2013-05-26 22:56:57 +00:00
global printer , gcodeManager , userManager , eventManager
2013-08-25 13:03:27 +00:00
return PrinterStateConnection ( printer , gcodeManager , userManager , eventManager , session )
2013-03-19 20:07:47 +00:00
2013-08-10 19:59:05 +00:00
def _checkForRoot ( self ) :
if " geteuid " in dir ( os ) and os . geteuid ( ) == 0 :
exit ( " You should not run OctoPrint as root! " )
2013-03-11 20:00:43 +00:00
def _initSettings ( self , configfile , basedir ) :
s = settings ( init = True , basedir = basedir , configfile = configfile )
def _initLogging ( self , debug ) :
2013-03-18 21:27:23 +00:00
config = {
2013-03-10 13:14:37 +00:00
" version " : 1 ,
" formatters " : {
" simple " : {
" format " : " %(asctime)s - %(name)s - %(levelname)s - %(message)s "
}
2013-02-17 16:47:06 +00:00
} ,
2013-03-10 13:14:37 +00:00
" 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 " )
2013-03-16 01:24:33 +00:00
} ,
" serialFile " : {
" class " : " logging.handlers.RotatingFileHandler " ,
" level " : " DEBUG " ,
" formatter " : " simple " ,
" maxBytes " : 2 * 1024 * 1024 , # let's limit the serial log to 2MB in size
" filename " : os . path . join ( settings ( ) . getBaseFolder ( " logs " ) , " serial.log " )
2013-03-10 13:14:37 +00:00
}
} ,
" loggers " : {
2013-06-16 20:42:18 +00:00
#"octoprint.timelapse": {
# "level": "DEBUG"
#},
#"octoprint.events": {
# "level": "DEBUG"
2013-06-30 20:18:29 +00:00
#},
" SERIAL " : {
" level " : " CRITICAL " ,
" handlers " : [ " serialFile " ] ,
" propagate " : False
}
2013-03-10 13:14:37 +00:00
} ,
" root " : {
" level " : " INFO " ,
" handlers " : [ " console " , " file " ]
2013-02-03 20:14:22 +00:00
}
}
2013-03-17 21:32:35 +00:00
if debug :
2013-07-09 20:19:58 +00:00
config [ " root " ] [ " level " ] = " DEBUG "
2013-03-17 21:32:35 +00:00
2013-03-18 21:27:23 +00:00
logging . config . dictConfig ( config )
2013-02-03 20:14:22 +00:00
2013-08-18 16:40:08 +00:00
if settings ( ) . getBoolean ( [ " serial " , " log " ] ) :
# enable debug logging to serial.log
logging . getLogger ( " SERIAL " ) . setLevel ( logging . DEBUG )
logging . getLogger ( " SERIAL " ) . debug ( " Enabling serial logging " )
2012-12-31 12:18:54 +00:00
if __name__ == " __main__ " :
2013-03-10 13:14:37 +00:00
octoprint = Server ( )
2013-03-11 20:00:43 +00:00
octoprint . run ( )