Now UI and backend take data from saved metadata (if available). Metadata gets written after file upload and also on startup (for files that have not been added yet). Gcode analysis is interrupted if a printjob is started and resumed when it ends. Frontend is notified when new metadata comes available and UI triggers reload of gcode file list. Also started on implementing proper logging.
188 lines
No EOL
4.8 KiB
Python
188 lines
No EOL
4.8 KiB
Python
# 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
|
|
import yaml
|
|
|
|
APPNAME="OctoPrint"
|
|
OLD_APPNAME="PrinterWebUI"
|
|
|
|
instance = None
|
|
|
|
def settings():
|
|
global instance
|
|
if instance is None:
|
|
instance = Settings()
|
|
return instance
|
|
|
|
old_default_settings = {
|
|
"serial": {
|
|
"port": None,
|
|
"baudrate": None
|
|
},
|
|
"server": {
|
|
"host": "0.0.0.0",
|
|
"port": 5000
|
|
},
|
|
"webcam": {
|
|
"stream": None,
|
|
"snapshot": None,
|
|
"ffmpeg": None,
|
|
"bitrate": "5000k"
|
|
},
|
|
"folder": {
|
|
"uploads": None,
|
|
"timelapse": None,
|
|
"timelapse_tmp": None
|
|
},
|
|
"feature": {
|
|
},
|
|
}
|
|
|
|
default_settings = old_default_settings.copy()
|
|
default_settings.update({
|
|
"controls": []
|
|
})
|
|
|
|
class Settings(object):
|
|
|
|
def __init__(self):
|
|
self.settings_dir = None
|
|
|
|
self._config = None
|
|
self._dirty = False
|
|
|
|
self.init_settings_dir()
|
|
self.load()
|
|
|
|
def init_settings_dir(self):
|
|
self.settings_dir = _resolveSettingsDir(APPNAME)
|
|
|
|
# migration due to rename
|
|
old_settings_dir = _resolveSettingsDir(OLD_APPNAME)
|
|
if os.path.exists(old_settings_dir) and os.path.isdir(old_settings_dir) and not os.path.exists(self.settings_dir):
|
|
os.rename(old_settings_dir, self.settings_dir)
|
|
|
|
def load(self):
|
|
filename = os.path.join(self.settings_dir, "config.yaml")
|
|
oldFilename = os.path.join(self.settings_dir, "config.ini")
|
|
if os.path.exists(filename) and os.path.isfile(filename):
|
|
with open(filename, "r") as f:
|
|
self._config = yaml.safe_load(f)
|
|
elif os.path.exists(oldFilename) and os.path.isfile(oldFilename):
|
|
config = ConfigParser.ConfigParser(allow_no_value=True)
|
|
config.read(oldFilename)
|
|
self._config = {}
|
|
for section in old_default_settings.keys():
|
|
if not config.has_section(section):
|
|
continue
|
|
|
|
self._config[section] = {}
|
|
for option in old_default_settings[section].keys():
|
|
if not config.has_option(section, option):
|
|
continue
|
|
|
|
self._config[section][option] = config.get(section, option)
|
|
self._dirty = True
|
|
self.save(force=True)
|
|
os.rename(oldFilename, oldFilename + ".bck")
|
|
else:
|
|
self._config = {}
|
|
|
|
def save(self, force=False):
|
|
if not self._dirty and not force:
|
|
return
|
|
|
|
with open(os.path.join(self.settings_dir, "config.yaml"), "wb") as configFile:
|
|
yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True)
|
|
self._dirty = False
|
|
self.load()
|
|
|
|
def getObject(self, key):
|
|
if key not in default_settings.keys():
|
|
return None
|
|
|
|
if key in self._config.keys():
|
|
return self._config[key]
|
|
|
|
return default_settings[key]
|
|
|
|
def get(self, section, key):
|
|
if section not in default_settings.keys():
|
|
return None
|
|
|
|
if self._config.has_key(section) and self._config[section].has_key(key):
|
|
return self._config[section][key]
|
|
|
|
if default_settings.has_key(section) and default_settings[section].has_key(key):
|
|
return default_settings[section][key]
|
|
|
|
return None
|
|
|
|
def getInt(self, section, key):
|
|
value = self.get(section, key)
|
|
if value is None:
|
|
return None
|
|
|
|
try:
|
|
return int(value)
|
|
except ValueError:
|
|
return None
|
|
|
|
def getBoolean(self, section, key):
|
|
value = self.get(section, key)
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, bool):
|
|
return value
|
|
return value.lower() in ["true", "yes", "y", "1"]
|
|
|
|
def getBaseFolder(self, type):
|
|
if type not in old_default_settings["folder"].keys():
|
|
return None
|
|
|
|
folder = self.get("folder", type)
|
|
if folder is None:
|
|
folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep))
|
|
|
|
if not os.path.isdir(folder):
|
|
os.makedirs(folder)
|
|
|
|
return folder
|
|
|
|
def set(self, section, key, value):
|
|
if section not in default_settings.keys():
|
|
return
|
|
|
|
if self._config.has_key(section):
|
|
sectionConfig = self._config[section]
|
|
else:
|
|
sectionConfig = {}
|
|
|
|
sectionConfig[key] = value
|
|
self._config[section] = sectionConfig
|
|
self._dirty = True
|
|
|
|
def setObject(self, key, value):
|
|
if key not in default_settings.keys():
|
|
return
|
|
|
|
self._config[key] = value
|
|
self._dirty = True
|
|
|
|
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:
|
|
return os.path.expanduser(os.path.join("~", "." + applicationName.lower())) |