diff --git a/README.md b/README.md index f58ea702..f8d3568f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Released](https://img.shields.io/badge/dynamic/json.svg?color=brightgreen&label=released&url=https://api.github.com/repos/dojohnso/OctoPrint-SpoolManager/releases&query=$[0].published_at)]() ![GitHub Releases (by Release)](https://img.shields.io/github/downloads/dojohnso/OctoPrint-SpoolManager/latest/total.svg) -The OctoPrint-Plugin manages all spool informations and stores it in a database. +The OctoPrint-Plugin manages all spool informations and stores it in a database. Now includes the option to store to an external Postgres or MySQL database to share across multiple instances of OctoPrint. #### *NOTE: this plugin has been abandoned by the original creator and adopted here by a new maintainer* @@ -13,7 +13,7 @@ The OctoPrint-Plugin manages all spool informations and stores it in a database. ## Tested with: -- OctoPrint 1.7.2: with Python 3.7.3 +- OctoPrint 1.9.3: with Python 3.11.5 ## Included features @@ -39,9 +39,9 @@ The OctoPrint-Plugin manages all spool informations and stores it in a database. - [X] Scan QR/Barcodes of a spool - [X] Multi Tool support - [X] Support for manual mid-print filament change +- [X] External Database Support (MySQL or Postgres) ## Planning / next features -- [ ] External Database (IN PROGRESS) - [ ] PrintJobHistory integration [PrintJobHistory-Plugin](https://github.com/dojohnso/OctoPrint-PrintJobHistory) - [ ] Capture Spool-Image - [ ] ...more planing details could be found [here](https://github.com/dojohnso/OctoPrint-SpoolManager/projects/1) @@ -58,6 +58,8 @@ The OctoPrint-Plugin manages all spool informations and stores it in a database. ![scanSpool-dialog](screenshots/scanSpool-dialog.png "ScanSpool-Dialog") +![externaldb-dialog](screenshots/externalDatabase.png "External-Database") + ## Setup Install via the bundled [Plugin Manager](http://docs.octoprint.org/en/master/bundledplugins/pluginmanager.html) or manually using this URL: diff --git a/octoprint_SpoolManager/DatabaseManager.py b/octoprint_SpoolManager/DatabaseManager.py index e934ded7..d6de614b 100644 --- a/octoprint_SpoolManager/DatabaseManager.py +++ b/octoprint_SpoolManager/DatabaseManager.py @@ -10,6 +10,7 @@ from octoprint_SpoolManager.WrappedLoggingHandler import WrappedLoggingHandler from peewee import * +from playhouse.shortcuts import model_to_dict, dict_to_model from octoprint_SpoolManager.api import Transformer from octoprint_SpoolManager.common import StringUtils @@ -74,7 +75,7 @@ def _buildDatabaseConnection(self): databaseType = self._databaseSettings.type databaseName = self._databaseSettings.name host = self._databaseSettings.host - port = self._databaseSettings.port + port = int(self._databaseSettings.port) user = self._databaseSettings.user password = self._databaseSettings.password if ("postgres" == databaseType): @@ -111,8 +112,8 @@ def _createOrUpgradeSchemeIfNecessary(self): schemeVersionFromDatabaseModel = None schemeVersionFromDatabase = None try: - cursor = self.db.execute_sql('select "value" from "spo_pluginmetadatamodel" where key="'+PluginMetaDataModel.KEY_DATABASE_SCHEME_VERSION+'";') - result = cursor.fetchone() + cursor = PluginMetaDataModel.get(PluginMetaDataModel.key == PluginMetaDataModel.KEY_DATABASE_SCHEME_VERSION) + result = cursor.value if (result != None): schemeVersionFromDatabase = int(result[0]) self._logger.info("Current databasescheme: " + str(schemeVersionFromDatabase)) @@ -602,6 +603,10 @@ def initDatabase(self, databaseSettings, sendMessageToClient): existsDatabaseFile = str(os.path.exists(self._databaseSettings.fileLocation)) self._logger.info("Databasefile '" +self._databaseSettings.fileLocation+ "' exists: " + existsDatabaseFile) + if (existsDatabaseFile == False): + self._createDatabase(FORCE_CREATE_TABLES) + self.closeDatabase() + import logging logger = logging.getLogger('peewee') # we need only the single logger without parent @@ -637,8 +642,8 @@ def testDatabaseConnection(self, databaseSettings = None): backupCurrentDatabaseSettings = self._databaseSettings self._databaseSettings = databaseSettings - succesfull = self.connectoToDatabase() - if (succesfull == False): + succesful = self.connectoToDatabase() + if (succesful == False): result = self.getCurrentErrorMessageDict() finally: try: @@ -663,7 +668,7 @@ def connectoToDatabase(self, withMetaCheck=False, sendErrorPopUp=True) : # build connection try: if (self.sqlLoggingEnabled): - self._logger.info("Databaseconnection with...") + self._logger.info("Database connection with...") self._logger.info(self._databaseSettings) self._database = self._buildDatabaseConnection() @@ -673,7 +678,7 @@ def connectoToDatabase(self, withMetaCheck=False, sendErrorPopUp=True) : self._database.connect() if (self.sqlLoggingEnabled): - self._logger.info("Database connection succesful. Checking Scheme versions") + self._logger.info("Database connection successful. Checking Scheme versions") # TODO do I realy need to check the meta-infos in the connect function # schemeVersionFromPlugin = str(CURRENT_DATABASE_SCHEME_VERSION) # schemeVersionFromDatabaseModel = str(PluginMetaDataModel.get(PluginMetaDataModel.key == PluginMetaDataModel.KEY_DATABASE_SCHEME_VERSION).value) @@ -720,34 +725,38 @@ def showSQLLogging(self, enabled): def backupDatabaseFile(self): - if (os.path.exists(self._databaseSettings.fileLocation)): - self._logger.info("Starting database backup") - now = datetime.datetime.now() - currentDate = now.strftime("%Y%m%d-%H%M") - currentSchemeVersion = "unknown" - try: - currentSchemeVersion = PluginMetaDataModel.get( - PluginMetaDataModel.key == PluginMetaDataModel.KEY_DATABASE_SCHEME_VERSION) - if (currentSchemeVersion != None): - currentSchemeVersion = str(currentSchemeVersion.value) - except Exception as e: - self._logger.exception("Could not read databasescheme version:" + str(e)) - - backupDatabaseFilePath = self._databaseSettings.fileLocation[0:-3] + "-backup-V" + currentSchemeVersion + "-" +currentDate+".db" - # backupDatabaseFileName = "spoolmanager-backup-"+currentDate+".db" - # backupDatabaseFilePath = os.path.join(backupFolder, backupDatabaseFileName) - if not os.path.exists(backupDatabaseFilePath): - shutil.copy(self._databaseSettings.fileLocation, backupDatabaseFilePath) - self._logger.info("Backup of spoolmanager database created '" + backupDatabaseFilePath + "'") - else: - self._logger.warn("Backup of spoolmanager database ('" + backupDatabaseFilePath + "') is already present. No backup created.") - return backupDatabaseFilePath + if (self._databaseSettings.useExternal == True): + self._logger.info("No database backup needed, because we are using an external database.") else: - self._logger.info("No database backup needed, because there is no databasefile '"+str(self._databaseSettings.fileLocation)+"'") + if (os.path.exists(self._databaseSettings.fileLocation)): + self._logger.info("Starting database backup") + now = datetime.datetime.now() + currentDate = now.strftime("%Y%m%d-%H%M") + currentSchemeVersion = "unknown" + try: + currentSchemeVersion = PluginMetaDataModel.get( + PluginMetaDataModel.key == PluginMetaDataModel.KEY_DATABASE_SCHEME_VERSION) + if (currentSchemeVersion != None): + currentSchemeVersion = str(currentSchemeVersion.value) + except Exception as e: + self._logger.exception("Could not read databasescheme version:" + str(e)) + + backupDatabaseFilePath = self._databaseSettings.fileLocation[0:-3] + "-backup-V" + currentSchemeVersion + "-" +currentDate+".db" + # backupDatabaseFileName = "spoolmanager-backup-"+currentDate+".db" + # backupDatabaseFilePath = os.path.join(backupFolder, backupDatabaseFileName) + if not os.path.exists(backupDatabaseFilePath): + shutil.copy(self._databaseSettings.fileLocation, backupDatabaseFilePath) + self._logger.info("Backup of spoolmanager database created '" + backupDatabaseFilePath + "'") + else: + self._logger.warn("Backup of spoolmanager database ('" + backupDatabaseFilePath + "') is already present. No backup created.") + return backupDatabaseFilePath + else: + self._logger.info("No database backup needed, because there is no databasefile '"+str(self._databaseSettings.fileLocation)+"'") def reCreateDatabase(self, databaseSettings = None): self._currentErrorMessageDict = None self._logger.info("ReCreating Database") + self._logger.info(databaseSettings) backupCurrentDatabaseSettings = None if (databaseSettings != None): @@ -766,6 +775,74 @@ def reCreateDatabase(self, databaseSettings = None): if (backupCurrentDatabaseSettings != None): self._databaseSettings = backupCurrentDatabaseSettings + def copySpoolData(self, databaseSettings = None): + + loadResult = False + copySpoolCount = 0 + + backupCurrentDatabaseSettings = None + if (databaseSettings != None): + backupCurrentDatabaseSettings = self._databaseSettings + else: + # use default settings + databaseSettings = self._databaseSettings + backupCurrentDatabaseSettings = self._databaseSettings + + try: + currentDatabaseType = databaseSettings.type + currentUseExternal = databaseSettings.useExternal + + # First load meta from local sqlite database + databaseSettings.type = "sqlite" + databaseSettings.baseFolder = self._databaseSettings.baseFolder + databaseSettings.fileLocation = self._databaseSettings.fileLocation + databaseSettings.useExternal = False + self._databaseSettings = databaseSettings + + try: + self.connectoToDatabase( sendErrorPopUp=False) + allSpools = SpoolModel.select() + self.closeDatabase() + except Exception as e: + errorMessage = "local database: " + str(e) + self._logger.error("Connecting to local database not possible") + self._logger.exception(e) + try: + self.closeDatabase() + except Exception: + pass # ignore close exception + + + databaseSettings.type = currentDatabaseType + databaseSettings.useExternal = True + self._databaseSettings = databaseSettings + + try: + self.connectoToDatabase( sendErrorPopUp=False) + self._createDatabase(True) + for spool in allSpools: + spoolJson = model_to_dict(spool) + SpoolModel.insert(spoolJson).execute() + copySpoolCount = copySpoolCount + 1 + self.closeDatabase() + except Exception as e: + errorMessage = "database: " + str(e) + self._logger.error("Connecting to external database not possible") + self._logger.exception(e) + try: + self.closeDatabase() + except Exception: + pass # ignore close exception + + finally: + # restore orig. databasettings + if (backupCurrentDatabaseSettings != None): + self._databaseSettings = backupCurrentDatabaseSettings + + return { + "success": loadResult, + "copySpoolCount": copySpoolCount + } ################################################################################################ DATABASE OPERATIONS def _handleReusableConnection(self, databaseCallMethode, withReusedConnection, methodeNameForLogging, defaultReturnValue=None): @@ -820,11 +897,13 @@ def loadDatabaseMetaInformations(self, databaseSettings = None): # always read local meta data try: currentDatabaseType = databaseSettings.type + currentUseExternal = databaseSettings.useExternal # First load meta from local sqlite database databaseSettings.type = "sqlite" databaseSettings.baseFolder = self._databaseSettings.baseFolder databaseSettings.fileLocation = self._databaseSettings.fileLocation + databaseSettings.useExternal = False self._databaseSettings = databaseSettings try: self.connectoToDatabase( sendErrorPopUp=False) @@ -840,8 +919,9 @@ def loadDatabaseMetaInformations(self, databaseSettings = None): except Exception: pass # ignore close exception - # Use orign Databasetype to collect the other meta dtaa (if neeeded) + # Use orign Databasetype to collect the other meta data (if neeeded) databaseSettings.type = currentDatabaseType + databaseSettings.useExternal = currentUseExternal if (databaseSettings.useExternal == True): # External DB self._databaseSettings = databaseSettings @@ -998,6 +1078,8 @@ def databaseCallMethode(): myQuery = myQuery.order_by(SpoolModel.material.desc()) else: myQuery = myQuery.order_by(SpoolModel.material.asc()) + + self._logger.info("Quering spools: %s" % myQuery) return myQuery return self._handleReusableConnection(databaseCallMethode, withReusedConnection, "loadAllSpoolsByQuery") diff --git a/octoprint_SpoolManager/__init__.py b/octoprint_SpoolManager/__init__.py index 5299ca35..a8820076 100644 --- a/octoprint_SpoolManager/__init__.py +++ b/octoprint_SpoolManager/__init__.py @@ -743,14 +743,17 @@ def on_settings_save(self, data): if (selectedSpool != None): self.set_temp_offsets(toolIndex, selectedSpool) - # - # databaseSettings = self._buildDatabaseSettingsFromPluginSettings() - # - # self._databaseManager.assignNewDatabaseSettings(databaseSettings) - # testResult = self._databaseManager.testDatabaseConnection(databaseSettings) - # if (testResult != None): - # # TODO Send to client - # pass + # In case we are switching between internal and external storage + databaseSettings = self._buildDatabaseSettingsFromPluginSettings() + self._databaseManager.assignNewDatabaseSettings(databaseSettings) + # testResult = self._databaseManager.testDatabaseConnection(databaseSettings) + # if (testResult != None): + # # TODO Send to client + # pass + + self._sendDataToClient(dict( + action="reloadTable and sidebarSpools" + )) # to allow the frontend to trigger an update @@ -925,7 +928,8 @@ def register_custom_events(*args, **kwargs): EventBusKeys.EVENT_BUS_SPOOL_DELETED ] - + def is_blueprint_csrf_protected(self): + return True # def message_on_connect(self, comm, script_type, script_name, *args, **kwargs): # print(script_name) diff --git a/octoprint_SpoolManager/api/SpoolManagerAPI.py b/octoprint_SpoolManager/api/SpoolManagerAPI.py index 4249d277..254e98d6 100644 --- a/octoprint_SpoolManager/api/SpoolManagerAPI.py +++ b/octoprint_SpoolManager/api/SpoolManagerAPI.py @@ -140,7 +140,7 @@ def loadSelectedSpools(self): self._databaseManager.closeDatabase() if (spoolModel == None): self._logger.warning( - "Last selected Spool for Tool %d from plugin-settings not found in database. Maybe deleted in the meantime." % i) + "Last selected Spool for Tool %d from plugin-settings not found in database. Maybe deleted in the meantime." % toolIndex) spoolModelList.append(spoolModel) if (spoolModel != None): eventPayload = { @@ -635,6 +635,16 @@ def importSpoolData(self): if input_upload_path in flask.request.values: + databaseSettings = self._databaseManager.getDatabaseSettings() + backupDatabaseSettings = self._databaseManager.getDatabaseSettings() + + if (flask.request.form["externalDatabaseGroup"] == "true"): + databaseSettings.useExternal = True + else: + databaseSettings.useExternal = False + + self._databaseManager.assignNewDatabaseSettings(databaseSettings) + importMode = flask.request.form["importCSVMode"] # file was uploaded sourceLocation = flask.request.values[input_upload_path] @@ -645,7 +655,6 @@ def importSpoolData(self): shutil.copy(sourceLocation, archive.name) sourceLocation = archive.name - thread = threading.Thread(target=self._processCSVUploadAsync, args=(sourceLocation, importMode, @@ -655,6 +664,8 @@ def importSpoolData(self): thread.daemon = True thread.start() + self._databaseManager.assignNewDatabaseSettings(backupDatabaseSettings) + # targetLocation = self._cameraManager.buildSnapshotFilenameLocation(snapshotFilename, False) # os.rename(sourceLocation, targetLocation) pass @@ -721,7 +732,7 @@ def updateParsingStatus(lineNumber): successMessage = "" if (len(errorCollection) == 0): - successMessage = "All data is successful " + importModeText + " with '" + str(len(resultOfSpools)) + "' spools." + successMessage = "All data is successful " + importModeText + " with " + str(len(resultOfSpools)) + " spools." else: successMessage = "Some error(s) occurs! Maybe you need to manually rollback the database!" logger.info(successMessage) @@ -747,7 +758,7 @@ def _buildDatabaseSettingsFromJson(self, jsonData): def downloadDatabase(self): return send_file(self._databaseManager.getDatabaseSettings().fileLocation, mimetype='application/octet-stream', - attachment_filename='spoolmanager.db', + # attachment_filename='spoolmanager.db', Was throwing: TypeError: send_file() got an unexpected keyword argument 'attachment_filename' as_attachment=True) @@ -759,15 +770,28 @@ def deleteDatabase(self, databaseType): if (databaseType == "external"): jsonData = request.json databaseSettings = self._buildDatabaseSettingsFromJson(jsonData) + databaseSettings.useExternal = True self._databaseManager.reCreateDatabase(databaseSettings) + metaDataResult = self._databaseManager.loadDatabaseMetaInformations(None) return flask.jsonify({ - "result": "success" + "metadata": metaDataResult }) + + ####################################################################################### COPY DATABASE + @octoprint.plugin.BlueprintPlugin.route("/copyDatabase", methods=["POST"]) + def copyDatabase(self): + # metaDataResult = self._databaseManager.loadDatabaseMetaInformations(None) + jsonData = request.json + databaseSettings = self._buildDatabaseSettingsFromJson(jsonData) + metaDataResult = self._databaseManager.copySpoolData(databaseSettings) - + return flask.jsonify({ + "metadata": metaDataResult + }) + ####################################################################################### LOAD DATABASE METADATA @octoprint.plugin.BlueprintPlugin.route("/loadDatabaseMetaData", methods=["GET"]) def loadDatabaseMetaData(self): @@ -786,7 +810,7 @@ def testDatabaseConnection(self): jsonData = request.json databaseSettings = self._buildDatabaseSettingsFromJson(jsonData) - + # databaseId = self._getValueFromJSONOrNone("databaseId", jsonData) metaDataResult = self._databaseManager.loadDatabaseMetaInformations(databaseSettings) @@ -809,9 +833,22 @@ def confirmDatabaseConnectionProblem(self): @octoprint.plugin.BlueprintPlugin.route("/exportSpools/", methods=["GET"]) def exportSpoolsData(self, exportType): + databaseSettings = self._databaseManager.getDatabaseSettings() + backupDatabaseSettings = self._databaseManager.getDatabaseSettings() + if exportType == "CSV": + + if (flask.request.values["instance"] == "external"): + databaseSettings.useExternal = True + else: + databaseSettings.useExternal = False + + self._databaseManager.assignNewDatabaseSettings(databaseSettings) + allSpoolModels = self._databaseManager.loadAllSpoolsByQuery(None) + self._databaseManager.assignNewDatabaseSettings(backupDatabaseSettings) + now = datetime.datetime.now() currentDate = now.strftime("%Y%m%d-%H%M") fileName = "SpoolManager-" + currentDate + ".csv" diff --git a/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js b/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js index 2d295cd9..45f5ba36 100644 --- a/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js +++ b/octoprint_SpoolManager/static/js/SpoolManager-APIClient.js @@ -33,8 +33,8 @@ function SpoolManagerAPIClient(pluginId, baseUrl) { return urlContext; } - this.getExportUrl = function(exportType){ - return _addApiKeyIfNecessary("./plugin/" + this.pluginId + "/exportSpools/" + exportType); + this.getExportUrl = function(exportType, databaseInUse){ + return _addApiKeyIfNecessary("./plugin/" + this.pluginId + "/exportSpools/" + exportType + "?instance=" + databaseInUse); } this.getSampleCSVUrl = function(){ @@ -66,7 +66,6 @@ function SpoolManagerAPIClient(pluginId, baseUrl) { jsonPayload = ko.toJSON(databaseSettings) $.ajax({ - //url: API_BASEURL + "plugin/"+PLUGIN_ID+"/loadPrintJobHistory", url: this.baseUrl + "plugin/" + this.pluginId + "/testDatabaseConnection", dataType: "json", contentType: "application/json; charset=UTF-8", @@ -196,9 +195,20 @@ function SpoolManagerAPIClient(pluginId, baseUrl) { type: "POST" }).always(function( data ){ responseHandler(data) - //shoud be done by the server to make sure the server is informed countdownDialog.modal('hide'); - //countdownDialog.modal('hide'); - //countdownCircle = null; + }); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// Copy Database + this.callCopyDatabase = function(databaseSettings, responseHandler) { + jsonPayload = ko.toJSON(databaseSettings) + $.ajax({ + url: this.baseUrl + "plugin/"+this.pluginId+"/copyDatabase", + dataType: "json", + contentType: "application/json; charset=UTF-8", + data: jsonPayload, + type: "POST" + }).always(function( data ){ + responseHandler(data) }); } diff --git a/octoprint_SpoolManager/static/js/SpoolManager.js b/octoprint_SpoolManager/static/js/SpoolManager.js index 4a4335c8..e99fa56b 100644 --- a/octoprint_SpoolManager/static/js/SpoolManager.js +++ b/octoprint_SpoolManager/static/js/SpoolManager.js @@ -174,33 +174,50 @@ $(function() { externalSpoolItemCount: ko.observable(), schemeVersionFromPlugin: ko.observable(), } - self.showSuccessMessage = ko.observable(false); - self.showDatabaseErrorMessage = ko.observable(false); + self.showInternalSuccessMessage = ko.observable(false); + self.showInternalDatabaseErrorMessage = ko.observable(false); + self.showExternalSuccessMessage = ko.observable(false); + self.showExternalDatabaseErrorMessage = ko.observable(false); self.showUpdateSchemeMessage = ko.observable(false); - self.databaseErrorMessage = ko.observable(""); + self.externalDatabaseErrorMessage = ko.observable(""); + self.internalDatabaseErrorMessage = ko.observable(""); self.showLocalBusyIndicator = ko.observable(false); self.showExternalBusyIndicator = ko.observable(false); + self.databaseInUse = ko.observable("Internal"); self.resetDatabaseMessages = function(){ - self.showSuccessMessage(false); - self.showDatabaseErrorMessage(false); + self.showInternalSuccessMessage(false); + self.showInternalDatabaseErrorMessage(false); + self.showExternalSuccessMessage(false); + self.showExternalDatabaseErrorMessage(false); self.showUpdateSchemeMessage(false); - self.databaseErrorMessage(""); + self.externalDatabaseErrorMessage(""); + self.internalDatabaseErrorMessage(""); } self.handleDatabaseMetaDataResponse = function(metaDataResponse){ var metadata = metaDataResponse["metadata"]; + console.log(metadata); + if (metadata != null){ var errorMessage = metadata["errorMessage"]; if (errorMessage != null && errorMessage.length != 0){ - self.showDatabaseErrorMessage(true); - self.databaseErrorMessage(errorMessage); + if (self.pluginSettings.useExternal()) { + self.showExternalDatabaseErrorMessage(true); + self.externalDatabaseErrorMessage(errorMessage); + } + else { + self.showInternalDatabaseErrorMessage(true); + self.internalDatabaseErrorMessage(errorMessage); + } } var success = metadata["success"]; if (success != null && success == true){ - self.showSuccessMessage(true); + self.showExternalSuccessMessage(true && self.pluginSettings.useExternal()); + self.showInternalSuccessMessage(true && !self.pluginSettings.useExternal()); } else { - self.showSuccessMessage(false); + self.showExternalSuccessMessage(false && self.pluginSettings.useExternal()); + self.showInternalSuccessMessage(false && !self.pluginSettings.useExternal()); } self.databaseMetaData.localSchemeVersionFromDatabaseModel(metadata["localSchemeVersionFromDatabaseModel"]); @@ -219,6 +236,7 @@ $(function() { self.buildDatabaseSettings = function(){ var databaseSettings = { + useExternal: self.pluginSettings.useExternal(), databaseType: self.pluginSettings.databaseType(), databaseHost: self.pluginSettings.databaseHost(), databasePort: self.pluginSettings.databasePort(), @@ -230,57 +248,68 @@ $(function() { } self.testDatabaseConnection = function(){ - self.resetDatabaseMessages() - self.showExternalBusyIndicator(true); - -// TODO cleanup var databaseSettings = { -// databaseType: self.pluginSettings.databaseType(), -// databaseHost: self.pluginSettings.databaseHost(), -// databasePort: self.pluginSettings.databasePort(), -// databaseName: self.pluginSettings.databaseName(), -// databaseUser: self.pluginSettings.databaseUser(), -// databasePassword: self.pluginSettings.databasePassword(), -// } + self.showLocalBusyIndicator(self.pluginSettings.useExternal() == false); + self.showExternalBusyIndicator(self.pluginSettings.databasePassword() == true); + var databaseSettings = self.buildDatabaseSettings(); - // api-call - self.apiClient.testDatabaseConnection(databaseSettings, function(responseData){ - self.handleDatabaseMetaDataResponse(responseData); - self.showExternalBusyIndicator(false); - }); + + self.apiClient.testDatabaseConnection(databaseSettings, function(responseData) { + self.handleDatabaseMetaDataResponse(responseData); + self.showLocalBusyIndicator(false); + self.showExternalBusyIndicator(false); + }); } self.deleteDatabaseAction = function(databaseType) { - var result = confirm("Do you really want to delete all SpoolManager data?"); + var result = confirm("Do you really want to delete all SpoolManager data in the " + databaseType + " database?"); if (result == true){ -// TODO cleanup -// var databaseSettings = { -// databaseType: self.pluginSettings.databaseType(), -// databaseHost: self.pluginSettings.databaseHost(), -// databasePort: self.pluginSettings.databasePort(), -// databaseName: self.pluginSettings.databaseName(), -// databaseUser: self.pluginSettings.databaseUser(), -// databasePassword: self.pluginSettings.databasePassword(), -// } var databaseSettings = self.buildDatabaseSettings(); + databaseSettings.useExternal = databaseType == "external" + self.apiClient.callDeleteDatabase(databaseType, databaseSettings, function(responseData) { + self.handleDatabaseMetaDataResponse(responseData); self.spoolItemTableHelper.reloadItems(); }); } }; + self.copySpools = function() { + var result = confirm("Do you really want to copy all SpoolManager data from the internal database? This will replace all existing data."); + if (result == true) { + var databaseSettings = self.buildDatabaseSettings(); + self.apiClient.callCopyDatabase(databaseSettings, function(responseData) { + self.spoolItemTableHelper.reloadItems(); + self.apiClient.loadDatabaseMetaData(function(responseData) { + self.handleDatabaseMetaDataResponse(responseData); + }); + }); + + } + } + $("#spoolmanger-settings-tab").find('a[data-toggle="tab"]').on('shown', function (e) { var activatedTab = e.target.hash; // activated tab var prevTab = e.relatedTarget.hash; // previous tab + if (self.pluginSettings.useExternal() == true) { + self.databaseInUse("External") + } else { + self.databaseInUse("Internal") + } + if ("#tab-spool-Storage" == activatedTab){ self.resetDatabaseMessages() - - self.showLocalBusyIndicator(true); - self.showExternalBusyIndicator(true); + + self.showLocalBusyIndicator(self.pluginSettings.useExternal() == false); + self.showExternalBusyIndicator(self.pluginSettings.databasePassword() == true); + + var databaseSettings = self.buildDatabaseSettings(); self.apiClient.loadDatabaseMetaData(function(responseData) { self.handleDatabaseMetaDataResponse(responseData); + self.showExternalSuccessMessage(false); + self.showInternalSuccessMessage(false); self.showLocalBusyIndicator(false); self.showExternalBusyIndicator(false); }); @@ -357,13 +386,10 @@ $(function() { const origSaveSettingsFunction = self.settingsViewModel.saveData; const newSaveSettingsFunction = function confirmSpoolSelectionBeforeStartPrint(data, successCallback, setAsSending) { if (self.pluginSettings.useExternal() == true && - (self.showDatabaseErrorMessage() == true || self.showUpdateSchemeMessage() == true) + (self.showExternalDatabaseErrorMessage() == true || self.showInternalDatabaseErrorMessage() == true || + self.showUpdateSchemeMessage() == true) ){ - var check = confirm('External database will not work. Save settings anyway?'); - if (check == true) { - return origSaveSettingsFunction(data, successCallback, setAsSending); - } - return null; + return origSaveSettingsFunction(data, successCallback, setAsSending); } return origSaveSettingsFunction(data, successCallback, setAsSending); } @@ -649,6 +675,8 @@ $(function() { self.sidebarOpenSelectSpoolDialog = function(toolIndex, spoolItem){ + self.loadSpoolsForSidebar(); + /* needed for Filter-Search dropdown-menu */ $('.dropdown-menu.keep-open').click(function(e) { e.stopPropagation(); @@ -1029,7 +1057,6 @@ $(function() { self.pluginSettings.qrCodeBackgroundColor(newColorValue); }); - // self.pluginSettings.hideEmptySpoolsInSidebar.subscribe(function(newCheckedVaue){ // var payload = { // "hideEmptySpoolsInSidebar": newCheckedVaue @@ -1137,6 +1164,9 @@ $(function() { } self.onTabChange = function(next, current){ + if ("#tab_plugin_SpoolManager" == next) { + self.spoolItemTableHelper.reloadItems(); + } //alert("Next:"+next +" Current:"+current); if ("#tab_plugin_PrintJobHistory" == next){ //self.reloadTableData(); diff --git a/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 index f6dcfc27..78e90a24 100644 --- a/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 +++ b/octoprint_SpoolManager/templates/SpoolManager_dialog_csvImportStatus.jinja2 @@ -17,7 +17,7 @@ diff --git a/octoprint_SpoolManager/templates/SpoolManager_settings.jinja2 b/octoprint_SpoolManager/templates/SpoolManager_settings.jinja2 index 3577092d..6c0cc20a 100644 --- a/octoprint_SpoolManager/templates/SpoolManager_settings.jinja2 +++ b/octoprint_SpoolManager/templates/SpoolManager_settings.jinja2 @@ -257,10 +257,21 @@ FPE; 2.16
+ +
+
+
Database in Use:
+
Importing and exporting of SpoolManager data will be done against this database instance.
+
+
+

Export

- SpoolManager Database: Export all data as CSV-File + SpoolManager Database: Export all data as + CSV-File + CSV-File +
@@ -376,15 +387,20 @@ FPE; 2.16 Use local SqLite3 database -
+
+
+
+ Spool Count - + Schema Version - + Plugin Version - +
+
- -
@@ -395,29 +411,25 @@ FPE; 2.16 e.g.spoolmanager-backup-20200812-0924.db
-
- -
- -
-
+

External Database

+ +
-
- - Plugin version - +
+ Spool Count - + Schema Version - + Plugin Version - +
- -
-
-
-
- Database connection successful + +
+
+
+
+ Database connection successful +
+
+
+
@@ -263,7 +263,9 @@ {# g
#} g
- %
+ + + %
mm diff --git a/screenshots/externalDatabase.png b/screenshots/externalDatabase.png new file mode 100644 index 00000000..81ba0350 Binary files /dev/null and b/screenshots/externalDatabase.png differ diff --git a/setup.py b/setup.py index 50bc0c02..fd041967 100644 --- a/setup.py +++ b/setup.py @@ -38,9 +38,9 @@ "pillow", # since 7.0.0 no Python 2.7 Support, see https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst # "pillow >=6.2.0, <7.0.0", # since 7.0.0 no Python 2.7 Support, see https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst "qrcode", - "peewee" - # "psycopg2-binary", # postgres - driver - # "pymysql", #mysql - driver + "peewee", + "psycopg2-binary", # postgres - driver + "pymysql", #mysql - driver ] ### --------------------------------------------------------------------------------------------------------------------