2013-01-01 20:04:00 +00:00
|
|
|
# coding=utf-8
|
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
|
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
|
|
|
|
|
|
|
import ConfigParser
|
|
|
|
import sys
|
|
|
|
import os
|
2013-01-20 21:44:11 +00:00
|
|
|
import yaml
|
2013-02-17 21:30:34 +00:00
|
|
|
import logging
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-01-18 22:23:50 +00:00
|
|
|
APPNAME="OctoPrint"
|
2013-01-01 20:04:00 +00:00
|
|
|
|
|
|
|
instance = None
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
def settings(init=False, configfile=None, basedir=None):
|
2013-01-01 20:04:00 +00:00
|
|
|
global instance
|
|
|
|
if instance is None:
|
2013-03-11 20:00:43 +00:00
|
|
|
if init:
|
|
|
|
instance = Settings(configfile, basedir)
|
|
|
|
else:
|
|
|
|
raise ValueError("Settings not initialized yet")
|
2013-01-01 20:04:00 +00:00
|
|
|
return instance
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
default_settings = {
|
2013-01-01 20:04:00 +00:00
|
|
|
"serial": {
|
|
|
|
"port": None,
|
|
|
|
"baudrate": None
|
|
|
|
},
|
|
|
|
"server": {
|
|
|
|
"host": "0.0.0.0",
|
|
|
|
"port": 5000
|
2013-01-02 23:39:17 +00:00
|
|
|
},
|
|
|
|
"webcam": {
|
2013-01-03 14:25:20 +00:00
|
|
|
"stream": None,
|
2013-01-03 20:38:46 +00:00
|
|
|
"snapshot": None,
|
2013-01-27 10:12:28 +00:00
|
|
|
"ffmpeg": None,
|
2013-03-08 23:23:52 +00:00
|
|
|
"bitrate": "5000k",
|
|
|
|
"watermark": True
|
2013-01-03 14:25:20 +00:00
|
|
|
},
|
2013-03-11 20:00:43 +00:00
|
|
|
"feature": {
|
|
|
|
"gCodeVisualizer": True,
|
2013-05-20 17:18:03 +00:00
|
|
|
"waitForStartOnConnect": False,
|
2013-03-29 21:17:44 +00:00
|
|
|
"waitForWaitOnConnect": False,
|
2013-03-16 17:25:39 +00:00
|
|
|
"alwaysSendChecksum": False,
|
2013-05-23 20:53:34 +00:00
|
|
|
"resetLineNumbersWithPrefixedN": False,
|
2013-06-09 16:04:25 +00:00
|
|
|
"sdSupport": True
|
2013-03-11 20:00:43 +00:00
|
|
|
},
|
2013-01-03 14:25:20 +00:00
|
|
|
"folder": {
|
|
|
|
"uploads": None,
|
2013-01-03 21:58:47 +00:00
|
|
|
"timelapse": None,
|
2013-02-17 16:47:06 +00:00
|
|
|
"timelapse_tmp": None,
|
2013-05-20 17:18:03 +00:00
|
|
|
"logs": None,
|
|
|
|
"virtualSd": None
|
2013-01-12 23:58:54 +00:00
|
|
|
},
|
2013-03-11 20:00:43 +00:00
|
|
|
"temperature": {
|
|
|
|
"profiles":
|
|
|
|
[
|
|
|
|
{"name": "ABS", "extruder" : 210, "bed" : 100 },
|
|
|
|
{"name": "PLA", "extruder" : 180, "bed" : 60 }
|
|
|
|
]
|
2013-03-02 11:42:35 +00:00
|
|
|
},
|
2013-02-16 19:28:09 +00:00
|
|
|
"printerParameters": {
|
|
|
|
"movementSpeed": {
|
|
|
|
"x": 6000,
|
|
|
|
"y": 6000,
|
|
|
|
"z": 200,
|
|
|
|
"e": 300
|
|
|
|
}
|
2013-03-10 16:04:05 +00:00
|
|
|
},
|
2013-03-11 20:00:43 +00:00
|
|
|
"appearance": {
|
|
|
|
"name": "",
|
|
|
|
"color": "default"
|
2013-03-10 16:04:05 +00:00
|
|
|
},
|
2013-03-11 20:00:43 +00:00
|
|
|
"controls": [],
|
2013-03-10 16:04:05 +00:00
|
|
|
"system": {
|
|
|
|
"actions": []
|
2013-03-18 21:27:23 +00:00
|
|
|
},
|
|
|
|
"accessControl": {
|
|
|
|
"enabled": False,
|
|
|
|
"userManager": "octoprint.users.FilebasedUserManager",
|
|
|
|
"userfile": None
|
2013-02-16 19:28:09 +00:00
|
|
|
}
|
2013-03-11 20:00:43 +00:00
|
|
|
}
|
2013-01-20 21:44:11 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
valid_boolean_trues = ["true", "yes", "y", "1"]
|
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
class Settings(object):
|
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
def __init__(self, configfile=None, basedir=None):
|
2013-02-17 21:30:34 +00:00
|
|
|
self._logger = logging.getLogger(__name__)
|
|
|
|
|
2013-01-01 20:04:00 +00:00
|
|
|
self.settings_dir = None
|
|
|
|
|
|
|
|
self._config = None
|
2013-01-20 21:44:11 +00:00
|
|
|
self._dirty = False
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
self._init_settings_dir(basedir)
|
2013-03-16 00:48:28 +00:00
|
|
|
|
|
|
|
if configfile is not None:
|
|
|
|
self._configfile = configfile
|
|
|
|
else:
|
|
|
|
self._configfile = os.path.join(self.settings_dir, "config.yaml")
|
|
|
|
self.load()
|
2013-01-18 22:23:50 +00:00
|
|
|
|
2013-03-11 20:00:43 +00:00
|
|
|
def _init_settings_dir(self, basedir):
|
|
|
|
if basedir is not None:
|
|
|
|
self.settings_dir = basedir
|
|
|
|
else:
|
|
|
|
self.settings_dir = _resolveSettingsDir(APPNAME)
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
def _getDefaultFolder(self, type):
|
|
|
|
folder = default_settings["folder"][type]
|
|
|
|
if folder is None:
|
|
|
|
folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep))
|
|
|
|
return folder
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
#~~ load and save
|
|
|
|
|
2013-03-16 00:48:28 +00:00
|
|
|
def load(self):
|
|
|
|
if os.path.exists(self._configfile) and os.path.isfile(self._configfile):
|
|
|
|
with open(self._configfile, "r") as f:
|
2013-01-20 21:44:11 +00:00
|
|
|
self._config = yaml.safe_load(f)
|
|
|
|
else:
|
|
|
|
self._config = {}
|
2013-01-01 20:04:00 +00:00
|
|
|
|
|
|
|
def save(self, force=False):
|
2013-01-20 21:44:11 +00:00
|
|
|
if not self._dirty and not force:
|
2013-01-01 20:04:00 +00:00
|
|
|
return
|
|
|
|
|
2013-03-16 00:48:28 +00:00
|
|
|
with open(self._configfile, "wb") as configFile:
|
2013-01-20 21:44:11 +00:00
|
|
|
yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True)
|
|
|
|
self._dirty = False
|
2013-01-03 14:25:20 +00:00
|
|
|
self.load()
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
#~~ getter
|
2013-01-27 10:12:28 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
def get(self, path):
|
|
|
|
if len(path) == 0:
|
2013-01-01 20:04:00 +00:00
|
|
|
return None
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
config = self._config
|
|
|
|
defaults = default_settings
|
|
|
|
|
|
|
|
while len(path) > 1:
|
|
|
|
key = path.pop(0)
|
|
|
|
if key in config.keys() and key in defaults.keys():
|
|
|
|
config = config[key]
|
|
|
|
defaults = defaults[key]
|
|
|
|
elif key in defaults.keys():
|
|
|
|
config = {}
|
|
|
|
defaults = defaults[key]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
k = path.pop(0)
|
|
|
|
if not isinstance(k, (list, tuple)):
|
|
|
|
keys = [k]
|
|
|
|
else:
|
|
|
|
keys = k
|
|
|
|
|
|
|
|
results = []
|
|
|
|
for key in keys:
|
|
|
|
if key in config.keys():
|
|
|
|
results.append(config[key])
|
|
|
|
elif key in defaults:
|
|
|
|
results.append(defaults[key])
|
|
|
|
else:
|
|
|
|
results.append(None)
|
|
|
|
|
|
|
|
if not isinstance(k, (list, tuple)):
|
|
|
|
return results.pop()
|
|
|
|
else:
|
|
|
|
return results
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
def getInt(self, path):
|
|
|
|
value = self.get(path)
|
2013-01-01 20:04:00 +00:00
|
|
|
if value is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return int(value)
|
|
|
|
except ValueError:
|
2013-02-17 21:30:34 +00:00
|
|
|
self._logger.warn("Could not convert %r to a valid integer when getting option %r" % (value, path))
|
2013-01-01 20:04:00 +00:00
|
|
|
return None
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
def getBoolean(self, path):
|
|
|
|
value = self.get(path)
|
2013-01-12 23:58:54 +00:00
|
|
|
if value is None:
|
|
|
|
return None
|
|
|
|
if isinstance(value, bool):
|
|
|
|
return value
|
2013-02-16 19:28:09 +00:00
|
|
|
return value.lower() in valid_boolean_trues
|
2013-01-12 23:58:54 +00:00
|
|
|
|
2013-01-03 14:25:20 +00:00
|
|
|
def getBaseFolder(self, type):
|
2013-02-17 21:30:34 +00:00
|
|
|
if type not in default_settings["folder"].keys():
|
2013-01-03 14:25:20 +00:00
|
|
|
return None
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
folder = self.get(["folder", type])
|
2013-01-03 14:25:20 +00:00
|
|
|
if folder is None:
|
2013-02-17 21:30:34 +00:00
|
|
|
folder = self._getDefaultFolder(type)
|
2013-01-03 14:25:20 +00:00
|
|
|
|
|
|
|
if not os.path.isdir(folder):
|
|
|
|
os.makedirs(folder)
|
|
|
|
|
|
|
|
return folder
|
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
#~~ setter
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
def set(self, path, value, force=False):
|
2013-02-16 19:28:09 +00:00
|
|
|
if len(path) == 0:
|
|
|
|
return
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
config = self._config
|
|
|
|
defaults = default_settings
|
|
|
|
|
|
|
|
while len(path) > 1:
|
|
|
|
key = path.pop(0)
|
2013-02-17 21:30:34 +00:00
|
|
|
if key in config.keys() and key in defaults.keys():
|
2013-02-16 19:28:09 +00:00
|
|
|
config = config[key]
|
2013-02-17 21:30:34 +00:00
|
|
|
defaults = defaults[key]
|
2013-02-16 19:28:09 +00:00
|
|
|
elif key in defaults.keys():
|
|
|
|
config[key] = {}
|
|
|
|
config = config[key]
|
2013-02-17 21:30:34 +00:00
|
|
|
defaults = defaults[key]
|
2013-02-16 19:28:09 +00:00
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
key = path.pop(0)
|
2013-02-17 21:30:34 +00:00
|
|
|
if not force and key in defaults.keys() and key in config.keys() and defaults[key] == value:
|
|
|
|
del config[key]
|
|
|
|
self._dirty = True
|
|
|
|
elif force or (not key in config.keys() and defaults[key] != value) or (key in config.keys() and config[key] != value):
|
|
|
|
if value is None:
|
|
|
|
del config[key]
|
|
|
|
else:
|
|
|
|
config[key] = value
|
|
|
|
self._dirty = True
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
def setInt(self, path, value, force=False):
|
2013-02-16 19:28:09 +00:00
|
|
|
if value is None:
|
2013-02-17 21:30:34 +00:00
|
|
|
self.set(path, None, force)
|
2013-01-27 17:28:11 +00:00
|
|
|
|
2013-02-16 19:28:09 +00:00
|
|
|
try:
|
|
|
|
intValue = int(value)
|
|
|
|
except ValueError:
|
2013-02-17 21:30:34 +00:00
|
|
|
self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path))
|
2013-02-16 19:28:09 +00:00
|
|
|
return
|
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
self.set(path, intValue, force)
|
2013-02-16 19:28:09 +00:00
|
|
|
|
2013-02-17 21:30:34 +00:00
|
|
|
def setBoolean(self, path, value, force=False):
|
|
|
|
if value is None or isinstance(value, bool):
|
|
|
|
self.set(path, value, force)
|
2013-02-16 19:28:09 +00:00
|
|
|
elif value.lower() in valid_boolean_trues:
|
2013-02-17 21:30:34 +00:00
|
|
|
self.set(path, True, force)
|
2013-02-16 19:28:09 +00:00
|
|
|
else:
|
2013-02-17 21:30:34 +00:00
|
|
|
self.set(path, False, force)
|
|
|
|
|
|
|
|
def setBaseFolder(self, type, path, force=False):
|
|
|
|
if type not in default_settings["folder"].keys():
|
|
|
|
return None
|
|
|
|
|
|
|
|
currentPath = self.getBaseFolder(type)
|
|
|
|
defaultPath = self._getDefaultFolder(type)
|
|
|
|
if (path is None or path == defaultPath) and "folder" in self._config.keys() and type in self._config["folder"].keys():
|
|
|
|
del self._config["folder"][type]
|
|
|
|
if not self._config["folder"]:
|
|
|
|
del self._config["folder"]
|
|
|
|
self._dirty = True
|
|
|
|
elif (path != currentPath and path != defaultPath) or force:
|
|
|
|
if not "folder" in self._config.keys():
|
|
|
|
self._config["folder"] = {}
|
|
|
|
self._config["folder"][type] = path
|
|
|
|
self._dirty = True
|
2013-01-01 20:04:00 +00:00
|
|
|
|
2013-01-18 22:23:50 +00:00
|
|
|
def _resolveSettingsDir(applicationName):
|
|
|
|
# taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python
|
|
|
|
if sys.platform == "darwin":
|
|
|
|
from AppKit import NSSearchPathForDirectoriesInDomains
|
|
|
|
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
|
|
|
|
# NSApplicationSupportDirectory = 14
|
|
|
|
# NSUserDomainMask = 1
|
|
|
|
# True for expanding the tilde into a fully qualified path
|
|
|
|
return os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], applicationName)
|
|
|
|
elif sys.platform == "win32":
|
|
|
|
return os.path.join(os.environ["APPDATA"], applicationName)
|
|
|
|
else:
|
2013-02-23 01:09:30 +00:00
|
|
|
return os.path.expanduser(os.path.join("~", "." + applicationName.lower()))
|