diff --git a/pom.xml b/pom.xml index 52171e7..325ae97 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 1.3.1a + 1.3.1b jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/dao/BackupFilename.java b/src/main/java/com/bwssystems/HABridge/dao/BackupFilename.java new file mode 100644 index 0000000..f1c5cdb --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/dao/BackupFilename.java @@ -0,0 +1,13 @@ +package com.bwssystems.HABridge.dao; + +public class BackupFilename { + private String filename; + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } +} diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java index 8abbc20..4a90243 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java @@ -3,12 +3,17 @@ package com.bwssystems.HABridge.dao; import java.io.IOException; import java.io.StringReader; +import java.nio.file.DirectoryStream; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -33,8 +38,16 @@ public class DeviceRepository { public DeviceRepository(String deviceDb) { super(); - repositoryPath = Paths.get(deviceDb); - String jsonContent = repositoryReader(repositoryPath); + _loadRepository(deviceDb); + } + + private void _loadRepository(String aFilePath){ + repositoryPath = Paths.get(aFilePath); + _loadRepository(repositoryPath); + } + + private void _loadRepository(Path aPath){ + String jsonContent = repositoryReader(aPath); devices = new HashMap(); if(jsonContent != null) @@ -47,7 +60,8 @@ public class DeviceRepository { put(theDevice.getId(), theDevice); } } - } + + } public List findAll() { List list = new ArrayList(devices.values()); @@ -79,6 +93,66 @@ public class DeviceRepository { log.debug("Save device: " + aDescriptor.getName()); } + public String backup(String aFilename) { + if(aFilename == null || aFilename.equalsIgnoreCase("")) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + aFilename = "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk"; + } + else + aFilename = aFilename + ".bk"; + try { + Files.copy(repositoryPath, FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + log.error("Could not backup to file: " + aFilename + " message: " + e.getMessage(), e); + } + log.debug("Backup repository: " + aFilename); + return aFilename; + } + + public String deleteBackup(String aFilename) { + log.debug("Delete backup repository: " + aFilename); + try { + Files.delete(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename)); + } catch (IOException e) { + log.error("Could not delete file: " + aFilename + " message: " + e.getMessage(), e); + } + return aFilename; + } + + public String restoreBackup(String aFilename) { + log.debug("Restore backup repository: " + aFilename); + try { + Path target = null; + if(Files.exists(repositoryPath)) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + target = FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk"); + Files.move(repositoryPath, target); + } + Files.copy(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), repositoryPath, StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + log.error("Error restoring the file: " + aFilename + " message: " + e.getMessage(), e); + return null; + } + _loadRepository(repositoryPath); + return aFilename; + } + + public List getBackups() { + List theFilenames = new ArrayList(); + Path dir = repositoryPath.getParent(); + try (DirectoryStream stream = + Files.newDirectoryStream(dir, "*.{bk}")) { + for (Path entry: stream) { + theFilenames.add(entry.getFileName().toString()); + } + } catch (IOException x) { + // IOException can never be thrown by the iteration. + // In this snippet, it can // only be thrown by newDirectoryStream. + log.error("Issue getting direcotyr listing for backups - " + x.getMessage()); + } + return theFilenames; + } + public String delete(DeviceDescriptor aDescriptor) { if (aDescriptor != null) { devices.remove(aDescriptor.getId()); diff --git a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java index 7e53de6..43bfb1f 100644 --- a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java +++ b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java @@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory; import com.bwssystems.HABridge.BridgeSettings; import com.bwssystems.HABridge.JsonTransformer; import com.bwssystems.HABridge.Version; +import com.bwssystems.HABridge.dao.BackupFilename; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceRepository; import com.bwssystems.NestBridge.NestHome; @@ -234,5 +235,66 @@ public class DeviceResource { response.status(HttpStatus.SC_OK); return nestHome.getItems(); }, new JsonTransformer()); + + get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> { + log.debug("Get backup filenames"); + response.status(HttpStatus.SC_OK); + return deviceRepository.getBackups(); + }, new JsonTransformer()); + + // http://ip_address:port/api/devices/backup/create CORS request + options(API_CONTEXT + "/backup/create", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "PUT"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); + put (API_CONTEXT + "/backup/create", "application/json", (request, response) -> { + log.debug("Create backup: " + request.body()); + BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class); + BackupFilename returnFilename = new BackupFilename(); + returnFilename.setFilename(deviceRepository.backup(aFilename.getFilename())); + return returnFilename; + }, new JsonTransformer()); + + // http://ip_address:port/api/devices/backup/delete CORS request + options(API_CONTEXT + "/backup/delete", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "POST"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); + post (API_CONTEXT + "/backup/delete", "application/json", (request, response) -> { + log.debug("Delete backup: " + request.body()); + BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class); + if(aFilename != null) + deviceRepository.deleteBackup(aFilename.getFilename()); + else + log.warn("No filename given for delete backup."); + return null; + }, new JsonTransformer()); + + // http://ip_address:port/api/devices/backup/restore CORS request + options(API_CONTEXT + "/backup/restore", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "POST"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); + post (API_CONTEXT + "/backup/restore", "application/json", (request, response) -> { + log.debug("Restore backup: " + request.body()); + BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class); + if(aFilename != null) + deviceRepository.restoreBackup(aFilename.getFilename()); + else + log.warn("No filename given for restore backup."); + return null; + }, new JsonTransformer()); } } \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 965d00b..8a34707 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -348,7 +348,7 @@ public class HueMulator { else { for(int i = 0; i < deviceButtons.length; i++) { if( i > 0) - Thread.sleep(500); + Thread.sleep(100); log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i)); myHarmony.pressButton(deviceButtons[i]); } diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index e774eda..8563ddc 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -94,7 +94,7 @@ app.factory('BridgeSettings', function() { app.service('bridgeService', function ($http, $window, BridgeSettings) { var self = this; self.BridgeSettings = BridgeSettings; - this.state = {base: window.location.origin + "/api/devices", upnpbase: window.location.origin + "/upnp/settings", huebase: window.location.origin + "/api", devices: [], device: [], error: "", showVera: false, showHarmony: false, showNest: false, habridgeversion: ""}; + this.state = {base: window.location.origin + "/api/devices", upnpbase: window.location.origin + "/upnp/settings", huebase: window.location.origin + "/api", backups: [], devices: [], device: [], error: "", showVera: false, showHarmony: false, showNest: false, habridgeversion: ""}; this.viewDevices = function () { this.state.error = ""; @@ -192,11 +192,26 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { ); }; + this.viewBackups = function () { + this.state.error = ""; + return $http.get(this.state.base + "/backup/available").then( + function (response) { + self.state.backups = response.data; + }, + function (error) { + if (error.data) { + $window.alert("Get Backups Error: " + error.data.message); + } else { + $window.alert("Get Backups Error: unknown"); + } + } + ); + }; + this.viewNestItems = function () { this.state.error = ""; if(!this.state.showNest) return; - this.state.error = ""; return $http.get(this.state.base + "/nest/items").then( function (response) { self.state.nestitems = response.data; @@ -215,7 +230,6 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { this.state.error = ""; if(!this.state.showVera) return; - this.state.error = ""; return $http.get(this.state.base + "/vera/devices").then( function (response) { self.state.veradevices = response.data; @@ -361,6 +375,58 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { } }; + this.backupDeviceDb = function (afilename) { + this.state.error = ""; + return $http.put(this.state.base + "/backup/create", { + filename: afilename + }).then( + function (response) { + self.viewBackups(); + }, + function (error) { + if (error.data) { + self.state.error = error.data.message; + } + $window.alert("Backup Device Db Error: unknown"); + } + ); + }; + + this.restoreBackup = function (afilename) { + this.state.error = ""; + return $http.post(this.state.base + "/backup/restore", { + filename: afilename + }).then( + function (response) { + self.viewBackups(); + self.viewDevices(); + }, + function (error) { + if (error.data) { + self.state.error = error.data.message; + } + $window.alert("Backup Db Restore Error: unknown"); + } + ); + }; + + this.deleteBackup = function (afilename) { + this.state.error = ""; + return $http.post(this.state.base + "/backup/delete", { + filename: afilename + }).then( + function (response) { + self.viewBackups(); + }, + function (error) { + if (error.data) { + self.state.error = error.data.message; + } + $window.alert("Backup Db Frlryr Error: unknown"); + } + ); + }; + this.deleteDevice = function (id) { this.state.error = ""; return $http.delete(this.state.base + "/" + id).then( @@ -417,9 +483,13 @@ app.controller('ViewingController', function ($scope, $location, $http, $window, $scope.BridgeSettings = bridgeService.BridgeSettings; bridgeService.viewDevices(); + bridgeService.viewBackups(); $scope.bridge = bridgeService.state; + $scope.optionalbackupname = ""; $scope.visible = false; $scope.imgUrl = "glyphicon glyphicon-plus"; + $scope.visibleBk = false; + $scope.imgBkUrl = "glyphicon glyphicon-plus"; $scope.predicate = ''; $scope.reverse = true; $scope.order = function(predicate) { @@ -443,6 +513,15 @@ app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService.editDevice(device); $location.path('/editdevice'); }; + $scope.backupDeviceDb = function (optionalbackupname) { + bridgeService.backupDeviceDb(optionalbackupname); + }; + $scope.restoreBackup = function (backupname) { + bridgeService.restoreBackup(backupname); + }; + $scope.deleteBackup = function (backupname) { + bridgeService.deleteBackup(backupname); + }; $scope.toggle = function () { $scope.visible = !$scope.visible; if($scope.visible) @@ -450,6 +529,13 @@ app.controller('ViewingController', function ($scope, $location, $http, $window, else $scope.imgUrl = "glyphicon glyphicon-plus"; }; + $scope.toggleBk = function () { + $scope.visibleBk = !$scope.visibleBk; + if($scope.visibleBk) + $scope.imgBkUrl = "glyphicon glyphicon-minus"; + else + $scope.imgBkUrl = "glyphicon glyphicon-plus"; + }; }); app.controller('AddingController', function ($scope, $location, $http, bridgeService, BridgeSettings) { diff --git a/src/main/resources/public/views/configuration.html b/src/main/resources/public/views/configuration.html index aaac8dc..e7f8813 100644 --- a/src/main/resources/public/views/configuration.html +++ b/src/main/resources/public/views/configuration.html @@ -134,3 +134,40 @@ +
+
+

Bridge Device DB Backup

+
+
+
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + +
FilenameActions
{{backup}} + + +
+
+
+ \ No newline at end of file