]> localhost Git - scsi2sd-util.git/commitdiff
Prepare for 1.0-BETA release
authorMichael McMaster <michael@codesrc.com>
Sat, 23 Feb 2019 05:48:58 +0000 (15:48 +1000)
committerMichael McMaster <michael@codesrc.com>
Fri, 6 Dec 2019 09:47:33 +0000 (19:47 +1000)
89 files changed:
doc/COPYING [deleted file]
pom.xml [changed mode: 0644->0755]
scsi2sd.io/pom.xml [new file with mode: 0755]
scsi2sd.io/scsi2sd.io.iml [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java [new file with mode: 0755]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java [new file with mode: 0644]
scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java [new file with mode: 0644]
scsi2sd.io/src/main/java/module-info.java [new file with mode: 0644]
scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java [new file with mode: 0644]
scsi2sd.ui/doc/COPYING [new file with mode: 0644]
scsi2sd.ui/doc/gplv3.txt [moved from doc/gplv3.txt with 100% similarity]
scsi2sd.ui/pom.xml [new file with mode: 0755]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag.java [moved from src/main/java/com/codesrc/scsi2sd/model/Flag.java with 50% similarity]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java [moved from src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java with 57% similarity]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java [new file with mode: 0644]
scsi2sd.ui/src/main/java/module-info.java [new file with mode: 0644]
scsi2sd.ui/src/main/resources/app.properties [new file with mode: 0644]
scsi2sd.ui/src/main/resources/css/main.css [new file with mode: 0644]
scsi2sd.ui/src/main/resources/css/splash.css [new file with mode: 0644]
scsi2sd.ui/src/main/resources/fx/about.fxml [moved from src/main/resources/fx/about.fxml with 81% similarity]
scsi2sd.ui/src/main/resources/fx/configTab.fxml [new file with mode: 0644]
scsi2sd.ui/src/main/resources/fx/debugLog.fxml [moved from src/main/resources/fx/debugLog.fxml with 64% similarity]
scsi2sd.ui/src/main/resources/fx/disksTab.fxml [new file with mode: 0644]
scsi2sd.ui/src/main/resources/fx/main.fxml [moved from src/main/resources/fx/main.fxml with 71% similarity]
scsi2sd.ui/src/main/resources/fx/statusTab.fxml [moved from src/main/resources/fx/statusTab.fxml with 67% similarity]
scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-floppy-disk.svg [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-folder-open.svg [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/icon-16.png [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/icon-256.png [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/icon-32.png [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/icon.svg [new file with mode: 0644]
scsi2sd.ui/src/main/resources/icons/scsi2sd-util.ico [new file with mode: 0644]
scsi2sd.ui/src/main/resources/img/splash.png [new file with mode: 0644]
scsi2sd.ui/src/main/resources/img/splash.svg [new file with mode: 0644]
src/main/java/com/codesrc/scsi2sd/App.java [deleted file]
src/main/java/com/codesrc/scsi2sd/AppConfiguration.java [deleted file]
src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java [deleted file]
src/main/java/com/codesrc/scsi2sd/model/BitEnum.java [deleted file]
src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java [deleted file]
src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java [deleted file]
src/main/java/com/codesrc/scsi2sd/model/Flag6.java [deleted file]
src/main/java/com/codesrc/scsi2sd/model/Speed.java [deleted file]
src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java [deleted file]
src/main/java/com/codesrc/scsi2sd/presentation/FXController.java [deleted file]
src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java [deleted file]
src/main/java/com/codesrc/scsi2sd/presentation/MainController.java [deleted file]
src/main/resources/app.properties [deleted file]
src/main/resources/context/applicationContext.xml [deleted file]
src/main/resources/fx/configTab.fxml [deleted file]
src/main/resources/fx/disksTab.fxml [deleted file]
src/main/resources/icons/codesrc-16x16.png [deleted file]
src/main/resources/icons/codesrc-32x32.png [deleted file]
src/main/resources/img/codesrc-square-135.png [deleted file]
src/main/resources/licence.txt [deleted file]
src/main/resources/logback.xml [deleted file]

diff --git a/doc/COPYING b/doc/COPYING
deleted file mode 100644 (file)
index ef7a877..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-SCSI2SD is distributed under the GNU General Public License version 3. Please
-find the license in the file gplv3.txt
-
-The additional license terms apply:
-
-////////////////////////////////////////////////////////////////////////////////////
-Code generated via STM32CubeMX:
-
-COPYRIGHT(c) 2015 STMicroelectronics
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-  1. Redistributions of source code must retain the above copyright notice,
-     this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright notice,
-     this list of conditions and the following disclaimer in the documentation
-     and/or other materials provided with the distribution.
-  3. Neither the name of STMicroelectronics nor the names of its contributors
-     may be used to endorse or promote products derived from this software
-     without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-////////////////////////////////////////////////////////////////////////////////////
-ARM CMSIS, distributed with STM32CubeMX:
-
-Copyright (c) 2009 - 2015 ARM LIMITED
-
-All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-- Neither the name of ARM nor the names of its contributors may be used
-  to endorse or promote products derived from this software without
-  specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-
-
diff --git a/pom.xml b/pom.xml
old mode 100644 (file)
new mode 100755 (executable)
index d73eda0..c65d6b4
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,11 @@
        <groupId>com.codesrc</groupId>
        <artifactId>scsi2sd-util</artifactId>
        <version>1.0-SNAPSHOT</version>
-       <packaging>jar</packaging>
+       <modules>
+               <module>scsi2sd.io</module>
+               <module>scsi2sd.ui</module>
+       </modules>
+       <packaging>pom</packaging>
 
        <name>SCSI2SD Utility</name>
        <url>http://www.codesrc.com</url>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
 
-       <build>
-               <directory>target</directory>
-               <outputDirectory>target/classes</outputDirectory>
-               <finalName>${project.artifactId}-${project.version}</finalName>
-
-               <plugins>
-                       <plugin>
-                               <groupId>org.apache.maven.plugins</groupId>
-                               <artifactId>maven-compiler-plugin</artifactId>
-                               <version>3.3</version>
-                               <configuration>
-                                       <source>1.8</source>
-                                       <target>1.8</target>
-                               </configuration>
-                       </plugin>
-                       <plugin>
-                               <groupId>com.zenjava</groupId>
-                               <artifactId>javafx-maven-plugin</artifactId>
-                               <version>8.8.3</version>
-                               <configuration>
-                                       <mainClass>com.codesrc.scsi2sd.App</mainClass>
-                                       <vendor>com.codesrc</vendor>
-                                       <needMenu>true</needMenu>
-                                       <appName>SCSI2SD Utility</appName>
-                                       <additionalAppResources>doc</additionalAppResources>
-                               </configuration>
-                               <executions>
-                                       <execution>
-                                               <!-- required before build-native -->
-                                               <id>create-jfxjar</id>
-                                               <phase>package</phase>
-                                               <goals>
-                                                       <goal>build-jar</goal>
-                                               </goals>
-                                       </execution>
-                                       <execution>
-                                               <id>create-native</id>
-                                               <phase>package</phase>
-                                               <goals>
-                                                       <goal>build-native</goal>
-                                               </goals>
-                                       </execution>
-                               </executions>
-                       </plugin>
-               </plugins>
-       </build>
-
-
-       <dependencies>
-<!--
-               <dependency>
-                       <groupId>javafx-packager</groupId>
-                       <artifactId>javafx-packager</artifactId>
-                       <version>1.8.0_20</version>
-                       <scope>system</scope>
-                       <systemPath>${java.home}/../lib/ant-javafx.jar</systemPath>
-               </dependency>
--->
-               <dependency>
-                       <groupId>org.springframework</groupId>
-                       <artifactId>spring-context</artifactId>
-                       <version>4.3.8.RELEASE</version>
-                       <exclusions>
-                               <exclusion>
-                                       <groupId>commons-logging</groupId>
-                                       <artifactId>commons-logging</artifactId>
-                               </exclusion>
-                       </exclusions>
-               </dependency>
-
-               <dependency>
-                       <groupId>org.controlsfx</groupId>
-                       <artifactId>controlsfx</artifactId>
-                       <version>8.40.12</version>
-               </dependency>
-
-               <!-- LogBack dependencies -->
-               <dependency>
-                       <groupId>ch.qos.logback</groupId>
-                       <artifactId>logback-classic</artifactId>
-                       <version>1.2.3</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.slf4j</groupId>
-                       <artifactId>jcl-over-slf4j</artifactId>
-                       <version>1.7.25</version>
-               </dependency>
-       </dependencies>
 </project>
 
diff --git a/scsi2sd.io/pom.xml b/scsi2sd.io/pom.xml
new file mode 100755 (executable)
index 0000000..684beb1
--- /dev/null
@@ -0,0 +1,179 @@
+<project
+        xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.codesrc</groupId>
+        <artifactId>scsi2sd-util</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>scsi2sd.io</artifactId>
+    <packaging>jar</packaging>
+
+    <name>scsi2sd.io</name>
+    <url>http://www.codesrc.com</url>
+    <developers>
+        <developer>
+            <name>Michael McMaster</name>
+            <email>michael@codesrc.com</email>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <directory>target</directory>
+        <outputDirectory>target/classes</outputDirectory>
+        <finalName>${project.artifactId}-${project.version}</finalName>
+
+        <resources>
+            <resource>
+                <directory>doc</directory>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <source>10</source>
+                    <target>10</target>
+                </configuration>
+            </plugin>
+
+
+            <!--<plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.1</version>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/install</outputDirectory>
+                            <overWriteReleases>false</overWriteReleases>
+                            <overWriteSnapshots>false</overWriteSnapshots>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                                                       <excludeGroupIds>org.hid4java,net.java.dev.jna</excludeGroupIds>
+                                                       <excludeScope>test</excludeScope>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>-->
+
+                       <plugin>
+                           <groupId>org.moditect</groupId>
+                           <artifactId>moditect-maven-plugin</artifactId>
+                           <version>1.0.0.Beta2</version>
+                           <executions>
+                               <execution>
+            <id>add-module-infos</id>
+            <phase>generate-resources</phase>
+            <goals>
+                <goal>add-module-info</goal>
+            </goals>
+
+            <configuration>
+                <outputDirectory>${project.build.directory}/install</outputDirectory>
+                <modules>
+                    <module>
+                        <artifact>
+            <groupId>org.hid4java</groupId>
+            <artifactId>hid4java</artifactId>
+            <version>0.5.0</version>
+                        </artifact>
+                        <moduleInfoSource>
+                            open module hid4java {
+                                requires jna;
+                                exports org.hid4java;
+                                                               exports org.hid4java.jna to jna;
+                                exports org.hid4java.event to com.codesrc.scsi2sd.io;
+
+                                                               // native libraries. Use jimage on the jlink'd module file
+                                                               // to list the relevant directories (which get
+                                                               // turned into packages)
+                                                               // (disabled and open'd entire module because the
+                                                               // hyphen in the directory names causes parse problems)
+                                                               //exports darwin;
+                                                               //exports win32-amd64;
+                                                               //exports linux-amd64;
+                                                               //exports linux-x86-64;
+                            }
+                        </moduleInfoSource>
+                    </module>
+
+                    <module>
+                        <artifact>
+                   <groupId>net.java.dev.jna</groupId>
+                   <artifactId>jna</artifactId>
+                   <version>5.5.0</version>
+                        </artifact>
+                        <moduleInfoSource>
+                                                       // Open is required to access the native library resources
+                            open module jna {
+                                exports com.sun.jna;
+                                                               requires java.logging;
+                            }
+                        </moduleInfoSource>
+                    </module>
+                </modules>
+            </configuration>
+
+                               </execution>
+                           </executions>
+                       </plugin>
+        </plugins>
+    </build>
+
+
+    <dependencies>
+               <dependency>
+                   <groupId>org.slf4j</groupId>
+               <artifactId>slf4j-api</artifactId>
+                   <version>1.8.0-beta2</version>
+               </dependency>
+
+               <dependency>
+                   <groupId>net.java.dev.jna</groupId>
+                   <artifactId>jna</artifactId>
+                   <version>5.2.0</version>
+               </dependency>
+
+        <dependency>
+            <groupId>org.hid4java</groupId>
+            <artifactId>hid4java</artifactId>
+            <version>0.5.0</version>
+                     <exclusions>
+                       <exclusion>  <!-- declare the exclusion here -->
+                         <groupId>net.java.dev.jna</groupId>
+                 <artifactId>jna</artifactId>
+                       </exclusion>
+                     </exclusions>
+
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
+
diff --git a/scsi2sd.io/scsi2sd.io.iml b/scsi2sd.io/scsi2sd.io.iml
new file mode 100644 (file)
index 0000000..129903a
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_10">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.8.0-beta2" level="project" />
+    <orderEntry type="library" name="Maven: net.java.dev.jna:jna:5.2.0" level="project" />
+    <orderEntry type="library" name="Maven: org.hid4java:hid4java:0.5.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java
new file mode 100644 (file)
index 0000000..edd93de
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+public enum Commands {
+    NONE, // Invalid
+
+    // Command content:
+    // uint8_t S2S_CFG_PING
+    // Response:
+    // S2S_CFG_STATUS
+    PING,
+
+    // Command content:
+    // uint8_t S2S_CFG_WRITEFLASH
+    // uint8_t[256] flashData
+    // uint8_t flashArray
+    // uint8_t flashRow
+    // Response:
+    // S2S_CFG_STATUS
+    WRITEFLASH,
+
+    // Command content:
+    // uint8_t S2S_CFG_READFLASH
+    // uint8_t flashArray
+    // uint8_t flashRow
+    // Response:
+    // 256 bytes of flash
+    READFLASH,
+
+    // Command content:
+    // uint8_t S2S_CFG_REBOOT
+    // Response: None.
+    REBOOT,
+
+    // Command content:
+    // uint8_t S2S_CFG_INFO
+    // Response:
+    // uint8_t[16] CSD
+    // uint8_t[16] CID
+    SDINFO,
+
+    // Command content:
+    // uint8_t S2S_CFG_SCSITEST
+    // Response:
+    // S2S_CFG_STATUS
+    // uint8_t result code (0 = passed)
+    SCSITEST,
+
+    // Command content:
+    // uint8_t S2S_CFG_DEVINFO
+    // Response:
+    // uint16_t protocol version (MSB)
+    // uint16_t firmware version (MSB)
+    // uint32_t SD capacity(MSB)
+    DEVINFO,
+
+    // Command content:
+    // uint8_t S2S_CFG_SD_WRITE
+    // uint32_t Sector Number (MSB)
+    // uint8_t[512] data
+    // Response:
+    // S2S_CFG_STATUS
+    SD_WRITE,
+
+    // Command content:
+    // uint8_t S2S_CFG_SD_READ
+    // uint32_t Sector Number (MSB)
+    // Response:
+    // 512 bytes of data
+    SD_READ,
+
+    // Command content:
+    // uint8_t S2S_CFG_DEBUG
+    // Response:
+    DEBUG,
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java
new file mode 100644 (file)
index 0000000..ef5d68b
--- /dev/null
@@ -0,0 +1,160 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import java.util.Arrays;
+
+// scsi2sd custom protocol for sending packet data over a USB HID
+// interface,
+class HidPacketProtocol {
+    static final int USBHID_LEN = 64;
+
+    // Maximum packet payload length. Must be large enough to support a SD sector
+    // + sector number
+    static final int HIDPACKET_MAX_LEN = 520;
+
+    private enum State { IDLE, PARTIAL, COMPLETE };
+    private class PacketState {
+        PacketState() {
+            this.reset();
+        }
+
+        void reset() {
+            this.state = State.IDLE;
+            this.chunk = 0;
+            this.offset = 0;
+        }
+
+        byte[] buffer = new byte[HIDPACKET_MAX_LEN];
+        State state;
+        int chunk;
+        int offset; // Current offset into buffer.
+    }
+
+    private PacketState rx = new PacketState();
+    private PacketState tx = new PacketState();
+
+    // Process data received over USB HID
+    public void receiveHIDData(byte[] bytes) {
+        if (bytes.length < 2) {
+            // Invalid. We need at least a chunk number and payload length.
+            this.rx.reset();
+            return;
+        }
+
+        int chunk = bytes[0] & 0x7F;
+        boolean isFinal = (bytes[0] & 0x80) == 0x80;
+        int payloadLen = uint8ToInt(bytes[1]);
+        if ((payloadLen > (bytes.length - 2)) || // short packet
+                (payloadLen + this.rx.offset > this.rx.buffer.length)) { // buffer overflow
+            this.rx.reset();
+            return;
+        }
+
+        if (chunk == 0) {
+            // Initial chunk
+            this.rx.reset();
+            System.arraycopy(bytes, 2, this.rx.buffer, 0, payloadLen);
+            this.rx.offset = payloadLen;
+            this.rx.state = State.PARTIAL;
+        }
+        else if ((this.rx.state == State.PARTIAL) && (chunk == this.rx.chunk + 1)) {
+            System.arraycopy(bytes, 2, this.rx.buffer, this.rx.offset, payloadLen);
+            this.rx.offset += payloadLen;
+            this.rx.chunk++;
+        }
+        else if (chunk == this.rx.chunk) {
+            // duplicated packet. ignore.
+        }
+        else {
+            // invalid. Maybe we missed some data.
+            this.rx.reset();
+        }
+
+        if ((this.rx.state == State.PARTIAL) && isFinal)
+        {
+            this.rx.state = State.COMPLETE;
+        }
+    }
+
+    public byte[] getPacket() {
+        if (this.rx.state == State.COMPLETE) {
+            byte[] result = Arrays.copyOf(this.rx.buffer, this.rx.offset);
+            this.rx.reset();
+            return result;
+        } else {
+            return null;
+        }
+    }
+
+    public void sendPacket(byte[] packet)
+    {
+        if (packet.length <= this.tx.buffer.length) {
+            this.tx.state = State.PARTIAL;
+            this.tx.chunk = 0;
+            this.tx.offset = packet.length;
+            System.arraycopy(packet, 0, this.tx.buffer, 0, packet.length);
+        }
+        else {
+            this.tx.reset();
+        }
+    }
+
+    public byte[] getHIDBytes() {
+        if ((this.tx.state != State.PARTIAL) || (this.tx.offset <= 0))
+        {
+            return null;
+        }
+
+        byte[] hidBuffer = new byte[USBHID_LEN];
+        hidBuffer[0] = intToUint8(this.tx.chunk);
+        this.tx.chunk++;
+        int payload;
+        if (this.tx.offset <= USBHID_LEN - 2)
+        {
+            hidBuffer[0] = intToUint8(hidBuffer[0] | 0x80);
+            payload = this.tx.offset;
+            this.tx.state = State.IDLE;
+        }
+        else
+        {
+            payload = USBHID_LEN - 2;
+        }
+
+        this.tx.offset -= payload;
+        hidBuffer[1] = intToUint8(payload);
+        System.arraycopy(this.tx.buffer, 0, hidBuffer, 2, payload);
+
+        // memmove
+        for (int i = payload; i < this.tx.buffer.length; ++i)
+        {
+            this.tx.buffer[i - payload] = this.tx.buffer[i];
+        }
+
+        return hidBuffer;
+    }
+
+
+    public static int uint8ToInt(byte data) {
+        return (int) data & 0xFF;
+    }
+
+    public static byte intToUint8(int data) {
+        return (byte) (data & 0xFF);
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java
new file mode 100644 (file)
index 0000000..5c1b416
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import org.hid4java.HidDevice;
+
+public class HidPacketProtocolDevice {
+
+    private HidDevice hidDevice;
+    private HidPacketProtocol protocol;
+
+    public HidPacketProtocolDevice(HidDevice hidDevice) {
+        this.hidDevice = hidDevice;
+        this.protocol = new HidPacketProtocol();
+    }
+
+    public byte[] sendHIDPacket(byte[] cmd, int responseLength)
+    {
+        assert(cmd.length <= HidPacketProtocol.HIDPACKET_MAX_LEN);
+
+        this.protocol.sendPacket(cmd);
+
+        byte[] chunk = this.protocol.getHIDBytes();
+        while (chunk != null)
+        {
+            int result = -1;
+            for (int retry = 0; retry < 10 && result <= 0; ++retry)
+            {
+                result = this.hidDevice.write(chunk, chunk.length, (byte)0x00);
+            }
+
+            if (result <= 0)
+            {
+                throw new RuntimeException("USB HID write failure: " + this.hidDevice.getLastErrorMessage());
+            }
+            chunk = this.protocol.getHIDBytes();
+        }
+
+        byte[] readBuffer = new byte[HidPacketProtocol.HIDPACKET_MAX_LEN];
+        byte[] resp = this.protocol.getPacket();
+        for (int retry = 0; retry < responseLength * 2 && resp == null; ++retry)
+        {
+            this.hidDevice.read(readBuffer); // Will block
+            this.protocol.receiveHIDData(readBuffer);
+            resp = this.protocol.getPacket();
+        }
+
+        if (resp == null)
+        {
+            throw new RuntimeException("SCSI2SD USB protocol error");
+        }
+        return resp;
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java
new file mode 100644 (file)
index 0000000..0a2569b
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Consumer;
+
+public class SimpleEvent<EventParamT> {
+    private ConcurrentLinkedQueue<Consumer<EventParamT>> observers = new ConcurrentLinkedQueue<Consumer<EventParamT>>();
+
+    public void subscribe(Consumer<EventParamT> observer) {
+        observers.add(observer);
+    }
+
+    public void unsubscribe(Consumer<EventParamT> observer) {
+        observers.remove(observer);
+    }
+
+    public void trigger(EventParamT param) {
+        for (Consumer<EventParamT> observer : observers) {
+            observer.accept(param);
+        }
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java
new file mode 100644 (file)
index 0000000..34d2118
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+/**
+ * Created by michael on 27/08/18.
+ */
+public interface UsbDevice {
+    String getHardwareDescription();
+
+    int getFirmwareVersion();
+    String getFirmwareDescription();
+    int getSdCapacity();
+
+    int getMaxDisks();
+    byte[] readBoardConfig();
+
+    byte[] readDiskConfig(int index);
+
+    void saveBoardConfig(byte[] data);
+    void saveDiskConfig(int index, byte[] data);
+    void saveCommit();
+
+    boolean supportsTerminator();
+    boolean supportsSynchronous();
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java
new file mode 100644 (file)
index 0000000..dbcc786
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+/**
+ * Created by michael on 27/08/18.
+ */
+public class UsbDeviceConnectedEvent {
+    private UsbDevice usbDevice;
+
+    public UsbDeviceConnectedEvent(UsbDevice usbDevice) {
+        this.usbDevice = usbDevice;
+    }
+
+    public UsbDevice getUsbDevice() {
+        return usbDevice;
+    }
+}
+
+
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java
new file mode 100644 (file)
index 0000000..e905404
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+
+/**
+ * Created by michael on 27/08/18.
+ */
+public class UsbDeviceDisconnectedEvent {
+    private UsbDevice usbDevice;
+
+    public UsbDeviceDisconnectedEvent(UsbDevice device) {
+        this.usbDevice = usbDevice;
+    }
+
+    public UsbDevice getUsbDevice() {
+        return usbDevice;
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java
new file mode 100644 (file)
index 0000000..45e0797
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+/**
+ * Created by michael on 27/08/18.
+ */
+public class UsbDeviceFailedEvent {
+    private String devicePath;
+    private String message;
+
+    public UsbDeviceFailedEvent(String devicePath, String message) {
+        this.devicePath = devicePath;
+        this.message = message;
+    }
+
+    public String getUsbDevicePath() {
+        return devicePath;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
+
+
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java
new file mode 100644 (file)
index 0000000..725a52f
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import java.util.List;
+
+/**
+ * Created by michael on 27/08/18.
+ */
+public interface UsbDeviceManager {
+
+    List<UsbDevice> getDevices();
+    void shutdown();
+
+    SimpleEvent<UsbDeviceConnectedEvent> getDeviceConnectedEvent();
+    SimpleEvent<UsbDeviceDisconnectedEvent> getDeviceDisconnectedEvent();
+
+    SimpleEvent<UsbDeviceFailedEvent> getDeviceFailedEvent();
+
+    static UsbDeviceManager getInstance() {
+        return UsbDeviceManagerImpl.getInstance();
+    }
+
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java
new file mode 100755 (executable)
index 0000000..7ce1af1
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import org.hid4java.*;
+import org.hid4java.event.HidServicesEvent;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+
+class UsbDeviceManagerImpl implements UsbDeviceManager, HidServicesListener {
+    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UsbDeviceManagerImpl.class.getPackageName());
+    private static UsbDeviceManagerImpl INSTANCE = new UsbDeviceManagerImpl();
+
+    private SimpleEvent<UsbDeviceConnectedEvent> deviceConnectedEvent = new SimpleEvent<>();
+    private SimpleEvent<UsbDeviceDisconnectedEvent> deviceDisconnectedEvent = new SimpleEvent<>();
+    private SimpleEvent<UsbDeviceFailedEvent> deviceFailedEvent = new SimpleEvent<>();
+
+    private final ScheduledExecutorService scheduler;
+
+
+    private HidServices hidServices;
+    private Map<HidDevice, UsbDevice> devices = new ConcurrentHashMap<>();
+
+    public static UsbDeviceManager getInstance() {
+        return INSTANCE;
+    }
+
+    public UsbDeviceManagerImpl()
+    {
+        scheduler = Executors.newScheduledThreadPool(1);
+
+        LOGGER.debug("Initialising UsbDeviceManagerImpl");
+
+        HidServicesSpecification spec = new HidServicesSpecification();
+        spec.setScanMode(ScanMode.NO_SCAN);
+
+        hidServices = HidManager.getHidServices(spec);
+        hidServices.getAttachedHidDevices().forEach(d -> this.attached(d));
+        hidServices.addHidServicesListener(this);
+
+        scheduler.scheduleWithFixedDelay(() -> this.poll(), 1, 1, TimeUnit.SECONDS);
+
+        LOGGER.debug("Initialised UsbDeviceManagerImpl");
+    }
+
+    @Override
+    public void shutdown()
+    {
+        scheduler.shutdown();
+        scheduler.shutdownNow(); // Cancel currently executing tasks
+
+        // Wait a while for tasks to respond to being cancelled
+        try {
+            if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
+                LOGGER.error("Scheduler did not terminate");
+            }
+            hidServices.shutdown();
+        }
+        catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    @Override
+    public List<UsbDevice> getDevices() {
+        return new ArrayList<>(devices.values());
+    }
+
+    private void poll() {
+        LOGGER.trace("Polling Usb Devices");
+
+        try {
+            hidServices.scan();
+        } catch (Exception e)
+        {
+            LOGGER.error("Failure scanning USB devices", e);
+        }
+
+        LOGGER.trace("Polling Complete");
+    }
+
+    public SimpleEvent<UsbDeviceConnectedEvent> getDeviceConnectedEvent() {
+        return deviceConnectedEvent;
+    }
+
+    public SimpleEvent<UsbDeviceDisconnectedEvent> getDeviceDisconnectedEvent() {
+        return deviceDisconnectedEvent;
+    }
+
+    public SimpleEvent<UsbDeviceFailedEvent> getDeviceFailedEvent() {
+        return deviceFailedEvent;
+    }
+
+    @Override
+    public void hidDeviceAttached(HidServicesEvent hidServicesEvent) {
+        try {
+            LOGGER.debug("Usb Device attached {}", hidServicesEvent.getHidDevice().getPath());
+
+            this.attached(hidServicesEvent.getHidDevice());
+        } catch (Exception e) {
+            LOGGER.error("Exception in UsbDeviceManagerImpl.hidDeviceAttached", e);
+        }
+    }
+
+    @Override
+    public void hidDeviceDetached(HidServicesEvent hidServicesEvent) {
+        try {
+            LOGGER.debug("Usb Device detached {}", hidServicesEvent.getHidDevice().getPath());
+            this.detached(hidServicesEvent.getHidDevice());
+        } catch (Exception e) {
+            LOGGER.error("Exception in UsbDeviceManagerImpl.hidDeviceDettached", e);
+        }
+
+    }
+
+    @Override
+    public void hidFailure(HidServicesEvent hidServicesEvent) {
+        LOGGER.warn("Usb Device failure {}", hidServicesEvent.toString());
+    }
+
+    private void detached(HidDevice hidDevice) {
+        UsbDevice attachedDevice = devices.get(hidDevice);
+        if (attachedDevice != null) {
+            devices.remove(attachedDevice);
+
+            LOGGER.info("Detached SCSI2SD");
+
+            this.deviceDisconnectedEvent.trigger(new UsbDeviceDisconnectedEvent(attachedDevice));
+        }
+        if (hidDevice.isOpen()) {
+            hidDevice.close();
+        }
+    }
+
+    private void attached(HidDevice hidDevice) {
+        if (devices.containsKey(hidDevice)) {
+            // Why are we getting this again ?
+            LOGGER.warn("Usb Device duplicate attach {}", hidDevice.getPath());
+
+            return;
+        }
+
+        if (!devices.isEmpty()) {
+            // UI only supports one device for now
+            return;
+        }
+
+        UsbDevice attachedDevice = null;
+
+        try {
+            if (hidDevice.getVendorId() == V6FirmwareUsbDevice.VENDOR_ID &&
+                    hidDevice.getProductId() == V6FirmwareUsbDevice.PRODUCT_ID) {
+
+                attachedDevice = new V6FirmwareUsbDevice(hidDevice);
+                this.devices.put(hidDevice, attachedDevice);
+            } else if (hidDevice.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID &&
+                    hidDevice.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID) {
+
+                if (hidDevice.getInterfaceNumber() == 0
+                        || hidDevice.getUsagePage() == (short) 0xFF00) {
+
+                    // Search for the 2nd interface.
+                    for (HidDevice dbgDev : hidServices.getAttachedHidDevices()) {
+                        if (dbgDev.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID &&
+                                dbgDev.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID &&
+                                (dbgDev.getInterfaceNumber() == 1 || hidDevice.getUsagePage() == (short) 0xFF01) &&
+                                !this.devices.containsKey(dbgDev)) {
+                            attachedDevice = new V3FirmwareUsbDevice(hidDevice, dbgDev);
+                            this.devices.put(dbgDev, attachedDevice);
+                            this.devices.put(hidDevice, attachedDevice);
+                        }
+                    }
+
+                } else if (hidDevice.getInterfaceNumber() == 1
+                        || hidDevice.getUsagePage() == (short) 0xFF01) {
+
+                    // Search for the 1st interface.
+                    for (HidDevice cfgDev : hidServices.getAttachedHidDevices()) {
+                        if (cfgDev.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID &&
+                                cfgDev.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID &&
+                                (cfgDev.getInterfaceNumber() == 0 || hidDevice.getUsagePage() == (short) 0xFF00) &&
+                                !this.devices.containsKey(cfgDev)) {
+                            attachedDevice = new V3FirmwareUsbDevice(cfgDev, hidDevice);
+                            this.devices.put(cfgDev, attachedDevice);
+                            this.devices.put(hidDevice, attachedDevice);
+                        }
+                    }
+                }
+            }
+
+            if (attachedDevice != null) {
+                LOGGER.info("Attached SCSI2SD.\n\tHardware: {}\n\tFirmware: {}",
+                        attachedDevice.getHardwareDescription(),
+                        attachedDevice.getFirmwareDescription());
+
+                deviceConnectedEvent.trigger((new UsbDeviceConnectedEvent(attachedDevice)));
+            }
+        } catch (RuntimeException e) {
+            deviceFailedEvent.trigger(new UsbDeviceFailedEvent(hidDevice.getPath(), e.getMessage()));
+            throw e;
+        }
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java
new file mode 100644 (file)
index 0000000..02700f8
--- /dev/null
@@ -0,0 +1,261 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import org.hid4java.HidDevice;
+import org.hid4java.HidServices;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class V3FirmwareUsbDevice implements UsbDevice {
+    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(V3FirmwareUsbDevice.class.getPackageName());
+
+    public static final short VENDOR_ID = 0x04B4; // Cypress
+    public static final short PRODUCT_ID = 0x1337; // SCSI2SD
+
+    private static final int MAX_SCSI_TARGETS = 4;
+    private static final int SCSI_CONFIG_ARRAY = 1;
+    private static final int SCSI_CONFIG_ROWS = 16;
+
+// 256 bytes data, 32 bytes ECC
+    private static final int SCSI_CONFIG_ROW_SIZE = 256;
+    private static final int SCSI_CONFIG_ROW_ECC = 288;
+    private static final int SCSI_CONFIG_BOARD_ROW = 187;
+    private static final int SCSI_CONFIG_0_ROW = 188;
+    private static final int SCSI_CONFIG_1_ROW = 204;
+    private static final int SCSI_CONFIG_2_ROW = 220;
+    private static final int SCSI_CONFIG_3_ROW = 236;
+    private static final int SCSI_CONFIG_4_ROW = 124;
+    private static final int SCSI_CONFIG_5_ROW = 140;
+    private static final int SCSI_CONFIG_6_ROW = 156;
+
+    private HidDevice configHidDevice;
+    private HidDevice debugHidDevice;
+    private HidPacketProtocolDevice protocolDevice;
+
+    private int firmwareVersion;
+    private int sDCapacity;
+
+    private byte[] boardConfigSector;
+    private Map<Integer, byte[]> diskConfigSector = new HashMap<>();
+
+
+    public V3FirmwareUsbDevice(HidDevice configHidDevice, HidDevice debugHidDevice) {
+        this.configHidDevice = configHidDevice;
+        this.debugHidDevice = debugHidDevice;
+        this.protocolDevice = new HidPacketProtocolDevice(configHidDevice);
+
+        if (!configHidDevice.isOpen())
+        {
+            if (!configHidDevice.open()) {
+                LOGGER.error("Failed to open USB device {}\n{}", configHidDevice.getPath(), configHidDevice.getLastErrorMessage());
+                throw new RuntimeException("Failed to open USB device.");
+            }
+        }
+        if (!debugHidDevice.isOpen())
+        {
+            if (!debugHidDevice.open()) {
+                LOGGER.error("Failed to open USB device {}\n{}", debugHidDevice.getPath(), debugHidDevice.getLastErrorMessage());
+                throw new RuntimeException("Failed to open USB device.");
+            }
+        }
+        this.readDebugData();
+    }
+
+    @Override
+    public String getHardwareDescription() {
+        switch (this.configHidDevice.getReleaseNumber()) {
+            case 0x3001:
+                return "3.5\" SCSI2SD V3";
+                //info.version = "V3.0";
+                //info.firmwareName = "SCSI2SD-V3.cyacd";
+            case 0x3002:
+                return "3.5\" SCSI2SD V4.1/V4.2/V5.0 or 2.5\" SCSI2SD for Apple Powerbook";
+                //info.version = "V4.1/V4.2/V5.0";
+                //info.firmwareName = "SCSI2SD-V4.cyacd";
+            case 0x3003:
+                return "3.5\" SCSI2SD V5.1";
+                //info.version = "V5.1";
+                //info.firmwareName = "SCSI2SD-V51.cyacd";
+            case 0x0055:
+                return "External DB25 SCSI2SD";
+                //info.version = "V5.5";
+                //info.firmwareName = "SCSI2SD-V55.cyacd";
+
+        }
+        return "Unknown";
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return this.firmwareVersion;
+    }
+
+    @Override
+    public String getFirmwareDescription() {
+        StringBuilder out = new StringBuilder();
+        out.append(this.firmwareVersion >> 8)
+                .append('.')
+                .append((this.firmwareVersion & 0xF0) >> 4);
+
+        int rev = this.firmwareVersion & 0xF;
+        if (rev != 0)
+        {
+            out.append(".").append(rev);
+        }
+        return out.toString();
+    }
+
+    @Override
+    public int getSdCapacity() {
+        return this.sDCapacity;
+    }
+
+    @Override
+    public boolean supportsTerminator() {
+        switch (this.configHidDevice.getReleaseNumber()) {
+            case 0x3001:
+                return false; //"3.5\" SCSI2SD V3";
+            case 0x3002:
+                return false; //"3.5\" SCSI2SD V4.1/V4.2/V5.0 or 2.5\" SCSI2SD for Apple Powerbook";
+            case 0x3003:
+                return true; //"3.5\" SCSI2SD V5.1";
+            case 0x0055:
+                return false; //"External DB25 SCSI2SD";
+        }
+        return false;
+    }
+
+    @Override
+    public boolean supportsSynchronous() {
+        return false;
+    }
+
+
+    private void readDebugData()
+    {
+        byte[] data = new byte[64];
+        data[0] = 0; // report ID
+        this.debugHidDevice.read(data, 5000);
+
+        this.firmwareVersion = (data[62] << 8) | data[63];
+        this.sDCapacity =
+                (HidPacketProtocol.uint8ToInt(data[58]) << 24) |
+                        (HidPacketProtocol.uint8ToInt(data[59]) << 16) |
+                        (HidPacketProtocol.uint8ToInt(data[60]) << 8) |
+                        (HidPacketProtocol.uint8ToInt(data[61]));
+    }
+
+    @Override
+    public int getMaxDisks()
+    {
+        return MAX_SCSI_TARGETS;
+    }
+
+    @Override
+    public byte[] readBoardConfig()
+    {
+        if (boardConfigSector == null) {
+            boardConfigSector = readFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW);
+        }
+
+        return Arrays.copyOfRange(boardConfigSector, 0, 128);
+    }
+
+    @Override
+    public void saveBoardConfig(byte[] data) {
+        if (boardConfigSector == null) {
+            boardConfigSector = readFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW);
+        }
+
+        System.arraycopy(data, 0, boardConfigSector, 0, 128);
+
+    }
+
+    @Override
+    public byte[] readDiskConfig(int index)
+    {
+        int flashRow = this.getFlashRow(index);
+
+        var data = this.diskConfigSector.computeIfAbsent(flashRow, (k) -> this.readFlash(SCSI_CONFIG_ARRAY, flashRow));
+        return Arrays.copyOfRange(data, 0, 128);
+    }
+
+    @Override
+    public void saveDiskConfig(int index, byte[] data) {
+        int flashRow = this.getFlashRow(index);
+
+        var cachedData = this.diskConfigSector.computeIfAbsent(flashRow, (k) -> this.readFlash(SCSI_CONFIG_ARRAY, flashRow));
+        System.arraycopy(data, 0, cachedData, 0, 128);
+    }
+
+
+    @Override
+    public void saveCommit() {
+        if (boardConfigSector != null) {
+            writeFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW, boardConfigSector);
+        }
+
+        for (int i = 0; i < MAX_SCSI_TARGETS; ++i) {
+            int flashRow = this.getFlashRow(i);
+            if (this.diskConfigSector.containsKey(flashRow)) {
+                var data = this.diskConfigSector.get(flashRow);
+                writeFlash(SCSI_CONFIG_ARRAY, flashRow, data);
+            }
+        }
+    }
+
+    private byte[] readFlash(int flashArray, int flashRow)
+    {
+        byte[] cmd = {
+                (byte)Commands.READFLASH.ordinal(),
+                HidPacketProtocol.intToUint8(flashArray),
+                HidPacketProtocol.intToUint8(flashRow)
+        };
+
+        return protocolDevice.sendHIDPacket(cmd, 256);
+    }
+
+
+    private byte[] writeFlash(int flashArray, int flashRow, byte[] data)
+    {
+        var cmd = new byte[256+3];
+        cmd[0] =(byte)Commands.WRITEFLASH.ordinal();
+        cmd[257] = HidPacketProtocol.intToUint8(flashArray);
+        cmd[258] = HidPacketProtocol.intToUint8(flashRow);
+
+        System.arraycopy(data, 0, cmd, 1, 256);
+
+        return protocolDevice.sendHIDPacket(cmd, 1);
+    }
+
+    private int getFlashRow(int index) {
+        int flashRow;
+        switch (index) {
+            case 0: flashRow = SCSI_CONFIG_0_ROW; break;
+            case 1: flashRow = SCSI_CONFIG_1_ROW; break;
+            case 2: flashRow = SCSI_CONFIG_2_ROW; break;
+            case 3: flashRow = SCSI_CONFIG_3_ROW; break;
+            default: throw new IndexOutOfBoundsException();
+        }
+        return flashRow;
+    }
+}
diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java
new file mode 100644 (file)
index 0000000..7177637
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import org.hid4java.HidDevice;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class V6FirmwareUsbDevice implements UsbDevice {
+    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(V6FirmwareUsbDevice.class.getPackageName());
+
+    public static final short VENDOR_ID = 0x16D0; // MCS
+    public static final short PRODUCT_ID = 0x0BD4; // SCSI2SD
+
+    private HidDevice hidDevice;
+    private HidPacketProtocolDevice protocolDevice;
+
+    private int firmwareVersion;
+    private int sDCapacity;
+
+    private byte[] configSector0;
+    private byte[] configSector1;
+
+    public V6FirmwareUsbDevice(HidDevice hidDevice) {
+        this.hidDevice = hidDevice;
+        this.protocolDevice = new HidPacketProtocolDevice(hidDevice);
+
+        if (!hidDevice.isOpen()) {
+            if (!hidDevice.open()) {
+                LOGGER.error("Failed to open USB device {}\n{}", hidDevice.getPath(), hidDevice.getLastErrorMessage());
+                throw new RuntimeException("Failed to open USB device.");
+            }
+        }
+        this.readDebugData();
+    }
+
+    @Override
+    public String getHardwareDescription() {
+        return "3.5\" SCSI2SD V6";
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return this.firmwareVersion;
+    }
+
+    @Override
+    public String getFirmwareDescription() {
+        StringBuilder out = new StringBuilder();
+        out.append(this.firmwareVersion >> 8)
+           .append('.')
+           .append((this.firmwareVersion & 0xF0) >> 4);
+
+        int rev = this.firmwareVersion & 0xF;
+        if (rev != 0)
+        {
+            out.append(".").append(rev);
+        }
+        return out.toString();
+    }
+
+    @Override
+    public int getSdCapacity() {
+        return this.sDCapacity;
+    }
+
+    @Override
+    public boolean supportsTerminator() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSynchronous() {
+        return true;
+    }
+
+    private void readDebugData()
+    {
+        // Newer devices only have a single HID interface, and present
+        // a command to obtain the data
+        byte[] cmd = { (byte)Commands.DEVINFO.ordinal(), (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF };
+
+        byte[] data = protocolDevice.sendHIDPacket(cmd, 6);
+
+        this.firmwareVersion = (data[0] << 8) | data[1];
+        this.sDCapacity =
+                (HidPacketProtocol.uint8ToInt(data[2]) << 24) |
+                        (HidPacketProtocol.uint8ToInt(data[3]) << 16) |
+                        (HidPacketProtocol.uint8ToInt(data[4]) << 8) |
+                        (HidPacketProtocol.uint8ToInt(data[5]));
+    }
+
+    @Override
+    public int getMaxDisks()
+    {
+        return 7;
+    }
+
+    @Override
+    public byte[] readBoardConfig()
+    {
+        if (configSector0 == null) {
+            configSector0 = readSector(this.sDCapacity - 2);
+        }
+
+        return Arrays.copyOfRange(configSector0, 0, 128);
+    }
+
+    @Override
+    public void saveBoardConfig(byte[] data)
+    {
+        if (configSector0 == null) {
+            configSector0 = readSector(this.sDCapacity - 2);
+        }
+
+        System.arraycopy(data, 0, configSector0, 0, 128);
+    }
+
+    @Override
+    public void saveDiskConfig(int index, byte[] data)
+    {
+        if (configSector0 == null) {
+            configSector0 = readSector(this.sDCapacity - 2);
+        }
+        if (configSector1 == null) {
+            configSector1 = readSector(this.sDCapacity - 1);
+        }
+
+        if (index <= 2) {
+            var from = 128 + (index * 128);
+
+            System.arraycopy(data, 0, configSector0, from, 128);
+        }
+        else {
+            var from = (index - 3) * 128;
+            System.arraycopy(data, 0, configSector1, from, 128);
+        }
+
+    }
+
+    @Override
+    public void saveCommit() {
+        if (configSector0 != null) {
+            writeSector(this.sDCapacity - 2, configSector0);
+        }
+        if (configSector1 != null) {
+            writeSector(this.sDCapacity - 1, configSector1);
+        }
+    }
+
+    @Override
+    public byte[] readDiskConfig(int index)
+    {
+        if (configSector0 == null) {
+            configSector0 = readSector(this.sDCapacity - 2);
+        }
+        if (configSector1 == null) {
+            configSector1 = readSector(this.sDCapacity - 1);
+        }
+
+        if (index < 0 || index > this.getMaxDisks()) {
+            throw new IllegalArgumentException("Request for invalid disk index");
+        }
+
+        if (index <= 2) {
+            var from = 128 + (index * 128);
+            return Arrays.copyOfRange(configSector0, from, from + 128);
+        }
+        else {
+            var from = (index - 3) * 128;
+            return Arrays.copyOfRange(configSector1, from, from + 128);
+        }
+    }
+
+    private byte[] readSector(int sector)
+    {
+        byte[] cmd = {
+                (byte)Commands.SD_READ.ordinal(),
+                HidPacketProtocol.intToUint8(sector >> 24),
+                HidPacketProtocol.intToUint8(sector >> 16),
+                HidPacketProtocol.intToUint8(sector >> 8),
+                HidPacketProtocol.intToUint8(sector),
+        };
+
+        return protocolDevice.sendHIDPacket(cmd, 512);
+    }
+
+
+    private byte[] writeSector(int sector, byte[] data)
+    {
+        var cmd = new byte[512+5];
+        cmd[0] =(byte)Commands.SD_WRITE.ordinal();
+        cmd[1] =    HidPacketProtocol.intToUint8(sector >> 24);
+        cmd[2] =    HidPacketProtocol.intToUint8(sector >> 16);
+        cmd[3] = HidPacketProtocol.intToUint8(sector >> 8);
+        cmd[4] = HidPacketProtocol.intToUint8(sector);
+
+        for (int i = 0; i < data.length; ++i)
+        {
+            cmd[5 + i] = data[i];
+        }
+
+        return protocolDevice.sendHIDPacket(cmd, 1);
+    }
+}
diff --git a/scsi2sd.io/src/main/java/module-info.java b/scsi2sd.io/src/main/java/module-info.java
new file mode 100644 (file)
index 0000000..25319fd
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+module com.codesrc.scsi2sd.io {
+       requires hid4java;
+       requires org.slf4j;
+
+       exports com.codesrc.scsi2sd.io;
+}
diff --git a/scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java b/scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java
new file mode 100644 (file)
index 0000000..723b576
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.io;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class HidPacketProtocolTest {
+
+    private void test(HidPacketProtocol protocol, byte[] in)
+    {
+        System.out.println(String.format("Testing packet of size %d", in.length));
+        protocol.sendPacket(in);
+        byte[] toSend = protocol.getHIDBytes();
+        while (toSend != null)
+        {
+            System.out.println("Transferring packet");
+            protocol.receiveHIDData(toSend);
+            toSend = protocol.getHIDBytes();
+        }
+
+        byte[] receivedPacket = protocol.getPacket();
+        System.out.println(String.format("Received length = %d", receivedPacket.length));
+        Assert.assertTrue(receivedPacket != null);
+        Assert.assertTrue(receivedPacket.length == in.length);
+        Assert.assertTrue(Arrays.equals(in, receivedPacket));
+    }
+
+    @Test
+    public void testSmall() {
+        byte[] small = {1,2,3,4,5,6,7,8,9,0};
+        HidPacketProtocol protocol = new HidPacketProtocol();
+        this.test(protocol, small);
+    }
+
+    @Test
+    public void testMed() {
+        byte[] med = {
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4
+        };
+
+        HidPacketProtocol protocol = new HidPacketProtocol();
+        this.test(protocol, med);
+    }
+
+    @Test
+    public void testBig() {
+        byte[] big = {
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0
+        };
+        HidPacketProtocol protocol = new HidPacketProtocol();
+        this.test(protocol, big);
+    }
+
+    @Test
+    public void testHuge() {
+        byte[] huge = {
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+                1, 2, 3, 4, 5, 6
+        };
+        HidPacketProtocol protocol = new HidPacketProtocol();
+        this.test(protocol, huge);
+    }
+}
diff --git a/scsi2sd.ui/doc/COPYING b/scsi2sd.ui/doc/COPYING
new file mode 100644 (file)
index 0000000..9c9006b
--- /dev/null
@@ -0,0 +1,80 @@
+SCSI2SD Utility
+Copyright (C) 2019  Michael McMaster <michael@codesrc.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+////////////////////////////////////////////////////////////////////////////////////
+
+The additional license terms apply for software distributed with this program:
+
+////////////////////////////////////////////////////////////////////////////////////
+http://fxexperience.com/controlsfx/
+
+/**
+ * Copyright (c) 2013, 2014, ControlsFX
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *     * Neither the name of ControlsFX, any associated website, nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+////////////////////////////////////////////////////////////////////////////////////
+https://github.com/gary-rowe/hid4java/blob/develop/LICENSE
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Gary Rowe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+////////////////////////////////////////////////////////////////////////////////////
+https://github.com/frexy/glyph-iconset
+Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
+http://creativecommons.org/licenses/by-sa/4.0/
+////////////////////////////////////////////////////////////////////////////////////
similarity index 100%
rename from doc/gplv3.txt
rename to scsi2sd.ui/doc/gplv3.txt
diff --git a/scsi2sd.ui/pom.xml b/scsi2sd.ui/pom.xml
new file mode 100755 (executable)
index 0000000..6b5a785
--- /dev/null
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>scsi2sd-util</artifactId>
+        <groupId>com.codesrc</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>scsi2sd.ui</artifactId>
+    <packaging>jar</packaging>
+
+    <name>scsi2sd.ui</name>
+    <url>http://www.codesrc.com</url>
+    <developers>
+        <developer>
+            <name>Michael McMaster</name>
+            <email>michael@codesrc.com</email>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+       <profiles>
+               <profile>
+                       <id>linux</id>
+                       <activation>
+                               <os>
+                                       <family>unix</family>
+                                       <name>Linux</name>
+                               </os>
+                       </activation>
+                       <properties>
+                               <jmodPath>${project.build.directory}/../../build/linux/javafx-jmods-11.0.2</jmodPath>
+                       </properties>
+               </profile>
+               <profile>
+                       <id>mac</id>
+                       <activation>
+                               <os>
+                                       <family>mac</family>
+                               </os>
+                       </activation>
+                       <properties>
+                               <jmodPath>${project.build.directory}/../../build/mac/javafx-jmods-11.0.2</jmodPath>
+                       </properties>
+               </profile>
+               <profile>
+                       <id>windows</id>
+                       <activation>
+                               <os>
+                                       <family>windows</family>
+                               </os>
+                       </activation>
+                       <properties>
+                               <jmodPath>${project.build.directory}/../../build/windows/javafx-jmods-11.0.2</jmodPath>
+                       </properties>
+               </profile>
+       </profiles>
+
+    <build>
+        <directory>target</directory>
+        <outputDirectory>target/classes</outputDirectory>
+        <finalName>${project.artifactId}-${project.version}</finalName>
+
+        <resources>
+            <resource>
+                <directory>doc</directory>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <source>10</source>
+                    <target>10</target>
+                </configuration>
+            </plugin>
+         <plugin>
+            <!-- Build an executable JAR -->
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-jar-plugin</artifactId>
+            <version>3.1.1</version>
+            <configuration>
+                <archive>
+                    <manifest>
+                        <!-- <addClasspath>true</addClasspath> -->
+                        <mainClass>com.codesrc.scsi2sd.App</mainClass>
+                    </manifest>
+                </archive>
+            </configuration>
+        </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.1</version>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/install</outputDirectory>
+                            <overWriteReleases>false</overWriteReleases>
+                            <overWriteSnapshots>true</overWriteSnapshots>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                                                       <excludeGroupIds>org.openjfx,org.hid4java,net.java.dev.jna</excludeGroupIds>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+
+                       <plugin>
+                           <groupId>org.moditect</groupId>
+                           <artifactId>moditect-maven-plugin</artifactId>
+                           <version>1.0.0.Beta2</version>
+    <executions>
+                               <execution>
+            <id>add-module-infos</id>
+            <phase>generate-resources</phase>
+            <goals>
+                <goal>add-module-info</goal>
+            </goals>
+
+            <configuration>
+                <outputDirectory>${project.build.directory}/install</outputDirectory>
+                <modules>
+                    <module>
+                        <artifact>
+                            <groupId>net.sf.jopt-simple</groupId>
+                            <artifactId>jopt-simple</artifactId>
+                            <version>6.0-alpha-3</version>
+                        </artifact>
+                        <moduleInfoSource>
+                            module joptsimple {
+                                exports joptsimple;
+                            }
+                        </moduleInfoSource>
+                    </module>
+                </modules>
+            </configuration>
+        </execution>
+
+
+        <execution>
+            <id>create-runtime-image</id>
+            <phase>package</phase>
+            <goals>
+                <goal>create-runtime-image</goal>
+            </goals>
+            <configuration>
+                <modulePath>
+                    <path>${project.build.directory}/scsi2sd.ui-1.0-SNAPSHOT.jar</path>
+                    <path>${jmodPath}</path>
+                    <path>${project.build.directory}/../../scsi2sd.io/target/install</path>
+                    <path>${project.build.directory}/install</path>
+                </modulePath>
+                <modules>
+                    <module>com.codesrc.scsi2sd.ui</module>
+                </modules>
+                <excludedResources>
+                </excludedResources>
+                <!--<baseJdk>version=11,vendor=openjdk,platform=linux-x64</baseJdk>-->
+                <launcher>
+                    <name>scsi2sd-util</name>
+                    <module>com.codesrc.scsi2sd.ui/com.codesrc.scsi2sd.App</module>
+                </launcher>
+                <outputDirectory>
+                    ${project.build.directory}/jlink-image
+                </outputDirectory>
+                               <stripDebug>true</stripDebug>
+            </configuration>
+        </execution>
+                           </executions>
+                       </plugin>
+
+        </plugins>
+    </build>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>scsi2sd.io</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-base -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-base</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-media</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-web</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-fxml</artifactId>
+            <version>11.0.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.controlsfx</groupId>
+            <artifactId>controlsfx</artifactId>
+            <version>11.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <version>1.8.0-beta2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sf.jopt-simple</groupId>
+            <artifactId>jopt-simple</artifactId>
+            <version>6.0-alpha-3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hid4java</groupId>
+            <artifactId>hid4java</artifactId>
+            <version>0.5.0</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java
new file mode 100644 (file)
index 0000000..7240daf
--- /dev/null
@@ -0,0 +1,185 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd;
+
+import com.codesrc.scsi2sd.io.UsbDeviceManager;
+import com.codesrc.scsi2sd.presentation.FXWindow;
+import javafx.application.Application;
+import javafx.concurrent.Task;
+import javafx.concurrent.Worker;
+import javafx.scene.Scene;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class App extends Application {
+    private static AppOptions appOptions;
+
+    // Need to keep a reference or else weak-reference is garbage-collected and our debug
+    // log setting gets lost.
+    private static Logger usbLogger;
+
+    @Override
+    public void start(Stage stage) throws Exception {
+        final Stage splashStage = appOptions.isShowSplashScreen() ? showSplash(stage) : null;
+
+        Task<ScreensConfiguration> initTask = new Task<ScreensConfiguration>() {
+            @Override
+            protected ScreensConfiguration call() throws Exception {
+                // Spring framework startup
+                long startMs = System.currentTimeMillis();
+
+                AppContext appContext = new AppContext(appOptions);
+                ScreensConfiguration screens = new ScreensConfiguration(appContext);
+                screens.setPrimaryStage(stage);
+                long endMs = System.currentTimeMillis();
+                long sleepMs = 1200 - (endMs - startMs);
+
+                if (appOptions.isShowSplashScreen() && sleepMs > 0) {
+                    Thread.sleep(sleepMs);
+                }
+                return screens;
+            }
+        };
+
+        new Thread(initTask).start();
+        initTask.stateProperty().addListener((observableValue, oldState, newState) -> {
+            if (newState == Worker.State.SUCCEEDED) {
+                FXWindow main = initTask.getValue().mainWindow();
+                main.show();
+                if (splashStage != null) {
+                    splashStage.hide();
+                }
+            }
+        });
+    }
+
+    private Stage showSplash(Stage primaryStage) {
+        ImageView logo = new ImageView();
+        logo.setX(1);
+        logo.setY(1);
+        logo.setImage(new Image(App.class.getResourceAsStream("/img/splash.png")));
+
+        Pane splashRoot = new Pane();
+        splashRoot.setPrefWidth(logo.getImage().getWidth() + 2);
+        splashRoot.setPrefHeight(logo.getImage().getHeight() + 2);
+        splashRoot.getChildren().add(logo);
+
+        ProgressBar pb = new ProgressBar();
+        pb.setLayoutX(logo.getImage().getWidth() * 0.33);
+        pb.setLayoutY(logo.getImage().getHeight() * 0.90);
+        pb.setPrefWidth(logo.getImage().getWidth() * 0.33);
+        splashRoot.getChildren().add(pb);
+
+        splashRoot.setEffect(new DropShadow());
+
+        Scene splash = new Scene(splashRoot);
+        splash.getStylesheets().add(App.class.getResource("/css/splash.css").toString());
+        Stage splashStage = new Stage();
+        splashStage.initOwner(primaryStage);
+        splashStage.setScene(splash);
+        splashStage.initStyle(StageStyle.UNDECORATED);
+        splashStage.setTitle("codesrc SCSI2SD");
+
+        splashStage.show();
+        return splashStage;
+    }
+
+    private static void parseCommandLineOptions(String[] args) {
+        OptionParser parser = new OptionParser();
+        parser.acceptsAll(Arrays.asList("n", "nosplash"), "No splash screen delay");
+        parser.acceptsAll(Arrays.asList("h", "?", "help"), "Show help").forHelp();
+        parser.acceptsAll(Arrays.asList("d", "debug"), "Debug logging");
+
+
+        OptionSet options = parser.parse(args);
+        if (options.has("help")) {
+            try {
+                parser.printHelpOn(System.out);
+                System.exit(0);
+            } catch (IOException e) {
+                System.exit(1);
+            }
+        }
+
+        appOptions = new AppOptions();
+        appOptions.setShowSplashScreen(!options.has("n"));
+        appOptions.setDebugLogging(options.has("d"));
+
+    }
+
+    public static void main(String[] args) {
+
+        Properties appProperties = new Properties();
+        try (InputStream is = App.class.getResourceAsStream("/app.properties")) {
+            appProperties.load(is);
+        } catch (IOException e)
+        {
+            System.err.println("Could not load resources");
+            System.exit(1);
+        }
+
+        System.out.println(
+                "SCSI2SD Utility v" + appProperties.getProperty("com.codesrc.scsi2sd.app.version") + "\n" +
+                "Copyright (C) 2019  Michael McMaster <michael@codesrc.com>\n" +
+                "\n" +
+                "This program is free software: you can redistribute it and/or modify\n" +
+                "it under the terms of the GNU General Public License as published by\n" +
+                "the Free Software Foundation, either version 3 of the License, or\n" +
+                "(at your option) any later version.\n\n" +
+                "This program is distributed in the hope that it will be useful,\n" +
+                "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
+                "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
+                "GNU General Public License for more details.\n\n" +
+                "You should have received a copy of the GNU General Public License\n" +
+                "along with this program.  If not, see <https://www.gnu.org/licenses/>.\n");
+
+        parseCommandLineOptions(args);
+
+        if (appOptions.isDebugLogging())
+        {
+            setDebugLevel();
+        }
+
+        launch(args);
+    }
+
+    public static void setDebugLevel() {
+        Level targetLevel = Level.ALL;
+        usbLogger = Logger.getLogger(UsbDeviceManager.class.getPackageName());
+        usbLogger.setLevel(targetLevel);
+        for (Handler handler : usbLogger.getHandlers()) {
+            handler.setLevel(targetLevel);
+        }
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java
new file mode 100644 (file)
index 0000000..15c9eb2
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd;
+
+import com.codesrc.scsi2sd.document.ActiveDocument;
+import com.codesrc.scsi2sd.io.UsbDeviceManager;
+import com.codesrc.scsi2sd.presentation.Desktop;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class AppContext {
+       private static String appPropertyResource = "app.properties";
+       private AppOptions appOptions;
+
+       private Properties appProperties;
+       private UsbDeviceManager usbDeviceManager;
+       private Desktop desktop;
+       private ActiveDocument activeDocument;
+
+       public AppContext(AppOptions appOptions) {
+               this.appOptions = appOptions;
+       }
+
+       public synchronized Properties getAppProperties() {
+               if (this.appProperties == null) {
+                       this.appProperties = new Properties();
+
+                       try (InputStream inputStream = getClass().getResourceAsStream("/app.properties")) {
+
+                               if (inputStream == null)
+                                       System.err.println("null stream");
+                               this.appProperties.load(inputStream);
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                               throw new RuntimeException(e);
+                       }
+               }
+               return this.appProperties;
+       }
+
+       public synchronized Desktop getDesktop() {
+               if (this.desktop == null) {
+                       this.desktop = new Desktop();
+               }
+               return this.desktop;
+       }
+
+       public synchronized UsbDeviceManager getUsbDeviceManager() {
+               if (this.usbDeviceManager == null) {
+                       this.usbDeviceManager = UsbDeviceManager.getInstance();
+               }
+               return this.usbDeviceManager;
+       }
+
+       public synchronized ActiveDocument getActiveDocument() {
+               if (this.activeDocument == null) {
+                       this.activeDocument = new ActiveDocument();
+               }
+               return this.activeDocument;
+       }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java
new file mode 100644 (file)
index 0000000..bfa1eda
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd;
+
+/**
+ * Created by michael on 4/06/17.
+ */
+public class AppOptions {
+
+    private boolean showSplashScreen = true;
+    private boolean debugLogging = false;
+
+    public AppOptions() {
+
+    }
+
+    public boolean isShowSplashScreen() {
+        return showSplashScreen;
+    }
+
+    public void setShowSplashScreen(boolean showSplashScreen) {
+        this.showSplashScreen = showSplashScreen;
+    }
+
+    public boolean isDebugLogging() { return this.debugLogging; }
+
+    public void setDebugLogging(boolean debugLogging) {
+        this.debugLogging = debugLogging;
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java
new file mode 100644 (file)
index 0000000..57f77f2
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd;
+
+import com.codesrc.scsi2sd.presentation.*;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import javafx.util.Callback;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Suitable for use as a FXMLLoader ControllerFactory
+ */
+public class ScreensConfiguration implements Callback<Class<?>,Object> {
+
+    private AppContext appContext;
+    private Stage primaryStage;
+
+    public ScreensConfiguration(AppContext appContext) {
+        this.appContext = appContext;
+    }
+
+    public void setPrimaryStage(Stage primaryStage) {
+        this.primaryStage = primaryStage;
+    }
+
+    @Override
+    public Object call(Class<?> aClass) {
+               String methodName = "provide" + aClass.getSimpleName();
+        try {
+            return getClass().getMethod(methodName).invoke(this);
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public FXWindow mainWindow() {
+        return new FXWindow(this, getClass().getResource("/fx/main.fxml"), MainController.Title, primaryStage, StageStyle.DECORATED);
+    }
+
+    public FXWindow aboutWindow(FXWindow parentWindow) {
+        return new FXWindow(this, getClass().getResource("/fx/about.fxml"), "About SCSI2SD", parentWindow, StageStyle.DECORATED);
+    }
+
+    public MainController provideMainController() {
+        return new MainController(
+                this,
+                this.appContext.getUsbDeviceManager(),
+                this.appContext.getActiveDocument());
+    }
+
+    public AboutController provideAboutController() {
+        return new AboutController(
+            this,
+            this.appContext.getDesktop(),
+            this.appContext.getAppProperties());
+    }
+
+    public ConfigController provideConfigController() {
+        return new ConfigController(this, this.appContext.getActiveDocument());
+    }
+
+    public StatusTabController provideStatusTabController() {
+        return new StatusTabController(this, this.appContext.getUsbDeviceManager());
+    }
+
+    public DebugLogController provideDebugLogController() {
+        return new DebugLogController(this, this.appContext.getUsbDeviceManager());
+    }
+
+    public DiskConfigController provideDiskConfigController() {
+        return new DiskConfigController(this, this.appContext.getActiveDocument(), this.appContext.getUsbDeviceManager());
+    }
+
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java
new file mode 100644 (file)
index 0000000..2ac0a1d
--- /dev/null
@@ -0,0 +1,145 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.document;
+
+import com.codesrc.scsi2sd.model.ConfigRoot;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * Created by michael on 28/05/17.
+ */
+public class ActiveDocument {
+
+    private ConcurrentLinkedQueue<DocumentObserver> observers = new ConcurrentLinkedQueue<DocumentObserver>();
+
+    private Map<String, Boolean> isModified = new ConcurrentHashMap<String, Boolean>();
+    private Map<String, Boolean> isValid = new ConcurrentHashMap<String, Boolean>();
+
+    private ConfigRoot originalDocument;
+    private ConfigRoot activeDocument;
+
+    private String originalDocumentName;
+    private boolean inLoad = false;
+
+    public void addObserver(DocumentObserver observer) {
+        observers.add(observer);
+    }
+
+    public void removeObserver(DocumentObserver observer) {
+        observers.remove(observer);
+    }
+
+    public synchronized boolean isModified() {
+        for (Boolean value : isModified.values())
+        {
+            if (value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setModified(String source, boolean modified)
+    {
+        boolean wasModified;
+        boolean isNowModified;
+
+        synchronized(this) {
+            if (inLoad) {
+                return;
+            }
+
+            wasModified = this.isModified();
+
+            isModified.put(source, modified);
+
+            isNowModified = this.isModified();
+        }
+
+        if (wasModified != isNowModified) {
+            for (DocumentObserver observer : this.observers) {
+                observer.documentModified(this, isNowModified);
+            }
+        }
+    }
+
+    public synchronized boolean isValid() {
+        for (Boolean value : isValid.values())
+        {
+            if (!value) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void setValid(String source, boolean valid)
+    {
+        boolean wasValid;
+        boolean isNowValid;
+
+        synchronized(this) {
+            wasValid = this.isValid();
+
+            isValid.put(source, valid);
+
+            isNowValid = this.isValid();
+        }
+
+        if (wasValid != isNowValid) {
+            for (DocumentObserver observer : this.observers) {
+                observer.documentValid(this, isNowValid);
+            }
+        }
+    }
+
+    public ConfigRoot getOriginalDocument() {
+        return this.originalDocument;
+    }
+
+    public String getOriginalDocumentName() {
+        return this.originalDocumentName;
+    }
+
+    public ConfigRoot getActiveDocument() {
+        return this.activeDocument;
+    }
+
+    public void loadDocument(ConfigRoot document, String originalDocumentName) {
+        synchronized (this) {
+            this.originalDocument = new ConfigRoot(document);
+            this.originalDocumentName = originalDocumentName;
+            this.activeDocument = document;
+            this.isModified.clear();
+            this.isValid.clear();
+            this.inLoad = true;
+        }
+
+        for (DocumentObserver observer : this.observers) {
+            observer.documentLoaded(this);
+        }
+
+        synchronized (this) {
+            this.inLoad = false;
+        }
+    }
+}
+
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java
new file mode 100644 (file)
index 0000000..44c1067
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.document;
+
+/**
+ * Created by michael on 28/05/17.
+ */
+public interface DocumentObserver {
+    void documentLoaded(ActiveDocument document);
+    void documentModified(ActiveDocument document, boolean isModified);
+    void documentValid(ActiveDocument document, boolean isValid);
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java
new file mode 100644 (file)
index 0000000..8d57d73
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+public interface BitEnum {
+    int getBit();
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java
new file mode 100644 (file)
index 0000000..d9c7fa2
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.Set;
+
+public class BitUtil
+{
+
+    public static <T extends BitEnum> long getMask(Set<T> flags)
+    {
+        long result = 0;
+        for (T item : flags) {
+            result |= item.getBit();
+        }
+        return result;
+    }
+
+    public static long letohl(byte[] in, int offset)
+    {
+        return (((int) in[offset + 3] & 0xFF) << 24) |
+                (((int) in[offset + 2] & 0xFF) << 16) |
+                (((int) in[offset + 1] & 0xFF) << 8) |
+                ((int) in[offset] & 0xFF);
+    }
+
+    public static int letohs(byte[] in, int offset) {
+        return (((int) in[offset + 1] & 0xFF) << 8) |
+                ((int) in[offset] & 0xFF);
+    }
+
+
+    public static int uint8ToInt(byte data) {
+        return (int) data & 0xFF;
+    }
+
+    public static byte intToUint8(int data)
+    {
+        return (byte) (data & 0xFF);
+    }
+
+    public static byte longToUint8(long data)
+    {
+        return (byte) (data & 0xFF);
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java
new file mode 100644 (file)
index 0000000..cb25a3f
--- /dev/null
@@ -0,0 +1,319 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import org.w3c.dom.Node;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class BoardConfig {
+    public static final int LENGTH = 128;
+
+    private Set<Flag> flags;
+
+    private int startupDelay; // Seconds
+
+    private int selectionDelay; // milliseconds
+
+    private Set<Flag6> flags6;
+
+       private Speed scsiSpeed;
+
+    public BoardConfig(BoardConfig other)
+    {
+        this.flags = new HashSet<>(other.flags);
+        this.startupDelay = other.startupDelay;
+        this.selectionDelay = other.selectionDelay;
+        this.flags6 = new HashSet<>(other.flags6);
+        this.scsiSpeed = other.scsiSpeed;
+    }
+
+    public BoardConfig() {
+           this.flags = new HashSet<>();
+           this.flags6 = new HashSet<>();
+           this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR);
+
+           this.startupDelay = 0;
+           this.selectionDelay = 255; // "Auto".
+        this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit;
+    }
+
+
+    public BoardConfig(byte[] in) {
+        if (in.length != LENGTH) {
+            throw new IllegalArgumentException("BoardConfig size incorrect");
+        }
+
+        flags = Flag.fromBitmask(in[4]);
+        startupDelay = (int)in[5] & 0xFF;
+        selectionDelay = (int)in[6] & 0xFF;
+        flags6 = Flag6.fromBitmask(in[7]);
+        scsiSpeed = Speed.fromBits(in[8]);
+    }
+
+    public byte[] asBytes() {
+        byte[] out = new byte[LENGTH];
+
+        out[0] = 'B'; out[1] = 'C'; out[2] = 'F'; out[3] = 'G';
+
+        out[4] = BitUtil.longToUint8(BitUtil.getMask(flags));
+        out[5] = BitUtil.intToUint8(startupDelay);
+        out[6] = BitUtil.intToUint8(selectionDelay);
+        out[7] = BitUtil.longToUint8(BitUtil.getMask(flags6));
+        out[8] = BitUtil.intToUint8(scsiSpeed.getValue());
+        return out;
+    }
+
+    public Set<Flag> getFlags() {
+        return flags;
+    }
+
+    public void setFlags(Set<Flag> flags) {
+        this.flags = flags;
+    }
+
+    public void setFlag(Flag flag, boolean value)
+    {
+        if (value)
+        {
+            this.flags.add(flag);
+        }
+        else
+        {
+            this.flags.remove(flag);
+        }
+    }
+
+    public void setFlag6(Flag6 flag, boolean value)
+    {
+        if (value)
+        {
+            this.flags6.add(flag);
+        }
+        else
+        {
+            this.flags6.remove(flag);
+        }
+    }
+
+    public int getStartupDelay() {
+        return startupDelay;
+    }
+
+    public void setStartupDelay(int startupDelay) {
+        this.startupDelay = startupDelay;
+    }
+
+    public int getSelectionDelay() {
+        return selectionDelay;
+    }
+
+    public void setSelectionDelay(int selectionDelay) {
+        this.selectionDelay = selectionDelay;
+    }
+
+    public Set<Flag6> getFlags6() {
+        return flags6;
+    }
+
+    public void setFlag6(Set<Flag6> flags6) {
+        this.flags6 = flags6;
+    }
+
+    public Speed getScsiSpeed() {
+        return scsiSpeed;
+    }
+
+    public void setScsiSpeed(Speed scsiSpeed) {
+        this.scsiSpeed = scsiSpeed;
+    }
+
+    public boolean isEnableTerminator() {
+        return flags6.contains(Flag6.S2S_CFG_ENABLE_TERMINATOR);
+    }
+
+    public void setEnableTerminator(boolean enableTerminator) {
+        this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR);
+    }
+
+    public BoardConfig(Node node) {
+           var childNodes = node.getChildNodes();
+        var children = IntStream.range(0, childNodes.getLength())
+                .mapToObj(childNodes::item)
+                .filter(n -> n.getNodeType() == Node.ELEMENT_NODE)
+                .filter(n -> n.hasChildNodes() &&
+                        n.getFirstChild().getNodeType() == Node.TEXT_NODE)
+                .collect(Collectors.toMap(n -> n.getNodeName(), n -> n.getFirstChild().getNodeValue().trim()));
+
+        this.flags = new HashSet<>();
+
+        if (children.getOrDefault("unitAttention", "false").equalsIgnoreCase("true")) {
+            flags.add(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION);
+        }
+
+        if (children.getOrDefault("parity", "false").equalsIgnoreCase("true")) {
+
+            flags.add(Flag.S2S_CFG_ENABLE_PARITY);
+        }
+
+        if (children.getOrDefault("enableScsi2", "false").equalsIgnoreCase("true")) {
+            flags.add(Flag.S2S_CFG_ENABLE_SCSI2);
+        }
+
+        if (children.getOrDefault("selLatch", "false").equalsIgnoreCase("true")) {
+            flags.add(Flag.S2S_CFG_ENABLE_SEL_LATCH);
+        }
+
+        if (children.getOrDefault("mapLunsToIds", "false").equalsIgnoreCase("true")) {
+            flags.add(Flag.S2S_CFG_MAP_LUNS_TO_IDS);
+        }
+
+        this.flags6 = new HashSet<>();
+        if (children.getOrDefault("enableTerminator", "false").equalsIgnoreCase("true")) {
+            this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR);
+        }
+
+        this.selectionDelay = 255;
+        var selDelayText = children.getOrDefault("selectionDelay", "255");
+        try {
+            var intValue = Integer.valueOf(selDelayText);
+            if (intValue >= 0 && intValue <= 255) {
+                this.selectionDelay = intValue;
+            }
+        } catch (NumberFormatException e) {
+            // Ignore rubbish data.
+        }
+
+        this.startupDelay = 0;
+        var startDelayText = children.getOrDefault("startupDelay", "0");
+        try {
+            var intValue = Integer.valueOf(startDelayText);
+            if (intValue >= 0 && intValue <= 255) {
+                this.startupDelay = intValue;
+            }
+        } catch (NumberFormatException e) {
+            // Ignore rubbish data.
+        }
+
+        this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit;
+        var speedNodeText = children.getOrDefault("scsiSpeed", "0");
+        try {
+            var intValue = Integer.valueOf(speedNodeText);
+            switch (intValue) {
+                case 0: this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; break;
+                case 1: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_15; break;
+                case 2: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_33; break;
+                case 3: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_50; break;
+                case 4: this.scsiSpeed = Speed.S2S_CFG_SPEED_SYNC_5; break;
+                case 5: this.scsiSpeed = Speed.S2S_CFG_SPEED_SYNC_10; break;
+                case 6: this.scsiSpeed = Speed.S2S_CFG_SPEED_TURBO; break;
+                default: this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; break;
+            }
+        } catch (NumberFormatException e) {
+            // Ignore rubbish data.
+        }
+    }
+
+    public String toXML()
+    {
+        var out = new StringBuilder();
+
+        out.append("<S2S_BoardCfg>\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  Enable the onboard active terminator.\n")
+            .append("  Both ends of the SCSI chain should be terminated. Disable\n")
+            .append("  only if the SCSI2SD is in the middle of a chain with other\n")
+            .append("  devices.\n")
+            .append("  ********************************************************* -->\n")
+            .append(     "     <enableTerminator>")
+            .append(this.flags6.contains(Flag6.S2S_CFG_ENABLE_TERMINATOR) ? "true" : "false")
+            .append(    "</enableTerminator>\n")
+
+            .append("  <unitAttention>")
+            .append(this.flags.contains(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION) ? "true" : "false")
+            .append("</unitAttention>\n")
+
+            .append("  <parity>")
+            .append(this.flags.contains(Flag.S2S_CFG_ENABLE_PARITY) ? "true" : "false")
+            .append("</parity>\n")
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Only set to true when using with a fast SCSI2 host\n ")
+            .append("  controller. This can cause problems with older/slower\n")
+            .append("  hardware.\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <enableScsi2>")
+            .append(this.flags.contains(Flag.S2S_CFG_ENABLE_SCSI2) ? "true" : "false")
+            .append("</enableScsi2>\n")
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Respond to very short duration selection attempts. This supports\n")
+            .append("  non-standard hardware, but is generally safe to enable.\n")
+            .append("  Required for Philips P2000C.\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <selLatch>")
+            .append(this.flags.contains(Flag.S2S_CFG_ENABLE_SEL_LATCH) ? "true" : "false")
+            .append("</selLatch>\n")
+
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Convert luns to IDs. The unit must already be configured to respond\n")
+            .append("  on the ID. Allows dual drives to be accessed from a \n")
+            .append("  XEBEC S1410 SASI bridge.\n")
+            .append("  eg. Configured for dual drives as IDs 0 and 1, but the XEBEC will\n")
+            .append("  access the second disk as ID0, lun 1.\n")
+            .append("  See ttp://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <mapLunsToIds>")
+            .append(this.flags.contains(Flag.S2S_CFG_MAP_LUNS_TO_IDS) ? "true" : "false")
+            .append("</mapLunsToIds>\n")
+
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Delay (in milliseconds) before responding to a SCSI selection.\n")
+            .append("  255 (auto) sets it to 0 for SCSI2 hosts and 1ms otherwise.\n")
+            .append("  Some samplers need this set to 1 manually.\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <selectionDelay>").append(this.selectionDelay).append("</selectionDelay>\n")
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Startup delay (in seconds) before responding to the SCSI bus \n")
+            .append("  after power on. Default = 0.\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <startupDelay>").append(this.startupDelay).append("</startupDelay>\n")
+
+            .append("  <!-- ********************************************************\n")
+            .append("  Speed limit the SCSI interface. This is the -max- speed the \n")
+            .append("  device will run at. The actual spee depends on the capability\n")
+            .append("  of the host controller.\n")
+            .append("  0       No limit\n")
+            .append("  1       Async 1.5MB/s\n")
+            .append("  2       Async 3.3MB/s\n")
+            .append("  3       Async 5MB/s\n")
+            .append("  4       Sync 5MB/s\n")
+            .append("  5       Sync 10MB/s\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <scsiSpeed>").append(this.scsiSpeed.getValue()).append("</scsiSpeed>\n")
+            .append("</S2S_BoardCfg>\n");
+
+        return out.toString();
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java
new file mode 100644 (file)
index 0000000..874d116
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+public class ConfigRoot {
+    private BoardConfig boardConfig;
+    private DiskList diskList;
+
+    public ConfigRoot() {
+    }
+
+    // Deep copy ctor
+    public ConfigRoot(ConfigRoot other) {
+        if (other.boardConfig != null) {
+            this.boardConfig = new BoardConfig(other.boardConfig);
+        }
+
+        if (other.diskList != null) {
+            this.diskList = new DiskList(other.diskList);
+        }
+    }
+
+    public BoardConfig getBoardConfig() {
+        return boardConfig;
+    }
+
+    public void setBoardConfig(BoardConfig boardConfig) {
+        this.boardConfig = boardConfig;
+    }
+
+
+    public DiskList getDiskList() {
+        return diskList;
+    }
+
+    public void setDiskList(DiskList diskList) {
+        this.diskList = diskList;
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java
new file mode 100644 (file)
index 0000000..dd76ccd
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.*;
+
+public enum DeviceTypeEnum {
+    S2S_CFG_FIXED(0, "Hard Drive"),
+    S2S_CFG_REMOVEABLE(1, "Removable"),
+    S2S_CFG_OPTICAL(2, "CDROM"),
+    S2S_CFG_FLOPPY_14MB(3, "3.5\" Floppy"),
+    S2S_CFG_MO(4, "Magneto Optical"),
+    S2S_CFG_SEQUENTIAL(5, "Tape");
+
+    static DeviceTypeEnum fromValue(byte value) {
+        for (DeviceTypeEnum deviceType : EnumSet.allOf(DeviceTypeEnum.class)) {
+            if (deviceType.value == value)
+            {
+                return deviceType;
+            }
+        }
+        return S2S_CFG_FIXED;
+    }
+
+
+    private int value;
+    private String desc;
+
+    DeviceTypeEnum(int value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    public int getValue() { return this.value; }
+
+    @Override
+    public String toString()
+    {
+        return this.desc;
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java
new file mode 100644 (file)
index 0000000..83b4a8e
--- /dev/null
@@ -0,0 +1,596 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import org.w3c.dom.Node;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class DiskConfig {
+    public static final int LENGTH = 128;
+    public static final int SD_SECTOR_SIZE = 512;
+
+    public enum Type {
+        RESERVED,
+        EMPTY,
+        DISK
+    }
+
+    private boolean active;
+    private int scsiId;
+    private Type type;
+    private long sdSectorStart;
+    private long scsiSectors;
+    private int bytesPerSector;
+    private DeviceTypeEnum deviceType;
+    private int deviceTypeModifier;
+
+    // Max allowed by legacy IBM-PC bios is 6 bits (63)
+    private int sectorsPerTrack;
+
+       // MS-Dos up to 7.10 will crash on >= 256 heads.
+    private int headsPerCylinder;
+
+    private String vendor; //[8]
+    private String prodId; //[16];
+    private String revision; //[4];
+    private String serial; //[16];
+
+    private Set<Quirks> quirks;
+
+    public DiskConfig(
+            Type type,
+            long sdSectorStart,
+            long sdSectors) {
+
+        this(type, sdSectorStart, sdSectors, SD_SECTOR_SIZE);
+    }
+
+    public DiskConfig(DiskConfig other) {
+        this.active = other.active;
+        this.scsiId = other.scsiId;
+        this.type = other.type;
+        this.sdSectorStart = other.sdSectorStart;
+        this.scsiSectors = other.scsiSectors;
+        this.bytesPerSector = other.bytesPerSector;
+        this.deviceType = other.deviceType;
+        this.deviceTypeModifier = other.deviceTypeModifier;
+        this.sectorsPerTrack = other.sectorsPerTrack;
+        this.headsPerCylinder = other.headsPerCylinder;
+        this.vendor = other.vendor;
+        this.prodId = other.prodId;
+        this.revision = other.revision;
+        this.serial = other.serial;
+        this.quirks = new HashSet<>(other.quirks);
+    }
+
+    public DiskConfig(
+            Type type,
+            long sdSectorStart,
+            long scsiSectors,
+            int bytesPerSector) {
+
+        this.scsiId = 0;
+        this.active = true;
+        this.type = type;
+        this.sdSectorStart = sdSectorStart;
+        this.scsiSectors = scsiSectors;
+        this.bytesPerSector = bytesPerSector;
+        this.deviceType = DeviceTypeEnum.S2S_CFG_FIXED;
+        this.deviceTypeModifier = 0;
+
+        this.sectorsPerTrack = 63;
+        this.headsPerCylinder = 255;
+
+        this.vendor = this.pad("codesrc", 8);
+        this.prodId = this.pad("SCSI2SD", 16);
+        this.revision = this.pad("1.0", 4);
+        this.serial = this.pad("12345678", 16);
+
+        this.quirks = new HashSet<Quirks>();
+    }
+
+    public DiskConfig(byte[] in) {
+        if (in.length != LENGTH) {
+            throw new IllegalArgumentException("DiskConfig size incorrect");
+        }
+
+        this.active = (in[0] & 0x80) != 0;
+        this.scsiId = in[0] & 0x7;
+        this.deviceType = DeviceTypeEnum.fromValue(in[1]);
+        this.deviceTypeModifier = BitUtil.uint8ToInt(in[3]);
+        this.type = Type.DISK;
+        this.sdSectorStart = BitUtil.letohl(in, 4);
+        this.scsiSectors = BitUtil.letohl(in, 8);
+        this.bytesPerSector = BitUtil.letohs(in, 12);
+
+        this.sectorsPerTrack = BitUtil.letohs(in, 14);
+        this.headsPerCylinder = BitUtil.letohs(in, 16);
+
+        this.vendor = new String(in, 18, 8, StandardCharsets.US_ASCII);
+        this.prodId = new String(in, 26, 16, StandardCharsets.US_ASCII);
+        this.revision = new String(in, 42, 4, StandardCharsets.US_ASCII);
+        this.serial = new String(in, 46, 16, StandardCharsets.US_ASCII);
+
+        this.quirks = Quirks.fromBitmask(BitUtil.letohs(in, 52));
+    }
+
+
+    public byte[] asBytes() {
+        byte[] out = new byte[LENGTH];
+
+        out[0] = BitUtil.intToUint8((this.scsiId & 0x07) | (this.active ? 0 : 0x80));
+
+        out[1] = BitUtil.intToUint8(this.deviceType.getValue());
+        out[2] = 0; // flagsDeprecated
+        out[3] = BitUtil.intToUint8(this.deviceTypeModifier);
+
+        out[4] = BitUtil.longToUint8(this.sdSectorStart);
+        out[5] = BitUtil.longToUint8(this.sdSectorStart >> 8);
+        out[6] = BitUtil.longToUint8(this.sdSectorStart >> 16);
+        out[7] = BitUtil.longToUint8(this.sdSectorStart >> 24);
+
+        out[8] = BitUtil.longToUint8(this.scsiSectors);
+        out[9] = BitUtil.longToUint8(this.scsiSectors >> 8);
+        out[10] = BitUtil.longToUint8(this.scsiSectors >> 16);
+        out[11] = BitUtil.longToUint8(this.scsiSectors >> 24);
+
+        out[12] = BitUtil.intToUint8(this.bytesPerSector);
+        out[13] = BitUtil.intToUint8(this.bytesPerSector >> 8);
+
+        out[14] = BitUtil.intToUint8(this.sectorsPerTrack);
+        out[15] = BitUtil.intToUint8(this.sectorsPerTrack >> 8);
+
+        out[16] = BitUtil.intToUint8(this.headsPerCylinder);
+        out[17] = BitUtil.intToUint8(this.headsPerCylinder >> 8);
+
+        System.arraycopy(this.vendor.getBytes(StandardCharsets.US_ASCII), 0, out, 18, 8);
+        System.arraycopy(this.prodId.getBytes(StandardCharsets.US_ASCII), 0, out, 26, 16);
+        System.arraycopy(this.revision.getBytes(StandardCharsets.US_ASCII), 0, out, 42, 4);
+        System.arraycopy(this.serial.getBytes(StandardCharsets.US_ASCII), 0, out, 46, 16);
+
+        var quirksMask = BitUtil.getMask(this.quirks);
+        out[52] = BitUtil.longToUint8(quirksMask);
+        out[53] = BitUtil.longToUint8(quirksMask >> 8);
+
+        return out;
+    }
+
+    public int getScsiId()
+    {
+        return this.scsiId;
+    }
+
+    public void setScsiId(int value)
+    {
+        this.scsiId = value & 0x7;
+    }
+
+    public Type getType() {
+        return this.type;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void setActive(boolean active) {
+        this.active = active;
+    }
+
+    public DeviceTypeEnum getDeviceType()
+    {
+        return this.deviceType;
+    }
+
+    public void setDeviceType(DeviceTypeEnum deviceType)
+    {
+        this.deviceType = deviceType;
+    }
+
+    public int getDeviceTypeModifier() {
+        return this.deviceTypeModifier;
+    }
+
+    public void setDeviceTypeModifier(int deviceTypeModifier) {
+        this.deviceTypeModifier = deviceTypeModifier;
+    }
+
+    public long getSdSectorStart() {
+        return this.sdSectorStart;
+    }
+
+    public long getSdSectorEnd() {
+        return this.sdSectorStart + this.getSdSectors();
+    }
+
+    public void setSdSectorStart(long value) {
+        this.sdSectorStart = value;
+    }
+
+    public long getScsiSectors() {
+        return this.scsiSectors;
+    }
+
+    public void setScsiSectors(long value) {
+        this.scsiSectors = value;
+    }
+
+    public long getSdSectors() {
+        return ((this.scsiSectors * this.bytesPerSector) + (SD_SECTOR_SIZE - 1)) / SD_SECTOR_SIZE;
+    }
+
+    public int getBytesPerSector() {
+        return this.bytesPerSector;
+    }
+
+    public void setBytesPerSector(int value) {
+        this.bytesPerSector = value;
+    }
+
+    public Quirks getQuirksMode() {
+        if (this.quirks.isEmpty()) {
+            return Quirks.S2S_CFG_QUIRKS_NONE;
+        } else {
+            return this.quirks.iterator().next();
+        }
+    }
+
+    public void setQuirksMode(Quirks quirksMode) {
+        this.quirks.clear();
+        if (quirksMode != Quirks.S2S_CFG_QUIRKS_NONE) {
+            this.quirks.add(quirksMode);
+        }
+    }
+
+    public DiskUnit getBestUnit() {
+        var scsiSize = this.scsiSectors * this.bytesPerSector;
+
+        double base;
+        String suffix;
+        if (scsiSize >= 0.9 * 1024*1024*1024) {
+            return DiskUnit.GB;
+        }
+        else if (scsiSize >= 0.9 * 1024*1024) {
+            return DiskUnit.MB;
+        }
+        else
+        {
+            return DiskUnit.SECTOR;
+        }
+    }
+
+    public String getSizeString(DiskUnit unit, boolean showSuffix) {
+        var scsiSize = this.scsiSectors * this.bytesPerSector;
+
+        double base;
+        String suffix;
+        if (unit == DiskUnit.GB) {
+            base = scsiSize / (1024.0*1024*1024);
+            suffix = "GB";
+        }
+        else if (unit == DiskUnit.MB) {
+            base = scsiSize / (1024.0*1024);
+            suffix = "MB";
+        }
+        else {
+            base = this.scsiSectors;
+            suffix = "Sectors";
+        }
+
+        if (showSuffix) {
+            return String.format("%.2f %s", base, suffix);
+        }
+        else if (unit != DiskUnit.SECTOR)
+        {
+            return String.format("%.2f", base);
+        }
+        else
+        {
+            return String.format("%d", (long)base);
+        }
+    }
+
+    public String getVendor() {
+        return vendor;
+    }
+
+    public void setVendor(String vendor) {
+        this.vendor = this.pad(vendor, 8);
+    }
+
+    public String getProdId() {
+        return prodId;
+    }
+
+    public void setProdId(String prodId) {
+        this.prodId = this.pad(prodId, 16);
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = this.pad(revision, 4);
+    }
+
+    public String getSerial() {
+        return serial;
+    }
+
+    public void setSerial(String serial) {
+        this.serial = this.pad(serial, 16);
+    }
+
+    public int getSectorsPerTrack() {
+        return sectorsPerTrack;
+    }
+
+    public void setSectorsPerTrack(int sectorsPerTrack) {
+        this.sectorsPerTrack = sectorsPerTrack;
+    }
+
+    public int getHeadsPerCylinder() {
+        return headsPerCylinder;
+    }
+
+    public void setHeadsPerCylinder(int headsPerCylinder) {
+        this.headsPerCylinder = headsPerCylinder;
+    }
+
+    private String pad(String input, int count)
+    {
+        if (input.length() >= count)
+        {
+            return input.substring(0, count);
+        }
+        else
+        {
+            return " ".repeat(count - input.length()) + input;
+        }
+    }
+
+    public DiskConfig(Node node) {
+        this.type = Type.DISK;
+
+        var childNodes = node.getChildNodes();
+        var children = IntStream.range(0, childNodes.getLength())
+                .mapToObj(childNodes::item)
+                .filter(n -> n.getNodeType() == Node.ELEMENT_NODE)
+                .filter(n -> n.hasChildNodes() &&
+                        n.getFirstChild().getNodeType() == Node.TEXT_NODE)
+                .collect(Collectors.toMap(n -> n.getNodeName(), n -> n.getFirstChild().getNodeValue().trim()));
+
+        this.scsiId = 0;
+        var idAttr = node.getAttributes().getNamedItem("id");
+        if (idAttr != null) {
+            try {
+                var id = Integer.valueOf(idAttr.getNodeValue());
+                if (id >= 0 && id <= 7) {
+                    this.scsiId = id;
+                }
+            } catch (NumberFormatException e) {
+                // Ignore
+            }
+        }
+
+        if (children.getOrDefault("enabled", "true").equalsIgnoreCase("true")) {
+            this.active = true;
+        } else {
+            this.active = false;
+        }
+
+        this.quirks = new HashSet<>();
+        switch (children.getOrDefault("quirks", "").toLowerCase()) {
+            case "apple":
+                this.quirks.add(Quirks.S2S_CFG_QUIRKS_APPLE);
+                break;
+            case "omti":
+                this.quirks.add(Quirks.S2S_CFG_QUIRKS_OMTI);
+                break;
+            case "xebec":
+                this.quirks.add(Quirks.S2S_CFG_QUIRKS_XEBEC);
+                break;
+            case "vms":
+                this.quirks.add(Quirks.S2S_CFG_QUIRKS_VMS);
+                break;
+        }
+
+        this.deviceType = DeviceTypeEnum.S2S_CFG_FIXED;
+        var devTypeText = children.getOrDefault("deviceType", "0");
+        try {
+            var radix = 10;
+            if (devTypeText.toLowerCase().startsWith("0x")) {
+                radix = 16;
+                devTypeText = devTypeText.substring(2);
+            }
+            var byteValue = Byte.valueOf(devTypeText, radix);
+            this.deviceType = DeviceTypeEnum.fromValue(byteValue);
+        } catch (NumberFormatException e) {
+        }
+
+        this.deviceTypeModifier = 0;
+        var devTypeModText = children.getOrDefault("deviceTypeModifier", "0");
+        try {
+            var radix = 10;
+            if (devTypeModText.toLowerCase().startsWith("0x")) {
+                radix = 16;
+                devTypeModText = devTypeModText.substring(2);
+            }
+            this.deviceTypeModifier = Byte.valueOf(devTypeModText, radix);
+        } catch (NumberFormatException e) {
+        }
+
+
+        this.sdSectorStart = 0;
+        var sdSectorStartText = children.getOrDefault("sdSectorStart", "0");
+        try {
+            var longValue = Long.valueOf(sdSectorStartText);
+            if (longValue >= 0) {
+                this.sdSectorStart = longValue;
+            }
+        } catch (NumberFormatException e) {
+        }
+
+
+        this.scsiSectors = 1;
+        var scsiSectorsText = children.getOrDefault("scsiSectors", "1");
+        try {
+            var longValue = Long.valueOf(scsiSectorsText);
+            if (longValue >= 0) {
+                this.scsiSectors = longValue;
+            }
+        } catch (NumberFormatException e) {
+        }
+
+        this.bytesPerSector = 512;
+        var bytesPerSectorText = children.getOrDefault("bytesPerSector", "512");
+        try {
+             var intValue = Integer.valueOf(bytesPerSectorText);
+             if (intValue > 0 && intValue <= 65535) {
+                 this.bytesPerSector = intValue;
+             }
+        } catch (NumberFormatException e) {
+        }
+
+        this.sectorsPerTrack = 63;
+        var sectorsPerTrackText = children.getOrDefault("sectorsPerTrack", "63");
+        try {
+            var intValue = Integer.valueOf(sectorsPerTrackText);
+            if (intValue > 0 && intValue <= 65535) {
+                this.sectorsPerTrack = intValue;
+            }
+        } catch (NumberFormatException e) {
+        }
+
+        this.headsPerCylinder = 255;
+        var headsPerCylinderText = children.getOrDefault("headsPerCylinder", "255");
+        try {
+            var intValue = Integer.valueOf(headsPerCylinderText);
+            if (intValue > 0 && intValue <= 65535) {
+                this.headsPerCylinder = intValue;
+            }
+        } catch (NumberFormatException e) {
+        }
+
+        this.vendor = this.pad(children.getOrDefault("vendor", "codesrc"), 8);
+        this.prodId = this.pad(children.getOrDefault("prodId", "SCSI2SD"), 16);
+        this.revision = this.pad(children.getOrDefault("revision", "1.0"), 4);
+        this.serial = this.pad(children.getOrDefault("serial", "12345678"), 16);
+    }
+
+    public String toXML() {
+        var out = new StringBuilder();
+
+        out.append("<SCSITarget id=\"").append(this.scsiId).append("\">\n")
+            .append("  <enabled>")
+            .append(this.active ? "true" : "false")
+            .append("</enabled>\n")
+
+            .append("\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  Space separated list. Available options:\n")
+            .append("  apple\t\tReturns Apple-specific mode pages\n")
+            .append("  omti\t\tOMTI host non-standard link control\n")
+            .append("  xebec\t\tXEBEC ignore step options in control byte\n")
+            .append("  vms\t\tVMS treat 255 bytes as 254 in Inquiry length\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <quirks>");
+
+        if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_APPLE)) {
+            out.append("apple");
+        }
+        else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_OMTI)) {
+            out.append("omti");
+        }
+        else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_XEBEC)) {
+            out.append("xebec");
+        }
+        else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_VMS)) {
+            out.append("vms");
+        }
+
+        out.append("</quirks>\n")
+            .append("\n\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  0x0    Fixed hard drive.\n")
+            .append("  0x1    Removable drive.\n")
+            .append("  0x2    Optical drive  (ie. CD drive).\n")
+            .append("  0x3    1.44MB Floppy Drive.\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <deviceType>0x")
+            .append(Integer.toString(this.deviceType.getValue(), 16))
+            .append("</deviceType>\n")
+
+            .append("\n\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  Device type modifier is usually 0x00. Only change this if your\n")
+            .append("  OS requires some special value.\n")
+            .append("\n")
+            .append("  0x4C    Data General Micropolis disk\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <deviceTypeModifier>0x")
+            .append(Integer.toString(this.deviceTypeModifier, 16))
+            .append("</deviceTypeModifier>\n")
+
+            .append("\n\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  SD card offset, as a sector number (always 512 bytes).\n")
+            .append("  ********************************************************* -->\n")
+            .append("  <sdSectorStart>").append(this.sdSectorStart).append("</sdSectorStart>\n")
+            .append("\n\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  Drive geometry settings.\n")
+            .append("  ********************************************************* -->\n")
+            .append("\n")
+            .append("  <scsiSectors>").append(this.scsiSectors).append("</scsiSectors>\n")
+            .append("  <bytesPerSector>").append(this.bytesPerSector).append("</bytesPerSector>\n")
+            .append("  <sectorsPerTrack>").append(this.sectorsPerTrack).append("</sectorsPerTrack>\n")
+            .append("  <headsPerCylinder>").append(this.headsPerCylinder).append("</headsPerCylinder>\n")
+            .append("\n\n")
+            .append("  <!-- ********************************************************\n")
+            .append("  Drive identification information. The SCSI2SD doesn't\n")
+            .append("  care what these are set to. Use these strings to trick a OS\n")
+            .append("  thinking a specific hard drive model is attached.\n")
+            .append("  ********************************************************* -->\n")
+            .append("\n")
+            .append("  <!-- 8 character vendor string -->\n")
+            .append("  <!-- For Apple HD SC Setup/Drive Setup, use ' SEAGATE' -->\n")
+            .append("  <vendor>").append(this.vendor).append("</vendor>\n")
+            .append("\n")
+            .append("  <!-- 16 character produce identifier -->\n")
+            .append("  <!-- For Apple HD SC Setup/Drive Setup, use '          ST225N' -->\n")
+            .append("  <prodId>").append(this.prodId).append("</prodId>\n")
+            .append("\n")
+            .append("  <!-- 4 character product revision number -->\n")
+            .append("  <!-- For Apple HD SC Setup/Drive Setup, use '1.0 ' -->\n")
+            .append("  <revision>").append(this.revision).append("</revision>\n")
+            .append("\n")
+            .append("  <!-- 16 character serial number -->\n")
+            .append("  <serial>").append(this.serial).append("</serial>\n")
+            .append("\n")
+            .append("</SCSITarget>\n");
+
+        return out.toString();
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java
new file mode 100644 (file)
index 0000000..9590e05
--- /dev/null
@@ -0,0 +1,252 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+// List of disk configurations sorted by SD Starting Sector.
+public class DiskList {
+
+    public static final long reservedSize = 1024*1024 / 512; // oneMb
+
+    private long sdCardSize;
+    private LinkedList<DiskConfig> disks;
+
+    // Deep copy ctor
+    public DiskList(DiskList other) {
+        this.sdCardSize = other.sdCardSize;
+        this.disks = new LinkedList<>();
+        for (var disk : other.disks) {
+            this.disks.add(new DiskConfig(disk));
+        }
+    }
+
+    public DiskList(long sdCardSize) {
+        this.sdCardSize = sdCardSize;
+        disks = new LinkedList<>();
+
+        // Always reserve the first 1Mb for partitioning info
+        disks.add(new DiskConfig(DiskConfig.Type.RESERVED, 0, reservedSize));
+        disks.add(new DiskConfig(DiskConfig.Type.EMPTY, reservedSize, sdCardSize - reservedSize - reservedSize));
+
+        // Also reserve 1Mb at the end for config info
+        disks.add(new DiskConfig(DiskConfig.Type.RESERVED, sdCardSize - reservedSize, reservedSize));
+    }
+
+    public List<DiskConfig> getDisks() {
+        return new ArrayList<>(this.disks);
+    }
+
+    public String getSdCardSizeGb() {
+        return String.format("%.2f", (this.sdCardSize * 512.0) / (1024l*1024*1024));
+    }
+
+    public long getSdCardSize() {
+        return this.sdCardSize;
+    }
+
+
+    public void resetSizeGb(double gb) {
+        var newSize = (long) (gb * (1024l * 1024 * 1024 / 512));
+
+        if (newSize > sdCardSize) {
+            var diffSectors = newSize - sdCardSize;
+            if (disks.getLast().getType() == DiskConfig.Type.RESERVED) {
+                var reserved = disks.getLast();
+                var oldStart = reserved.getSdSectorStart();
+                reserved.setSdSectorStart(oldStart + diffSectors);
+                disks.add(disks.size() - 1, new DiskConfig(DiskConfig.Type.EMPTY, oldStart, diffSectors));
+            }
+            else
+            {
+                disks.add(new DiskConfig(DiskConfig.Type.EMPTY, sdCardSize, diffSectors));
+            }
+
+
+        } else if (newSize < sdCardSize){
+            var lastNonReserved = newSize - reservedSize;
+            while (!disks.isEmpty() && disks.getLast().getSdSectorEnd() > lastNonReserved) {
+                disks.removeLast();
+            }
+
+            if (!disks.isEmpty()) {
+                var lastNotRemoved = disks.getLast().getSdSectorEnd();
+                if (lastNotRemoved < lastNonReserved) {
+                    disks.add(new DiskConfig(DiskConfig.Type.EMPTY, lastNotRemoved, lastNonReserved - lastNotRemoved));
+                }
+            }
+
+            disks.add(new DiskConfig(DiskConfig.Type.RESERVED, lastNonReserved, reservedSize));
+        }
+        this.mergeEmptySections();
+
+        sdCardSize = newSize;
+    }
+
+    public boolean add(DiskConfig config) {
+        var found = false;
+
+        this.removeReservedSections(config);
+        this.mergeEmptySections();
+
+        var it = disks.listIterator();
+        while (it.hasNext())
+        {
+            var entry = it.next();
+            if (entry.getType() == DiskConfig.Type.EMPTY &&
+                entry.getSdSectors() >= config.getSdSectors()) {
+                if (config.getSdSectorStart() < 0 ||
+                    config.getSdSectorStart() == entry.getSdSectorStart())
+                {
+                    config.setSdSectorStart(entry.getSdSectorStart());
+                    entry.setSdSectorStart(entry.getSdSectorStart() + config.getSdSectors());
+                    entry.setScsiSectors(entry.getSdSectors() - config.getSdSectors());
+
+                    if (entry.getSdSectors() == 0)
+                    {
+                        it.remove();
+                    }
+                    else
+                    {
+                        it.previous();
+                    }
+
+                    it.add(config);
+
+                    found = true;
+                    break;
+                }
+                else if (config.getSdSectorStart() > entry.getSdSectorStart() &&
+                        config.getSdSectorEnd() <= entry.getSdSectorEnd())
+                {
+                    var remaining = new DiskConfig(
+                            DiskConfig.Type.EMPTY,
+                            config.getSdSectorEnd(),
+                            entry.getSdSectorEnd() - config.getSdSectorEnd());
+
+                    entry.setScsiSectors(config.getSdSectorStart() - entry.getSdSectorStart());
+
+                    it.add(entry);
+
+                    if (remaining.getSdSectors() > 0) {
+                        it.add(remaining);
+                    }
+
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        return found;
+    }
+
+    public void remove(DiskConfig config) {
+        if (config.getType() != DiskConfig.Type.DISK) {
+            // Don't delete reserved or empty space.
+            return;
+        }
+
+        for (var i = 0; i < disks.size(); ++i) {
+            var entry = disks.get(i);
+
+            if (entry == config)
+            {
+                var startSector = config.getSdSectorStart();
+                var endSector = config.getSdSectorEnd();
+                disks.remove(i);
+
+                var empty = new DiskConfig(DiskConfig.Type.EMPTY, startSector, endSector - startSector);
+                if (empty.getSdSectors() > 0)
+                {
+                    disks.add(i, empty);
+                }
+
+                break;
+            }
+        }
+
+        this.mergeEmptySections();
+    }
+
+    // Convert RESERVED into EMPTY if needed.
+    // Can't do this with the UI but can edit XML file or load an old config.
+    private void removeReservedSections(DiskConfig config)
+    {
+        for (var i = 0; i < disks.size(); ++i)
+        {
+            var entry = disks.get(i);
+
+            if (entry.getType() == DiskConfig.Type.RESERVED &&
+                    config.getSdSectorStart() < entry.getSdSectorEnd() &&
+                    config.getSdSectorEnd() > entry.getSdSectorStart())
+            {
+                var reservedStartSector = entry.getSdSectorStart();
+                var reservedEndSector = entry.getSdSectorEnd();
+                disks.remove(i);
+
+                var stillReservedAtStartSize = config.getSdSectorStart() - reservedStartSector;
+                if (stillReservedAtStartSize > 0)
+                {
+                    disks.add(i, new DiskConfig(DiskConfig.Type.RESERVED, reservedStartSector, stillReservedAtStartSize));
+                    ++i;
+                }
+
+                var emptyStart = reservedStartSector > config.getSdSectorStart() ? reservedStartSector : config.getSdSectorStart();
+                var emptyEnd = reservedEndSector > config.getSdSectorEnd() ? config.getSdSectorEnd() : reservedEndSector;
+                disks.add(i, new DiskConfig(DiskConfig.Type.EMPTY, emptyStart, emptyEnd - emptyStart));
+                ++i;
+
+                var stillReservedAtEndSize = reservedEndSector - config.getSdSectorEnd();
+                if (stillReservedAtEndSize > 0)
+                {
+                    disks.add(i, new DiskConfig(DiskConfig.Type.RESERVED, config.getSdSectorEnd(), stillReservedAtEndSize));
+                    ++i;
+                }
+            }
+        }
+    }
+
+    private void mergeEmptySections()
+    {
+        var i = 0;
+        while (i < disks.size() - 1)
+        {
+            var entry = disks.get(i);
+            if (entry.getType() != DiskConfig.Type.EMPTY)
+            {
+                ++i;
+                continue;
+            }
+
+            var next = disks.get(i + 1);
+            if (next.getType() != DiskConfig.Type.EMPTY)
+            {
+                ++i;
+                continue;
+            }
+
+            var endSector = next.getSdSectorEnd();
+            disks.remove(i + 1);
+            disks.remove(i);
+            disks.add(i, new DiskConfig(DiskConfig.Type.EMPTY, entry.getSdSectorStart(), endSector- entry.getSdSectorStart()));
+        }
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java
new file mode 100644 (file)
index 0000000..012edfb
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+public enum DiskUnit {
+    MB, GB, SECTOR
+}
similarity index 50%
rename from src/main/java/com/codesrc/scsi2sd/model/Flag.java
rename to scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag.java
index 37c6d3b9b31c51a7606eeeace264dd715024dba4..30e63ea3aedc79c25cf8416648dfd36bb2218833 100644 (file)
@@ -1,19 +1,19 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
 //
-//     This file is part of SCSI2SD.
+// This file is part of scsi2sd-util.
 //
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
 //
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
 //
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
 
 package com.codesrc.scsi2sd.model;
 
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java
new file mode 100644 (file)
index 0000000..56faddc
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+public enum Flag6 implements BitEnum {
+    S2S_CFG_ENABLE_TERMINATOR(1);
+
+    static Set<Flag6> fromBitmask(byte bitmask) {
+        Set<Flag6> result = new HashSet<>();
+        for (Flag6 flag : EnumSet.allOf(Flag6.class)) {
+            if ((((int) bitmask & 0xFF) & flag.getBit()) != 0) {
+                result.add(flag);
+            }
+        }
+        return result;
+    }
+
+    private int bit;
+    Flag6(int bit) {
+        this.bit = bit;
+    }
+
+    @Override
+    public int getBit() { return bit; }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java
new file mode 100644 (file)
index 0000000..8ebb841
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.*;
+
+public enum Quirks implements BitEnum {
+    S2S_CFG_QUIRKS_NONE(0, "None"),
+    S2S_CFG_QUIRKS_APPLE(1, "Apple"),
+    S2S_CFG_QUIRKS_OMTI(2, "OMTI"),
+    S2S_CFG_QUIRKS_XEBEC(4, "XEBEC"),
+    S2S_CFG_QUIRKS_VMS(8, "VMS");
+
+    static Set<Quirks> fromBitmask(int bitmask) {
+        Set<Quirks> result = new HashSet<>();
+        for (Quirks flag : EnumSet.allOf(Quirks.class)) {
+            if ((bitmask & flag.getBit()) != 0) {
+                result.add(flag);
+            }
+        }
+        return result;
+    }
+
+
+    private int bit;
+    private String desc;
+
+    Quirks(int bit, String desc) {
+        this.bit = bit;
+        this.desc = desc;
+    }
+
+    @Override
+    public int getBit() { return bit; }
+
+    @Override
+    public String toString()
+    {
+        return this.desc;
+    }
+
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java
new file mode 100644 (file)
index 0000000..59710e5
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.model;
+
+import java.util.EnumSet;
+
+public enum Speed {
+    S2S_CFG_SPEED_NoLimit(0, "No limit"),
+    S2S_CFG_SPEED_ASYNC_15(1, "Async, 1.5MB/s"),
+    S2S_CFG_SPEED_ASYNC_33(2, "Async, 3.3MB/s"),
+    S2S_CFG_SPEED_ASYNC_50(3, "Async, 5.0MB/s"),
+    S2S_CFG_SPEED_SYNC_5(4, "Sync, 5.0MB/s"),
+    S2S_CFG_SPEED_SYNC_10(5, "Sync, 10MB/s"),
+    S2S_CFG_SPEED_TURBO(6, "Turbo");
+
+    static Speed fromBits(byte bits) {
+
+        for (Speed speed : EnumSet.allOf(Speed.class)) {
+            if (speed.getValue() == ((int) bits & 0xFF)) {
+                return speed;
+            }
+        }
+        return S2S_CFG_SPEED_NoLimit;
+    }
+
+
+    private int value;
+    private String desc;
+
+    Speed(int value, String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return this.desc;
+    }
+}
+
+
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java
new file mode 100644 (file)
index 0000000..4ee445f
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import javafx.fxml.FXML;
+import javafx.scene.control.*;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.stream.Collectors;
+
+/**
+ * Created by michael on 27/05/17.
+ */
+public class AboutController implements FXController {
+    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AboutController.class);
+
+    private ScreensConfiguration screens;
+    private FXWindow window;
+
+    @FXML private TextArea aboutText;
+    @FXML private Label labelVersion;
+    @FXML private Hyperlink licenceLink;
+    @FXML private Button buttonClose;
+
+    private Desktop desktop;
+
+    private String licenseURL;
+
+    private String contactURL;
+
+    private String appVersion;
+
+    public AboutController(
+           ScreensConfiguration screens,
+           Desktop desktop,
+           Properties appProperties) {
+
+        this.screens = screens;
+        this.desktop = desktop;
+        this.licenseURL = appProperties.getProperty("com.codesrc.scsi2sd.app.licence.url");
+               this.appVersion = appProperties.getProperty("com.codesrc.scsi2sd.app.version");
+    }
+
+    @Override
+    public void setWindow(FXWindow window) {
+        this.window = window;
+    }
+
+       @Override
+       public void onSceneCreating(Scene scene)
+       {
+       }
+
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        String about = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/COPYING")))
+                .lines().collect(Collectors.joining("\n"));
+
+        aboutText.setText(about);
+        labelVersion.setText(appVersion);
+
+        licenceLink.setTooltip(new Tooltip(licenseURL));
+        licenceLink.setOnAction(h -> desktop.openBrowser(licenseURL));
+
+        buttonClose.setOnAction(h -> window.close());
+    }
+
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java
new file mode 100644 (file)
index 0000000..27ccb1a
--- /dev/null
@@ -0,0 +1,221 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import com.codesrc.scsi2sd.document.ActiveDocument;
+import com.codesrc.scsi2sd.document.DocumentObserver;
+import com.codesrc.scsi2sd.model.*;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ChoiceBox;
+import javafx.scene.control.TextField;
+import org.controlsfx.control.ToggleSwitch;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * Created by michael on 28/05/17.
+ */
+public class ConfigController implements Initializable, DocumentObserver {
+
+    private ScreensConfiguration screens;
+    private ActiveDocument activeDocument;
+
+    @FXML private TextField cfgSelectionDelay;
+
+    @FXML private TextField cfgStartupDelay;
+
+    @FXML private CheckBox cfgEnableTerminator;
+
+    @FXML private ToggleSwitch cfgCheckParity;
+    @FXML private ToggleSwitch cfgUnitAtt;
+    @FXML private ToggleSwitch cfgScsi2;
+    @FXML private ToggleSwitch cfgSelPulses;
+    @FXML private ToggleSwitch cfgMapLuns;
+
+    @FXML private ChoiceBox speedLimit;
+
+    private BoardConfig boardConfig;
+
+    public ConfigController(ScreensConfiguration screens, ActiveDocument document) {
+        this.screens = screens;
+        this.activeDocument = document;
+        document.addObserver(this);
+    }
+
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        this.cfgSelectionDelay.setOnAction(e ->  this.setSelectionDelay());
+        this.cfgSelectionDelay.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSelectionDelay();});
+
+        this.cfgStartupDelay.setOnAction(e ->  this.setStartupDelay());
+        this.cfgStartupDelay.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setStartupDelay();});
+
+        this.cfgEnableTerminator.setOnAction(e ->  this.setTerminator());
+        this.cfgCheckParity.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_PARITY, this.cfgCheckParity));
+        this.cfgUnitAtt.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION, this.cfgUnitAtt));
+        this.cfgScsi2.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_SCSI2, this.cfgScsi2));
+        this.cfgSelPulses.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_SEL_LATCH, this.cfgSelPulses));
+        this.cfgMapLuns.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_MAP_LUNS_TO_IDS, this.cfgMapLuns));
+
+        this.speedLimit.getItems().addAll(Speed.values());
+        this.speedLimit.setOnAction(e -> this.setSpeedLimit());
+
+    }
+    public void documentLoaded(ActiveDocument document)
+    {
+        ConfigRoot root = document.getActiveDocument();
+        this.boardConfig = root.getBoardConfig();
+
+        if (this.boardConfig.getSelectionDelay() == 255)
+        {
+            cfgSelectionDelay.setText(null);
+        }
+        else {
+            cfgSelectionDelay.setText(String.valueOf(this.boardConfig.getSelectionDelay()));
+        }
+
+        if (this.boardConfig.getStartupDelay() == 0)
+        {
+            cfgStartupDelay.setText(null);
+        }
+        else {
+            cfgStartupDelay.setText(String.valueOf(this.boardConfig.getStartupDelay()));
+        }
+
+        cfgEnableTerminator.setSelected(this.boardConfig.isEnableTerminator());
+   // TODO check board type.     cfgEnableTerminator.setDisable(this.get);
+
+        cfgCheckParity.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_PARITY));
+        cfgUnitAtt.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION));
+        cfgScsi2.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_SCSI2));
+        cfgSelPulses.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_SEL_LATCH));
+        cfgMapLuns.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_MAP_LUNS_TO_IDS));
+
+        this.speedLimit.setValue(this.boardConfig.getScsiSpeed());
+    }
+
+    public void documentModified(ActiveDocument document, boolean isModified)
+    {
+
+    }
+    public void documentValid(ActiveDocument document, boolean isValid)
+    {
+
+    }
+
+    private void setFlag(Flag flag, ToggleSwitch control)
+    {
+        if (this.boardConfig != null)
+        {
+            this.boardConfig.setFlag(flag, control.isSelected());
+            this.activeDocument.setModified(this.getClass().getName(), true);
+        }
+    }
+
+    private void setTerminator()
+    {
+        if (this.boardConfig != null)
+        {
+            this.boardConfig.setFlag6(Flag6.S2S_CFG_ENABLE_TERMINATOR, this.cfgEnableTerminator.isSelected());
+            this.activeDocument.setModified(this.getClass().getName(), true);
+        }
+    }
+
+    private void setStartupDelay()
+    {
+        if (this.boardConfig != null)
+        {
+            var in = this.cfgStartupDelay.getText();
+            int intValue;
+
+            if (in == null || in.isEmpty())
+            {
+                intValue = 0;
+            }
+            else
+            {
+                var oldValue = this.boardConfig.getStartupDelay();
+
+                try {
+                    intValue = Integer.valueOf(in);
+                    if (intValue < 0 || intValue > 255)
+                    {
+                        this.cfgStartupDelay.setText(oldValue == 0 ? "" : String.valueOf(oldValue));
+                        return;
+                    }
+
+                } catch (NumberFormatException e) {
+                    this.cfgStartupDelay.setText(oldValue == 0 ? "" : String.valueOf(oldValue));
+                    return;
+                }
+            }
+
+            this.boardConfig.setStartupDelay(intValue);
+            this.cfgStartupDelay.setText(intValue == 0 ? "" : String.valueOf(intValue));
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setSelectionDelay()
+    {
+        if (this.boardConfig != null)
+        {
+            var in = this.cfgSelectionDelay.getText();
+            int intValue;
+
+            if (in == null || in.isEmpty())
+            {
+                intValue = 255;
+            }
+            else
+            {
+                var oldValue = this.boardConfig.getSelectionDelay();
+
+                try {
+                    intValue = Integer.valueOf(in);
+                    if (intValue < 0 || intValue > 255)
+                    {
+                        this.cfgSelectionDelay.setText(oldValue == 255 ? "" : String.valueOf(oldValue));
+                        return;
+                    }
+
+                } catch (NumberFormatException e) {
+                    this.cfgSelectionDelay.setText(oldValue == 255 ? "" : String.valueOf(oldValue));
+                    return;
+                }
+            }
+
+            this.boardConfig.setSelectionDelay(intValue);
+            this.cfgSelectionDelay.setText(intValue == 255 ? "" : String.valueOf(intValue));
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setSpeedLimit()
+    {
+        if (this.boardConfig != null)
+        {
+            this.boardConfig.setScsiSpeed((Speed)this.speedLimit.getValue());
+            this.activeDocument.setModified(this.getClass().getName(), true);
+        }
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java
new file mode 100644 (file)
index 0000000..6a918d8
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import com.codesrc.scsi2sd.io.UsbDeviceManager;
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.TabPane;
+import javafx.scene.control.TextArea;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+
+
+/**
+ * Created by michael on 21/05/17.
+ */
+public class DebugLogController implements FXController {
+    private static Logger LOGGER = LoggerFactory.getLogger(DebugLogController.class);
+
+    private ScreensConfiguration screens;
+    private FXWindow window;
+
+    private UsbDeviceManager usbDeviceManager;
+    private LogHandler logHandler;
+
+    @FXML private TextArea logTextArea;
+
+    public DebugLogController(ScreensConfiguration screens, UsbDeviceManager usbDeviceManager) {
+        this.screens = screens;
+        this.usbDeviceManager = usbDeviceManager;
+    }
+
+    @Override
+    public void setWindow(FXWindow window) {
+        this.window = window;
+    }
+
+       @Override
+       public void onSceneCreating(Scene scene)
+       {
+       }
+
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+
+        this.logHandler = new LogHandler(this.logTextArea);
+        logHandler.setLevel(Level.INFO);
+        java.util.logging.Logger usbLogger = java.util.logging.Logger.getLogger(UsbDeviceManager.class.getPackageName());
+
+        usbLogger.addHandler(this.logHandler);
+    }
+}
similarity index 57%
rename from src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java
rename to scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java
index 82c35c46c98b9f433eb85ed7e7b1a1157d4b0563..a9450194e49ed81c88eeb0b827688f5fa1b939d9 100644 (file)
@@ -1,39 +1,37 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
 //
-//     This file is part of SCSI2SD.
+// This file is part of scsi2sd-util.
 //
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
 //
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
 //
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
 
 package com.codesrc.scsi2sd.presentation;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
 
 import java.io.IOException;
 
 /**
  * Created by michael on 28/05/17.
  */
-@Component
 public class Desktop {
-    private static Logger LOGGER = LoggerFactory.getLogger(Desktop.class.getName());
+    private static Logger LOGGER = LoggerFactory.getLogger(Desktop.class);
 
     public enum Environment {
-        LINUX,
-        MAC,
-        WINDOWS,
+        Linux,
+        OSX,
+        Windows,
         OTHER
     }
 
@@ -42,11 +40,11 @@ public class Desktop {
     public Desktop() {
         String os = System.getProperty("os.name").toLowerCase();
         if (os.indexOf("windows") != -1 || os.indexOf("nt") != -1) {
-            environment = Environment.WINDOWS;
+            environment = Environment.Windows;
         } else if (os.indexOf("mac") != -1) {
-            environment = Environment.MAC;
+            environment = Environment.OSX;
         } else if (os.indexOf("linux") != -1) {
-            environment = Environment.LINUX;
+            environment = Environment.Linux;
         } else {
             environment = Environment.OTHER;
         }
@@ -58,15 +56,15 @@ public class Desktop {
         LOGGER.info("Launching " + url);
         try {
             switch (environment) {
-                case MAC:
+                case OSX:
                     Runtime.getRuntime().exec(new String[]{"open", url});
                     break;
 
-                case WINDOWS:
+                case Windows:
                     Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", url});
                     break;
 
-                case LINUX:
+                case Linux:
                 case OTHER:
                     Runtime.getRuntime().exec(new String[]{"xdg-open", url});
                     break;
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java
new file mode 100644 (file)
index 0000000..bc5f7d2
--- /dev/null
@@ -0,0 +1,616 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import com.codesrc.scsi2sd.document.ActiveDocument;
+import com.codesrc.scsi2sd.document.DocumentObserver;
+import com.codesrc.scsi2sd.io.UsbDeviceConnectedEvent;
+import com.codesrc.scsi2sd.io.UsbDeviceManager;
+import com.codesrc.scsi2sd.model.*;
+import javafx.application.Platform;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.*;
+import javafx.scene.layout.Background;
+import javafx.stage.StageStyle;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+public class DiskConfigController implements Initializable, DocumentObserver {
+
+    private ScreensConfiguration screens;
+    private ActiveDocument activeDocument;
+    private UsbDeviceManager usbDeviceManager;
+
+    @FXML
+    private TableView diskTableView;
+
+    @FXML
+    private Button newBtn;
+
+    @FXML
+    private Button deleteBtn;
+
+    @FXML private Button setSdCardSizeBtn;
+
+    @FXML private TextField scsiId;
+
+    @FXML private CheckBox enabled;
+
+    @FXML
+    private TextField scsiSizeStr;
+
+    @FXML private ChoiceBox scsiSizeUnit;
+
+    @FXML
+    private TextField scsiBytesPerSector;
+
+    @FXML
+    private TextField sdStartingByte;
+
+    @FXML ComboBox scsiDeviceType;
+
+    @FXML private TextField vendor;
+    @FXML private TextField prodId;
+    @FXML private TextField revision;
+    @FXML private TextField serial;
+
+    @FXML private TextField deviceTypeModifier;
+    @FXML private TextField sectorsPerTrack;
+    @FXML private TextField headsPerCylinder;
+    @FXML private ComboBox quirksMode;
+
+    private DiskList diskList;
+
+    private DiskConfig selectedConfig;
+
+    private int maxSupportedDevices = 4;
+
+    // data bound to the table view.
+    public class DiskListItem
+    {
+        private DiskConfig config;
+
+        public DiskListItem(DiskConfig config) {
+            this.config = config;
+        }
+
+        public long getSdSector() {
+            return config.getSdSectorStart();
+        }
+
+        public DiskConfig getConfig() {
+            return this.config;
+        }
+
+        public DiskConfig.Type getDiskType() {
+            return this.config.getType();
+        }
+
+        public String getSize() {
+            return this.config.getSizeString(this.config.getBestUnit(), true);
+        }
+
+        public String getId() {
+            return this.config.getType() == DiskConfig.Type.DISK ? Integer.toString(this.config.getScsiId()) : "";
+        }
+
+        public DeviceTypeEnum getDeviceType() { return this.config.getType() == DiskConfig.Type.DISK ? this.config.getDeviceType() : null; }
+
+        public boolean isEnabled() {
+            return this.config.isActive();
+        }
+    }
+
+    public DiskConfigController(ScreensConfiguration screens, ActiveDocument document, UsbDeviceManager usbDeviceManager) {
+        this.screens = screens;
+        this.activeDocument = document;
+        this.usbDeviceManager = usbDeviceManager;
+        document.addObserver(this);
+    }
+
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        this.usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbConnected(e)));
+
+        this.newBtn.setOnAction((e) -> this.addNew());
+        this.deleteBtn.setOnAction((e) -> this.delete());
+        this.setSdCardSizeBtn.setOnAction((e) -> this.setSdCardSize());
+
+        this.scsiId.setOnAction((e) -> this.setScsiId());
+        this.scsiId.setTooltip(new Tooltip("SCSI ID 0 to 7"));
+        this.scsiId.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setScsiId();});
+
+        this.enabled.setOnAction((e) -> this.setEnabled());
+
+        this.scsiSizeUnit.setOnAction((e) -> this.setSizeUnits());
+        this.scsiSizeStr.setOnAction((e) -> this.setSize());
+        this.scsiSizeStr.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSize();});
+
+        this.scsiBytesPerSector.setOnAction((e) -> this.setBytesPerSector());
+        this.scsiBytesPerSector.setTooltip(new Tooltip("Normally 512 bytes for hard drives or 2048 bytes for CDROM devices"));
+        this.scsiBytesPerSector.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setBytesPerSector();});
+
+        this.scsiDeviceType.setOnAction((e) -> this.setDeviceType());
+
+        this.vendor.setOnAction((e) -> this.setVendor());
+        this.vendor.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setVendor();});
+
+        this.prodId.setOnAction((e) -> this.setProdId());
+        this.prodId.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setProdId();});
+
+        this.revision.setOnAction((e) -> this.setRevision());
+        this.revision.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setRevision();});
+
+        this.serial.setOnAction((e) -> this.setSerial());
+        this.serial.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSerial();});
+
+        this.deviceTypeModifier.setOnAction((e) -> this.setDeviceTypeModifier());
+        this.deviceTypeModifier.setTooltip(new Tooltip("SCSI device type modifier field, usually 0."));
+        this.deviceTypeModifier.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setDeviceTypeModifier();});
+
+        this.sectorsPerTrack.setOnAction((e) -> this.setSectorsPerTrack());
+        this.sectorsPerTrack.setTooltip(new Tooltip("Modify emulated disk geometry"));
+        this.sectorsPerTrack.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSectorsPerTrack();});
+
+        this.headsPerCylinder.setOnAction((e) -> this.setHeadsPerCylinder());
+        this.headsPerCylinder.setTooltip(new Tooltip("Modify emulated disk geometry"));
+        this.headsPerCylinder.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setHeadsPerCylinder();});
+
+        this.quirksMode.setOnAction((e) -> this.setQuirksMode());
+
+        this.newBtn.setDisable(true);
+        this.deleteBtn.setDisable(true);
+        this.setSdCardSizeBtn.setDisable(true);
+
+        this.scsiSizeUnit.getItems().addAll(DiskUnit.values());
+        this.scsiDeviceType.getItems().addAll(Arrays.stream(DeviceTypeEnum.values()).filter(dt -> dt != DeviceTypeEnum.S2S_CFG_SEQUENTIAL).toArray());
+
+        this.quirksMode.getItems().addAll(Quirks.values());
+
+        this.diskTableView.getSelectionModel().selectedItemProperty().addListener((obs, oldSel, newSel) -> {
+            this.selectRow(newSel);
+        });
+    }
+
+    private void handleUsbConnected(UsbDeviceConnectedEvent usbDeviceConnectedEvent) {
+        this.maxSupportedDevices = usbDeviceConnectedEvent.getUsbDevice().getMaxDisks();
+    }
+
+    public void documentLoaded(ActiveDocument document)
+    {
+        ConfigRoot root = document.getActiveDocument();
+        this.diskList = root.getDiskList();
+
+        ObservableList<DiskListItem> data = diskTableView.getItems();
+        data.clear();
+        for (var config : diskList.getDisks())
+        {
+            data.add(new DiskListItem(config));
+        }
+
+        // this.setSdCardSizeBtn.setDisable(false);
+    }
+
+    public void documentModified(ActiveDocument document, boolean isModified)
+    {
+
+    }
+
+    public void documentValid(ActiveDocument document, boolean isValid)
+    {
+
+    }
+
+    private void selectRow(Object item) {
+        if (item == null)
+        {
+            this.selectedConfig = null;
+        }
+        else
+        {
+            this.selectedConfig = ((DiskListItem) item).getConfig();
+
+            var numDevices = this.diskList.getDisks().stream().filter(c -> c.getType() == DiskConfig.Type.DISK).count();
+            this.newBtn.setDisable(this.selectedConfig.getType() != DiskConfig.Type.EMPTY || numDevices >= this.maxSupportedDevices);
+            this.deleteBtn.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.scsiId.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.enabled.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.scsiSizeStr.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.scsiBytesPerSector.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.scsiDeviceType.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.vendor.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.prodId.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.revision.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.serial.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.deviceTypeModifier.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.sectorsPerTrack.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.headsPerCylinder.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+            this.quirksMode.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK);
+
+            this.scsiId.setText(this.selectedConfig.getType() == DiskConfig.Type.DISK ? Integer.toString(this.selectedConfig.getScsiId()) : "");
+            this.enabled.setSelected(this.selectedConfig.isActive());
+            var bestUnit = this.selectedConfig.getBestUnit();
+            this.scsiSizeUnit.setValue(bestUnit);
+            this.scsiSizeStr.setText(this.selectedConfig.getSizeString(bestUnit, false));
+            this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector()));
+            this.sdStartingByte.setText(Long.toString(this.selectedConfig.getSdSectorStart() * DiskConfig.SD_SECTOR_SIZE));
+            this.scsiDeviceType.setValue(this.selectedConfig.getDeviceType());
+            this.deviceTypeModifier.setText(String.valueOf(this.selectedConfig.getDeviceTypeModifier()));
+            this.vendor.setText(this.selectedConfig.getVendor());
+            this.prodId.setText(this.selectedConfig.getProdId());
+            this.revision.setText(this.selectedConfig.getRevision());
+            this.serial.setText(this.selectedConfig.getSerial());
+            this.sectorsPerTrack.setText(String.valueOf(this.selectedConfig.getSectorsPerTrack()));
+            this.headsPerCylinder.setText(String.valueOf(this.selectedConfig.getHeadsPerCylinder()));
+            this.quirksMode.setValue(this.selectedConfig.getQuirksMode());
+
+        }
+    }
+
+    private void addNew()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.EMPTY)
+        {
+            var newConfig = new DiskConfig(DiskConfig.Type.DISK, this.selectedConfig.getSdSectorStart(), this.selectedConfig.getSdSectors());
+            this.diskList.add(newConfig);
+        }
+
+        this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        this.refreshList();
+    }
+
+    private void delete()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK)
+        {
+            this.diskList.remove(this.selectedConfig);
+        }
+
+        this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        this.refreshList();
+    }
+
+    private void refreshList()
+    {
+        var oldSelectedConfig = this.selectedConfig;
+        ObservableList<DiskListItem> data = diskTableView.getItems();
+        data.clear(); // Will call the row selection handler and set this.selectedConfig to null
+
+        for (var config : diskList.getDisks())
+        {
+            var tableItem = new DiskListItem(config);
+            data.add(tableItem);
+
+            if (oldSelectedConfig == config) {
+                diskTableView.getSelectionModel().select(tableItem);
+            }
+        }
+    }
+
+    private void setScsiId()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) {
+            int value;
+            try {
+                value = Integer.parseUnsignedInt(this.scsiId.getText());
+                if (value >= 0 && value <= 7) {
+                    this.selectedConfig.setScsiId(value);
+                }
+            } catch (NumberFormatException e) {
+            }
+
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+            this.refreshList();
+        }
+    }
+
+    private void setEnabled()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) {
+            this.selectedConfig.setActive(this.enabled.isSelected());
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+            this.refreshList();
+        }
+    }
+
+
+    private void setSizeUnits()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.scsiSizeStr.setText(this.selectedConfig.getSizeString((DiskUnit)this.scsiSizeUnit.getValue(), false));
+        }
+    }
+
+    private void setSize()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK)
+        {
+            double value;
+            try
+            {
+                value = Double.parseDouble(this.scsiSizeStr.getText());
+                if (value <= 0)
+                {
+                    this.setSizeUnits();
+                    return;
+                }
+            }
+            catch (NumberFormatException e)
+            {
+                this.setSizeUnits();
+                return;
+            }
+
+            var units = (DiskUnit)this.scsiSizeUnit.getValue();
+            var bytes = 0l;
+            switch (units)
+            {
+                case SECTOR: bytes = (long)value * this.selectedConfig.getBytesPerSector(); break;
+                case MB: bytes = (long)(value * 1024 * 1024); break;
+                case GB: bytes = (long)(value * 1024 * 1024 * 1024); break;
+            }
+
+            this.diskList.remove(this.selectedConfig);
+            var origScsiSectors = this.selectedConfig.getScsiSectors();
+            this.selectedConfig.setScsiSectors(bytes / this.selectedConfig.getBytesPerSector());
+            if (!this.diskList.add(this.selectedConfig))
+            {
+                this.selectedConfig.setScsiSectors(origScsiSectors);
+                this.diskList.add(this.selectedConfig);
+            }
+
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+            this.refreshList();
+        }
+    }
+
+    private void setBytesPerSector()
+    {
+        if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK)
+        {
+            int value;
+            try
+            {
+                value = Integer.parseUnsignedInt(this.scsiBytesPerSector.getText());
+
+                if (value <= 0)
+                {
+                    this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector()));
+                    return;
+                }
+            }
+            catch (NumberFormatException e)
+            {
+                this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector()));
+                return;
+            }
+
+            this.diskList.remove(this.selectedConfig);
+
+            var origBytesPerSector = this.selectedConfig.getBytesPerSector();
+            var origScsiSectors = this.selectedConfig.getScsiSectors();
+            var bytes = origBytesPerSector * origScsiSectors;
+
+            this.selectedConfig.setScsiSectors(bytes / value);
+            this.selectedConfig.setBytesPerSector(value);
+
+            if (!this.diskList.add(this.selectedConfig))
+            {
+                this.selectedConfig.setScsiSectors(origScsiSectors);
+                this.selectedConfig.setBytesPerSector(origBytesPerSector);
+
+                this.diskList.add(this.selectedConfig);
+            }
+
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+            this.refreshList();
+        }
+    }
+
+    private void setDeviceType()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setDeviceType((DeviceTypeEnum)this.scsiDeviceType.getValue());
+
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+            this.refreshList();
+        }
+    }
+
+    private void setVendor()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setVendor(this.vendor.getText());
+            this.vendor.setText(this.selectedConfig.getVendor()); // Fix padding
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setProdId()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setProdId(this.prodId.getText());
+            this.prodId.setText(this.selectedConfig.getProdId()); // Fix padding
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setRevision()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setRevision(this.revision.getText());
+            this.revision.setText(this.selectedConfig.getRevision()); // Fix padding
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setSerial()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setSerial(this.serial.getText());
+            this.serial.setText(this.selectedConfig.getSerial()); // Fix padding
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setSdCardSize() {
+        if (this.diskList != null) {
+            TextInputDialog dialog = new TextInputDialog(String.valueOf(this.diskList.getSdCardSizeGb()));
+            dialog.setTitle("SD Card Size");
+            dialog.setHeaderText(null);
+            dialog.setContentText("Set SD card Size (GB):");
+            dialog.initStyle(StageStyle.UTILITY); // remove icons
+
+            Optional<String> result = dialog.showAndWait();
+            if (result.isPresent()) {
+                double value;
+                try
+                {
+                    value = Double.parseDouble(result.get());
+
+                    if (value <= 0 || value > 1999)
+                    {
+                        return;
+                    }
+                }
+                catch (NumberFormatException e)
+                {
+                    return;
+                }
+
+                this.diskList.resetSizeGb(value);
+                this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+                this.refreshList();
+            }
+        }
+    }
+
+    private void setDeviceTypeModifier()
+    {
+        if (this.selectedConfig != null)
+        {
+            var in = this.deviceTypeModifier.getText();
+            int intValue;
+
+            if (in == null || in.isEmpty())
+            {
+                intValue = 0;
+            }
+            else
+            {
+                var oldValue = this.selectedConfig.getDeviceTypeModifier();
+
+                try {
+                    intValue = Integer.valueOf(in);
+                    if (intValue < 0 || intValue > 255)
+                    {
+                        this.deviceTypeModifier.setText(String.valueOf(oldValue));
+                        return;
+                    }
+
+                } catch (NumberFormatException e) {
+                    this.deviceTypeModifier.setText(String.valueOf(oldValue));
+                    return;
+                }
+            }
+
+            this.selectedConfig.setDeviceTypeModifier(intValue);
+            this.deviceTypeModifier.setText(String.valueOf(intValue));
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setSectorsPerTrack()
+    {
+        if (this.selectedConfig != null)
+        {
+            var in = this.sectorsPerTrack.getText();
+            int intValue;
+
+            var oldValue = this.selectedConfig.getSectorsPerTrack();
+
+            try {
+                intValue = Integer.valueOf(in);
+                if (intValue < 1 || intValue > 65535)
+                {
+                    this.sectorsPerTrack.setText(String.valueOf(oldValue));
+                    return;
+                }
+
+            } catch (NumberFormatException e) {
+                this.sectorsPerTrack.setText(String.valueOf(oldValue));
+                return;
+            }
+
+            this.selectedConfig.setSectorsPerTrack(intValue);
+            this.sectorsPerTrack.setText(String.valueOf(intValue));
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+    private void setHeadsPerCylinder()
+    {
+        if (this.selectedConfig != null)
+        {
+            var in = this.headsPerCylinder.getText();
+            int intValue;
+
+            var oldValue = this.selectedConfig.getHeadsPerCylinder();
+
+            try {
+                intValue = Integer.valueOf(in);
+                if (intValue < 1 || intValue > 65535)
+                {
+                    this.headsPerCylinder.setText(String.valueOf(oldValue));
+                    return;
+                }
+
+            } catch (NumberFormatException e) {
+                this.headsPerCylinder.setText(String.valueOf(oldValue));
+                return;
+            }
+
+            this.selectedConfig.setHeadsPerCylinder(intValue);
+            this.headsPerCylinder.setText(String.valueOf(intValue));
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+
+    private void setQuirksMode()
+    {
+        if (this.selectedConfig != null)
+        {
+            this.selectedConfig.setQuirksMode((Quirks)this.quirksMode.getValue());
+            this.activeDocument.setModified(DiskConfigController.class.getName(), true);
+        }
+    }
+
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java
new file mode 100644 (file)
index 0000000..3978bcc
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import javafx.fxml.Initializable;
+import javafx.scene.Scene;
+
+/**
+ * Created by michael on 27/05/17.
+ */
+public interface FXController extends Initializable {
+       void setWindow(FXWindow window);
+       void onSceneCreating(Scene scene);
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java
new file mode 100644 (file)
index 0000000..61decd3
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.App;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import javafx.stage.Window;
+import javafx.util.Callback;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Created by michael on 27/05/17.
+ */
+public class FXWindow extends Stage {
+
+    public FXWindow(Callback<Class<?>,Object> controllerFactory, URL fxml, String title, Window owner, StageStyle style) {
+        super(style);
+        initOwner(owner);
+        initModality(Modality.WINDOW_MODAL);
+        getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-16.png")));
+        getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-32.png")));
+        getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-256.png")));
+        setTitle(title);
+
+
+        FXMLLoader loader = new FXMLLoader(fxml);
+        try {
+            loader.setControllerFactory(controllerFactory);
+            Parent root = loader.load();
+            Object controller = loader.getController();
+                       if (controller instanceof FXController)
+                       {
+                ((FXController)controller).setWindow(this);
+                       }
+
+            Scene scene = new Scene(root);
+               
+                       if (controller instanceof FXController)
+                       {
+                ((FXController)controller).onSceneCreating(scene);
+                       }
+
+            setScene(scene);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java
new file mode 100644 (file)
index 0000000..c7d4962
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import javafx.application.Platform;
+import javafx.scene.control.TextArea;
+
+import java.time.format.DateTimeFormatter;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+public class LogHandler extends Handler {
+    private TextArea logTextArea;
+
+    public LogHandler(TextArea logTextArea) {
+        this.logTextArea = logTextArea;
+    }
+
+    @Override
+    public void publish(LogRecord logRecord) {
+        Platform.runLater(() -> {
+            logTextArea.appendText(DateTimeFormatter.ISO_INSTANT.format(logRecord.getInstant()) + " " + logRecord.getMessage() + "\n");
+        });
+    }
+
+    @Override
+    public void flush() {
+
+    }
+
+    @Override
+    public void close() throws SecurityException {
+
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java
new file mode 100644 (file)
index 0000000..a83bf96
--- /dev/null
@@ -0,0 +1,455 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import com.codesrc.scsi2sd.document.ActiveDocument;
+import com.codesrc.scsi2sd.document.DocumentObserver;
+import com.codesrc.scsi2sd.io.UsbDevice;
+import com.codesrc.scsi2sd.io.UsbDeviceManager;
+import com.codesrc.scsi2sd.model.BoardConfig;
+import com.codesrc.scsi2sd.model.ConfigRoot;
+import com.codesrc.scsi2sd.model.DiskConfig;
+import com.codesrc.scsi2sd.model.DiskList;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.*;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.FileChooser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
+
+
+/**
+ * Created by michael on 21/05/17.
+ */
+public class MainController implements FXController, DocumentObserver {
+    public static final String Title = "codesrc SCSI2SD";
+    private static Logger LOGGER = LoggerFactory.getLogger(MainController.class);
+    private final String RECENT = "recent_items";
+    private final int RECENT_MAX = 10;
+
+    private ScreensConfiguration screens;
+    private FXWindow window;
+    private ActiveDocument activeDocument;
+
+    private UsbDeviceManager usbDeviceManager;
+
+    private Preferences prefs;
+    private Deque<String> recent;
+
+    @FXML private Menu menuRecent;
+
+    @FXML private MenuItem menuAbout;
+    @FXML private MenuItem menuQuit;
+
+    @FXML private MenuItem menuOpenDevice;
+    @FXML private MenuItem menuSaveDevice;
+    @FXML private MenuItem menuOpenFile;
+    @FXML private MenuItem toolbarOpenFile;
+    @FXML private MenuItem menuSaveFile;
+    @FXML private MenuItem toolbarSaveFile;
+    @FXML private MenuItem menuRevert;
+    @FXML private Button buttonRevert;
+
+    @FXML private TabPane mainTabPane;
+
+    @FXML private AnchorPane debugLog;
+    @FXML private DebugLogController debugLogController;
+
+    public MainController(
+            ScreensConfiguration screens,
+            UsbDeviceManager usbDeviceManager,
+            ActiveDocument activeDocument) {
+        this.screens = screens;
+        this.usbDeviceManager = usbDeviceManager;
+        this.activeDocument = activeDocument;
+        this.activeDocument.addObserver(this);
+
+        this.recent = new ArrayDeque<>();
+    }
+
+    @Override
+    public void setWindow(FXWindow window) {
+        this.window = window;
+        this.window.setOnCloseRequest(e -> this.exit());
+    }
+
+       @Override
+       public void onSceneCreating(Scene scene)
+       {
+               URL stylesheet = MainController.class.getResource("/css/main.css");
+               scene.getStylesheets().add(stylesheet.toString());
+       }
+
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        menuQuit.setOnAction(h -> this.exit());
+        menuAbout.setOnAction(h -> screens.aboutWindow(window).showAndWait());
+        menuOpenDevice.setOnAction(h -> loadConfigFromDevice());
+        menuSaveDevice.setOnAction(h -> saveConfigToDevice());
+        menuOpenFile.setOnAction(h -> loadConfigFromFile());
+        toolbarOpenFile.setOnAction(h -> loadConfigFromFile());
+        menuSaveFile.setOnAction(h -> saveConfigToFile());
+        toolbarSaveFile.setOnAction(h -> saveConfigToFile());
+        menuRevert.setOnAction(h -> revert());
+        buttonRevert.setOnAction(h -> revert());
+
+        menuOpenDevice.setDisable(true);
+        menuSaveDevice.setDisable(true);
+
+        this.prefs = Preferences.userNodeForPackage(MainController.class);
+        this.populateRecentFromPrefs();
+
+        this.loadDefault();
+
+        this.usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleDeviceConnectedEvent(e.getUsbDevice())));
+        this.usbDeviceManager.getDeviceDisconnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleDeviceDisconnectedEvent(e.getUsbDevice())));
+
+        List<UsbDevice> deviceList = usbDeviceManager.getDevices();
+        if (!deviceList.isEmpty()) {
+            handleDeviceConnectedEvent(deviceList.get(0));
+        }
+    }
+
+    private void exit()
+    {
+        usbDeviceManager.shutdown();
+        System.exit(0);
+    }
+
+    private void loadConfigFromDevice() {
+        List<UsbDevice> deviceList = this.usbDeviceManager.getDevices();
+        if (!deviceList.isEmpty())
+        {
+            this.loadConfigFromDevice(deviceList.get(0));
+        }
+    }
+
+    private void handleDeviceDisconnectedEvent(UsbDevice device)
+    {
+        this.menuOpenDevice.setDisable(true);
+        this.menuSaveDevice.setDisable(true);
+    }
+
+    private void handleDeviceConnectedEvent(UsbDevice device)
+    {
+        this.menuOpenDevice.setDisable(false);
+        this.loadConfigFromDevice(device);
+        this.menuSaveDevice.setDisable(false);
+    }
+
+    private void loadConfigFromDevice(UsbDevice device) {
+        BoardConfig boardConfig = new BoardConfig(device.readBoardConfig());
+        ConfigRoot root = new ConfigRoot();
+        root.setBoardConfig(boardConfig);
+
+        var lastSectorUsed = 0l;
+        var configs = new ArrayList<DiskConfig>();
+        for (var i = 0; i < device.getMaxDisks(); ++i) {
+            var disk = new DiskConfig(device.readDiskConfig(i));
+            if (!disk.isActive() && disk.getSdSectorStart() == 0 && disk.getScsiSectors() == 0)
+            {
+                continue; // dummy entry.
+            }
+
+            lastSectorUsed = Math.max(lastSectorUsed, disk.getSdSectorEnd());
+            configs.add(disk);
+        }
+
+        var sdCapacity = device.getSdCapacity();
+        var fallbackSize = Math.max(2l*1024*1024*1024/512, lastSectorUsed + DiskList.reservedSize);
+        var diskList = new DiskList(sdCapacity > 0 ? sdCapacity : fallbackSize);
+        configs.forEach(c -> diskList.add(c));
+
+        root.setDiskList(diskList);
+
+        activeDocument.loadDocument(root, null);
+    }
+
+    private void saveConfigToDevice()
+    {
+        var device = this.getDevice();
+        if (device == null)
+        {
+            return;
+        }
+
+        var root = this.activeDocument.getActiveDocument();
+        device.saveBoardConfig(root.getBoardConfig().asBytes());
+
+        var configs = root.getDiskList().getDisks().stream()
+                .filter(d -> d.getType() == DiskConfig.Type.DISK)
+                .collect(Collectors.toList());
+
+        for (var i = 0; i < device.getMaxDisks(); ++i)
+        {
+            if (i < configs.size())
+            {
+                device.saveDiskConfig(i, configs.get(i).asBytes());
+            }
+            else
+            {
+                // Save a 0 byte dummy
+                var dummy = new DiskConfig(DiskConfig.Type.DISK, 0, 0);
+                dummy.setActive(false);
+                device.saveDiskConfig(i, dummy.asBytes());
+            }
+        }
+        device.saveCommit();
+        this.window.setTitle(Title);
+    }
+
+    @Override
+    public void documentLoaded(ActiveDocument document)
+    {
+        if (this.window != null) {
+            this.window.setTitle(Title);
+        }
+        this.enableDisableSave();
+    }
+
+    @Override
+    public void documentModified(ActiveDocument document, boolean isModified)
+    {
+        this.window.setTitle("*" + Title);
+        this.enableDisableSave();
+    }
+
+    @Override
+    public void documentValid(ActiveDocument document, boolean isValid)
+    {
+        this.enableDisableSave();
+    }
+
+    private void enableDisableSave()
+    {
+        var device = this.getDevice();
+        this.menuSaveDevice.setDisable(!this.activeDocument.isValid() || device == null || !this.activeDocument.isModified());
+    }
+
+    private UsbDevice getDevice()
+    {
+        List<UsbDevice> deviceList = usbDeviceManager.getDevices();
+        if (deviceList.isEmpty())
+        {
+            return null;
+        }
+        return deviceList.get(0);
+    }
+
+    private void loadConfigFromFile() {
+        FileChooser fileChooser = new FileChooser();
+        fileChooser.setTitle("Open Config File");
+        fileChooser.getExtensionFilters().addAll(
+                new FileChooser.ExtensionFilter("XML", "*.xml"),
+                new FileChooser.ExtensionFilter("All Files", "*.*")
+        );
+
+        File theFile = fileChooser.showOpenDialog(this.window);
+        if (theFile == null) {
+            return; // User canceled.
+        }
+        this.loadConfigFromFile(theFile);
+    }
+
+    private void loadConfigFromFile(File theFile)
+    {
+        try {
+            var builder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder();
+            var doc = builder.parse(theFile);
+            BoardConfig boardConfig;
+
+            if (doc.hasChildNodes()
+                && doc.getFirstChild().getNodeName() == "SCSI2SD")
+            {
+                var boardConfigNode = doc.getElementsByTagName("S2S_BoardCfg");
+                if (boardConfigNode != null && boardConfigNode.getLength() > 0)
+                {
+                    // do stuff
+                    boardConfig = new BoardConfig(boardConfigNode.item(0));
+                }
+                else
+                {
+                    throw new IllegalArgumentException("Missing S2S_BoardCfg node");
+                }
+
+                ConfigRoot root = new ConfigRoot();
+                root.setBoardConfig(boardConfig);
+
+                int maxDisks = 7;
+                var sdCapacity = 0;
+                List<UsbDevice> deviceList = this.usbDeviceManager.getDevices();
+                if (!deviceList.isEmpty())
+                {
+                    maxDisks = deviceList.get(0).getMaxDisks();
+                    sdCapacity = deviceList.get(0).getSdCapacity();
+                }
+
+                var lastSectorUsed = 0l;
+                var configs = new ArrayList<DiskConfig>();
+                var diskNodeList = doc.getElementsByTagName("SCSITarget");
+                for (var i = 0; i < maxDisks && i < diskNodeList.getLength(); ++i) {
+                    var disk = new DiskConfig(diskNodeList.item(i));
+                    lastSectorUsed = Math.max(lastSectorUsed, disk.getSdSectorEnd());
+                    configs.add(disk);
+                }
+
+                var fallbackSize = Math.max(2l*1024*1024*1024/512, lastSectorUsed + DiskList.reservedSize);
+                var diskList = new DiskList(sdCapacity > 0 ? sdCapacity : fallbackSize);
+                configs.forEach(c -> diskList.add(c));
+
+                root.setDiskList(diskList);
+
+                activeDocument.loadDocument(root, theFile.getCanonicalPath());
+                this.saveRecent(theFile.getCanonicalPath());
+            }
+            else {
+                throw new IllegalArgumentException("Missing SCSI2SD node");
+            }
+
+        } catch (Exception e) {
+            Alert alert = new Alert(Alert.AlertType.ERROR);
+            alert.setTitle("Load failed");
+            alert.setContentText(e.getLocalizedMessage());
+            alert.show();
+        }
+    }
+
+    private void saveConfigToFile()
+    {
+        var root = this.activeDocument.getActiveDocument();
+
+        var builder = new StringBuilder();
+        builder.append("<SCSI2SD>\n");
+        builder.append(root.getBoardConfig().toXML());
+
+        root.getDiskList().getDisks().stream()
+                .filter(c -> c.getType() == DiskConfig.Type.DISK)
+                .forEach(c -> builder.append(c.toXML()));
+
+        builder.append("</SCSI2SD>\n");
+
+
+        FileChooser fileChooser = new FileChooser();
+        fileChooser.setTitle("Save Config File");
+        fileChooser.getExtensionFilters().addAll(
+                new FileChooser.ExtensionFilter("XML", "*.xml"),
+                new FileChooser.ExtensionFilter("All Files", "*.*")
+        );
+
+        var filename = this.activeDocument.getOriginalDocumentName();
+        if (filename != null) {
+            Path path = Path.of(filename);
+            fileChooser.setInitialDirectory(path.getParent().toFile());
+            fileChooser.setInitialFileName(path.getFileName().toFile().toString());
+        }
+
+        File theFile = fileChooser.showSaveDialog(this.window);
+        if (theFile == null)
+        {
+            return; // User cancelled.
+        }
+
+        try {
+            var path = theFile.toPath();
+            if (fileChooser.getSelectedExtensionFilter().getExtensions().contains("*.xml") &&
+                    !path.getFileName().toString().toLowerCase().endsWith(".xml"))
+            {
+                path = Path.of(path.toString() + ".xml");
+            }
+
+            Files.write(path, builder.toString().getBytes(StandardCharsets.US_ASCII));
+
+            this.window.setTitle(Title);
+            this.saveRecent(path.toString());
+
+        } catch (Exception e) {
+            Alert alert = new Alert(Alert.AlertType.ERROR);
+            alert.setTitle("Save failed");
+            alert.setContentText(e.getLocalizedMessage());
+            alert.show();
+        }
+    }
+
+    private void revert() {
+        var orig = this.activeDocument.getOriginalDocument();
+        if (orig != null) {
+            var revertedDocument = new ConfigRoot(orig);
+            this.activeDocument.loadDocument(revertedDocument, this.activeDocument.getOriginalDocumentName());
+        }
+    }
+
+    private void loadDefault()
+    {
+        var root = new ConfigRoot();
+        root.setBoardConfig(new BoardConfig());
+        root.setDiskList(new DiskList(2l*1024*1024*1024/512));
+        this.activeDocument.loadDocument(root, null);
+    }
+
+    private void populateRecentFromPrefs() {
+        this.recent.clear();
+        for (int i = 0; i < RECENT_MAX; ++i) {
+            var prop = prefs.get(RECENT + i, null);
+            if (prop != null) {
+                this.recent.addLast(prop);
+            }
+        }
+
+        this.setRecentMenuItems();
+    }
+
+    private void saveRecent(String newFile) {
+        this.recent.remove(newFile);
+        this.recent.addFirst(newFile);
+
+        while (recent.size() > RECENT_MAX) {
+            this.recent.removeLast();
+        }
+
+        var i = 0;
+        for (var file : this.recent) {
+            prefs.put(RECENT + i, file);
+            ++i;
+        }
+
+        this.setRecentMenuItems();
+    }
+
+    private void setRecentMenuItems() {
+        this.menuRecent.getItems().clear();
+        for (var file : this.recent) {
+            var menuItem = new MenuItem(file);
+            menuItem.setOnAction(h -> this.loadConfigFromFile(new File(file)));
+            this.menuRecent.getItems().add(menuItem);
+        }
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java
new file mode 100644 (file)
index 0000000..7248fdd
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+package com.codesrc.scsi2sd.presentation;
+
+import com.codesrc.scsi2sd.ScreensConfiguration;
+import com.codesrc.scsi2sd.io.*;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.Scene;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.List;
+import java.util.ResourceBundle;
+
+public class StatusTabController implements FXController {
+    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(StatusTabController.class);
+
+    private ScreensConfiguration screens;
+    private FXWindow window;
+
+    private UsbDeviceManager usbDeviceManager;
+
+    @FXML private Label status;
+    @FXML private Label hwVersion;
+    @FXML private Label fwVersion;
+    @FXML private Label sdCapacity;
+
+    public StatusTabController(ScreensConfiguration screens, UsbDeviceManager usbDeviceManager) {
+        this.screens = screens;
+        this.usbDeviceManager = usbDeviceManager;
+    }
+
+    @Override
+    public void setWindow(FXWindow window) {
+        this.window = window;
+    }
+
+       @Override
+       public void onSceneCreating(Scene scene)
+       {
+       }
+
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        status.setText("No device");
+
+        usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbConnected(e)));
+        usbDeviceManager.getDeviceDisconnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbDisconnected(e)));
+        usbDeviceManager.getDeviceFailedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbFailedEvent(e)));
+
+        List<UsbDevice> deviceList = usbDeviceManager.getDevices();
+        if (!deviceList.isEmpty()) {
+            updateStatus(deviceList.get(0));
+        }
+    }
+
+    public void handleUsbConnected(UsbDeviceConnectedEvent usbDeviceConnectedEvent) {
+        updateStatus(usbDeviceConnectedEvent.getUsbDevice());
+    }
+
+    private void updateStatus(UsbDevice device) {
+
+        status.setText("Connected");
+
+        hwVersion.setText(device.getHardwareDescription());
+        fwVersion.setText(device.getFirmwareDescription());
+
+        StringBuilder sdSizeStr = new StringBuilder();
+        long sectors = device.getSdCapacity();
+        float gbSize = sectors * 512.0f / 1024 / 1024 / 1024;
+        sdSizeStr.append(String.format("%.1f", gbSize)).append("GiB")
+                .append(" (").append(sectors).append(" sectors)");
+        if (sectors < (2 * 1024 * 1024 *1024 / 512)) {
+            sdSizeStr.append(" ! Obsolete Card");
+        }
+        sdCapacity.setText(sdSizeStr.toString());
+    }
+
+    public void handleUsbDisconnected(UsbDeviceDisconnectedEvent usbDeviceDisconnectedEvent) {
+        status.setText("Disconnected");
+    }
+
+    private void handleUsbFailedEvent(UsbDeviceFailedEvent usbDeviceFailedEvent) {
+        status.setText("Failed: " + usbDeviceFailedEvent.getUsbDevicePath() + ". Error: " + usbDeviceFailedEvent.getMessage());
+    }
+}
diff --git a/scsi2sd.ui/src/main/java/module-info.java b/scsi2sd.ui/src/main/java/module-info.java
new file mode 100644 (file)
index 0000000..07130b8
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 Michael McMaster <michael@codesrc.com>
+//
+// This file is part of scsi2sd-util.
+//
+// scsi2sd-util is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// scsi2sd-util is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with scsi2sd-util.  If not, see <https://www.gnu.org/licenses/>.
+
+module com.codesrc.scsi2sd.ui {
+       exports com.codesrc.scsi2sd;
+
+       opens com.codesrc.scsi2sd.presentation to javafx.fxml, javafx.base;
+
+    requires java.logging;
+    requires org.controlsfx.controls;
+    requires javafx.controls;
+    requires joptsimple;
+    requires org.slf4j;
+       requires org.slf4j.jul;
+       requires java.xml;
+       requires java.prefs;
+
+    requires transitive com.codesrc.scsi2sd.io;
+    requires transitive javafx.base;
+    requires transitive javafx.fxml;
+    requires transitive javafx.graphics;
+}
diff --git a/scsi2sd.ui/src/main/resources/app.properties b/scsi2sd.ui/src/main/resources/app.properties
new file mode 100644 (file)
index 0000000..25a7b50
--- /dev/null
@@ -0,0 +1,5 @@
+
+com.codesrc.scsi2sd.app.version = 1.0-BETA
+
+com.codesrc.scsi2sd.app.licence.type = GPLv3
+com.codesrc.scsi2sd.app.licence.url = https://www.gnu.org/licenses/gpl-3.0.html
diff --git a/scsi2sd.ui/src/main/resources/css/main.css b/scsi2sd.ui/src/main/resources/css/main.css
new file mode 100644 (file)
index 0000000..69a8267
--- /dev/null
@@ -0,0 +1,3 @@
+.root {
+    -fx-background-color: #FFFFFF;
+ }
\ No newline at end of file
diff --git a/scsi2sd.ui/src/main/resources/css/splash.css b/scsi2sd.ui/src/main/resources/css/splash.css
new file mode 100644 (file)
index 0000000..152752f
--- /dev/null
@@ -0,0 +1,6 @@
+
+.root {
+    -fx-border-style: solid;
+    -fx-border-color: #AAAAAA;
+    -fx-border-width: 1;
+ }
\ No newline at end of file
similarity index 81%
rename from src/main/resources/fx/about.fxml
rename to scsi2sd.ui/src/main/resources/fx/about.fxml
index 8faaec14d181efa17fa0427eab4555e5c768d31d..e3c148f40135e18b6f6a1e89e6e2c08d93f0c31e 100644 (file)
@@ -7,8 +7,6 @@
 <?import javafx.scene.layout.*?>
 
 
-<?import org.controlsfx.control.HyperlinkLabel?>
-
 <AnchorPane prefHeight="400.0" prefWidth="600.0"
             xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
             fx:controller="com.codesrc.scsi2sd.presentation.AboutController">
@@ -16,7 +14,7 @@
 
       <ImageView fitHeight="135.0" fitWidth="135.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" pickOnBounds="true" preserveRatio="true" >
          <image>
-            <Image url="@/img/codesrc-square-135.png"/>
+            <Image url="jrt:/com.codesrc.scsi2sd.ui/icons/icon-256.png"/>
          </image>
       </ImageView>
 
@@ -28,9 +26,9 @@
                </font>
             </Label>
 
-            <Label text="Copyright 2017 Michael McMaster &lt;michael@codesrc.com&gt;" />
+            <Label>Copyright 2019 Michael McMaster [michael@codesrc.com]</Label>
             <Label text="Version: " fx:id="labelVersion"/>
-            <HyperlinkLabel fx:id="licenceLink"><text>Licence: [GPLv3+]</text></HyperlinkLabel>
+            <Hyperlink fx:id="licenceLink"><text>Licence: GPLv3</text></Hyperlink>
 
          </children>
       </VBox>
diff --git a/scsi2sd.ui/src/main/resources/fx/configTab.fxml b/scsi2sd.ui/src/main/resources/fx/configTab.fxml
new file mode 100644 (file)
index 0000000..e7ba01f
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import javafx.scene.text.*?>
+<?import java.lang.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<?import org.controlsfx.control.ToggleSwitch?>
+<ScrollPane
+        fitToWidth="true"
+        hbarPolicy="AS_NEEDED"
+        vbarPolicy="AS_NEEDED"
+        xmlns="http://javafx.com/javafx/8"
+        xmlns:fx="http://javafx.com/fxml/1"
+        fx:controller="com.codesrc.scsi2sd.presentation.ConfigController">
+
+    <content>
+        <VBox>
+            <children>
+                <GridPane hgap="15.0" vgap="5.0" VBox.vgrow="ALWAYS">
+                    <columnConstraints>
+                        <ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="380.0" minWidth="10.0" prefWidth="162.0" />
+                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="450.0" minWidth="200.0" prefWidth="329.0" />
+                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="0.0" prefWidth="215.0" />
+                    </columnConstraints>
+                    <rowConstraints>
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                        <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                    </rowConstraints>
+                    <children>
+                        <Label text="Enable Active Terminator" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+                        <CheckBox fx:id="cfgEnableTerminator" mnemonicParsing="false" GridPane.rowIndex="1" />
+                        <Label text="SCSI Speed Limit" GridPane.columnIndex="1" GridPane.rowIndex="3" />
+                        <ChoiceBox fx:id="speedLimit" prefWidth="150.0" GridPane.rowIndex="3" />
+                        <Label text="Startup Delay. 0 to 255 seconds." wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="4" />
+                        <TextField fx:id="cfgStartupDelay" alignment="CENTER_RIGHT" maxWidth="150.0" promptText="0" GridPane.rowIndex="4" />
+
+                        <Label text="Selection Delay. 0 to 255 milliseconds." GridPane.columnIndex="1" GridPane.rowIndex="5" />
+                        <TextField fx:id="cfgSelectionDelay" alignment="CENTER_RIGHT" maxWidth="150.0" promptText="auto" GridPane.rowIndex="5">
+                        </TextField>
+
+                        <Label text="Check parity" GridPane.columnIndex="1" GridPane.rowIndex="6" />
+                        <ToggleSwitch fx:id="cfgCheckParity" mnemonicParsing="false" GridPane.rowIndex="6" />
+
+                        <Label text="Unit Attention" GridPane.columnIndex="1" GridPane.rowIndex="7" />
+                        <ToggleSwitch fx:id="cfgUnitAtt" mnemonicParsing="false" GridPane.rowIndex="7" />
+
+                        <Label text="SCSI2 Mode" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+                        <ToggleSwitch fx:id="cfgScsi2" mnemonicParsing="false" GridPane.rowIndex="2" />
+
+                        <Label text="Respond to short selection pulses" GridPane.columnIndex="1" GridPane.rowIndex="10" />
+                        <ToggleSwitch fx:id="cfgSelPulses" mnemonicParsing="false" GridPane.rowIndex="10" />
+
+                        <Label text="Map LUNS to IDs" GridPane.columnIndex="1" GridPane.rowIndex="11" />
+                        <ToggleSwitch fx:id="cfgMapLuns" mnemonicParsing="false" GridPane.rowIndex="11" />
+
+                        <Label text="Default" GridPane.columnIndex="2">
+                            <font>
+                                <Font name="System Bold" size="13.0" />
+                            </font>
+                        </Label>
+                        <Label text="Enabled" GridPane.columnIndex="2" GridPane.rowIndex="1" />
+                        <Label text="No Limit" GridPane.columnIndex="2" GridPane.rowIndex="3" />
+                        <Label text="0" GridPane.columnIndex="2" GridPane.rowIndex="4" />
+                        <Label text="Auto (blank)" GridPane.columnIndex="2" GridPane.rowIndex="5" />
+                        <Label text="Enabled" GridPane.columnIndex="2" GridPane.rowIndex="6" />
+                        <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="7" />
+                        <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="2" />
+                        <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="10" />
+                        <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="11" />
+                        <Label text="Advanced" GridPane.columnIndex="1" GridPane.rowIndex="9">
+                            <font>
+                                <Font name="System Bold" size="13.0" />
+                            </font>
+                        </Label>
+                    </children>
+                </GridPane>
+            </children>
+        </VBox>
+    </content>
+</ScrollPane>
\ No newline at end of file
similarity index 64%
rename from src/main/resources/fx/debugLog.fxml
rename to scsi2sd.ui/src/main/resources/fx/debugLog.fxml
index d061230e26dba4e2aadadd20002d9d909fa59e05..d6b4e45085cca8ffee09ac6bfeb1ee144fc34f53 100644 (file)
@@ -7,10 +7,13 @@
 <?import javafx.scene.paint.*?>
 <?import javafx.scene.text.*?>
 
-<AnchorPane VBox.vgrow="ALWAYS">
+<AnchorPane VBox.vgrow="ALWAYS"
+            xmlns="http://javafx.com/javafx/8"
+            xmlns:fx="http://javafx.com/fxml/1"
+            fx:controller="com.codesrc.scsi2sd.presentation.DebugLogController">
     <children>
 
-                <TextArea AnchorPane.leftAnchor="15" AnchorPane.rightAnchor="15" AnchorPane.topAnchor="15" AnchorPane.bottomAnchor="50" editable="false" />
+                <TextArea fx:id="logTextArea" AnchorPane.leftAnchor="15" AnchorPane.rightAnchor="15" AnchorPane.topAnchor="15" AnchorPane.bottomAnchor="50" editable="false" />
                 <FlowPane AnchorPane.leftAnchor="15" AnchorPane.bottomAnchor="10" hgap="10">
                     <Button mnemonicParsing="false" text="Save Log As ..." />
                     <Button mnemonicParsing="false" text="Clear Log" />
diff --git a/scsi2sd.ui/src/main/resources/fx/disksTab.fxml b/scsi2sd.ui/src/main/resources/fx/disksTab.fxml
new file mode 100644 (file)
index 0000000..7b050a8
--- /dev/null
@@ -0,0 +1,187 @@
+<?import javafx.scene.layout.AnchorPane?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.TableView?>
+<?import javafx.scene.control.TableColumn?>
+
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.Accordion?>
+<?import javafx.scene.control.TitledPane?>
+<?import javafx.scene.layout.GridPane?>
+<?import javafx.scene.layout.ColumnConstraints?>
+<?import javafx.scene.layout.RowConstraints?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.TextField?>
+<?import javafx.scene.control.ComboBox?>
+<?import javafx.scene.control.ChoiceBox?>
+<?import javafx.scene.control.ScrollPane?>
+
+
+<?import javafx.scene.control.cell.PropertyValueFactory?>
+<?import javafx.scene.control.CheckBox?>
+<ScrollPane
+        fitToWidth="true"
+        hbarPolicy="AS_NEEDED"
+        vbarPolicy="AS_NEEDED"
+        xmlns="http://javafx.com/javafx/8"
+        xmlns:fx="http://javafx.com/fxml/1"
+        fx:controller="com.codesrc.scsi2sd.presentation.DiskConfigController">
+    <content>
+
+        <VBox>
+            <children>
+                <AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" minHeight="350" prefHeight="350.0" maxHeight="350">
+                    <children>
+                        <Button fx:id="newBtn" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="14.0" mnemonicParsing="false" prefWidth="100.0" text="New" />
+                        <Button fx:id="deleteBtn" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="164.0"  mnemonicParsing="false" prefWidth="100.0" text="Delete" />
+                        <Button fx:id="setSdCardSizeBtn" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="314.0"  mnemonicParsing="false" prefWidth="150.0" text="SD card Size ..." />
+
+                        <TableView fx:id="diskTableView"
+                                   AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="15.0" AnchorPane.rightAnchor="15.0" AnchorPane.topAnchor="10.0"
+                                    >
+                            <columnResizePolicy>
+                                <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
+                            </columnResizePolicy>
+                            <columns>
+                                <TableColumn editable="false" minWidth="50" prefWidth="100.0" text="SD Sector">
+                                    <cellValueFactory><PropertyValueFactory property="sdSector" /></cellValueFactory>
+                                </TableColumn>
+                                <TableColumn editable="false" minWidth="75" prefWidth="178.0" text="Type">
+                                    <cellValueFactory><PropertyValueFactory property="diskType" /></cellValueFactory>
+                                </TableColumn>
+                                <TableColumn editable="false" minWidth="75" prefWidth="178.0" text="Size">
+                                    <cellValueFactory><PropertyValueFactory property="size" /></cellValueFactory>
+                                </TableColumn>
+                                <TableColumn editable="false" minWidth="50" prefWidth="100.0" text="SCSI ID">
+                                    <cellValueFactory><PropertyValueFactory property="id" /></cellValueFactory>
+                                </TableColumn>
+                                <TableColumn editable="false" minWidth="50" prefWidth="100.0" text="Device Type">
+                                    <cellValueFactory><PropertyValueFactory property="deviceType" /></cellValueFactory>
+                                </TableColumn>
+                                <TableColumn minWidth="50" prefWidth="100.0" text="Enabled">
+                                    <cellValueFactory><PropertyValueFactory property="enabled" /></cellValueFactory>
+                                </TableColumn>
+                            </columns>
+                        </TableView>
+                    </children>
+                </AnchorPane>
+
+                <Accordion>
+                    <panes>
+                        <TitledPane animated="false" text="Basic">
+                            <content>
+                                <AnchorPane>
+                                    <children>
+                                        <GridPane layoutX="14.0" layoutY="14.0" prefHeight="105.0" prefWidth="500.0">
+                                            <columnConstraints>
+                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+                                            </columnConstraints>
+                                            <rowConstraints>
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                            </rowConstraints>
+                                            <children>
+                                                <Label text="SCSI ID" />
+                                                <TextField fx:id="scsiId" promptText="0" GridPane.columnIndex="1" />
+
+                                                <Label text="Type" GridPane.rowIndex="1" />
+                                                <ComboBox fx:id="scsiDeviceType" prefHeight="25.0" prefWidth="270.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+
+                                                <Label text="Enabled" GridPane.rowIndex="2" />
+                                                <CheckBox fx:id="enabled" prefHeight="25.0" prefWidth="270.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+
+                                            </children>
+                                        </GridPane>
+                                    </children>
+                                </AnchorPane>
+                            </content>
+                        </TitledPane>
+                        <TitledPane animated="false" text="Size">
+                            <content>
+                                <AnchorPane>
+                                    <children>
+                                        <Label layoutX="14.0" layoutY="14.0" text="Size" />
+                                        <TextField fx:id="scsiSizeStr" alignment="CENTER_RIGHT" layoutX="142.0" layoutY="8.0" prefHeight="25.0" prefWidth="200.0" />
+                                        <ChoiceBox fx:id="scsiSizeUnit" layoutX="375.0" layoutY="8.0" prefWidth="150.0" />
+
+                                        <Label layoutX="12.0" layoutY="44.0" text="SCSI Sector Size" />
+                                        <TextField fx:id="scsiBytesPerSector" alignment="CENTER_RIGHT" layoutX="142.0" layoutY="38.0" prefHeight="25.0" prefWidth="200.0" promptText="512 bytes" />
+
+                                        <Label layoutX="14.0" layoutY="74.0" text="SD Starting Byte" />
+                                        <TextField fx:id="sdStartingByte" disable="true" alignment="CENTER_RIGHT" layoutX="142.0" layoutY="68.0" prefHeight="25.0" prefWidth="200.0" />
+
+                                    </children>
+                                </AnchorPane>
+                            </content>
+                        </TitledPane>
+                        <TitledPane animated="false" text="Identification">
+                            <content>
+                                <AnchorPane>
+                                    <children>
+                                        <GridPane layoutX="14.0" layoutY="14.0" prefHeight="136.0" prefWidth="524.0">
+                                            <columnConstraints>
+                                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="255.0" minWidth="10.0" prefWidth="151.0" />
+                                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="373.0" minWidth="10.0" prefWidth="373.0" />
+                                            </columnConstraints>
+                                            <rowConstraints>
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                            </rowConstraints>
+                                            <children>
+                                                <Label text="Vendor" />
+                                                <Label text="Product ID" GridPane.rowIndex="1" />
+                                                <Label text="Revision" GridPane.rowIndex="2" />
+                                                <Label text="Serial Number" GridPane.rowIndex="3" />
+                                                <TextField fx:id="vendor" GridPane.columnIndex="1" />
+                                                <TextField fx:id="prodId" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+                                                <TextField fx:id="revision" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+                                                <TextField fx:id="serial" GridPane.columnIndex="1" GridPane.rowIndex="3" />
+                                            </children>
+                                        </GridPane>
+                                    </children>
+                                </AnchorPane>
+                            </content>
+                        </TitledPane>
+
+                        <TitledPane animated="false" text="Advanced">
+                            <content>
+                                <AnchorPane>
+                                    <children>
+                                        <GridPane layoutX="14.0" layoutY="14.0" prefHeight="105.0" prefWidth="500.0">
+                                            <columnConstraints>
+                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+                                            </columnConstraints>
+                                            <rowConstraints>
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+                                            </rowConstraints>
+                                            <children>
+                                                <Label text="Device Type Modifier" />
+                                                <TextField fx:id="deviceTypeModifier" promptText="0" GridPane.columnIndex="1" />
+
+                                                <Label text="Sectors per Track" GridPane.rowIndex="1" />
+                                                <TextField fx:id="sectorsPerTrack" GridPane.rowIndex="1" GridPane.columnIndex="1" />
+
+                                                <Label text="Heads per Cylinder" GridPane.rowIndex="2" />
+                                                <TextField fx:id="headsPerCylinder" GridPane.rowIndex="2" GridPane.columnIndex="1" />
+
+                                                <Label text="Quirks mode" GridPane.rowIndex="3" />
+                                                <ComboBox fx:id="quirksMode" GridPane.rowIndex="3" GridPane.columnIndex="1" />
+                                            </children>
+                                        </GridPane>
+                                    </children>
+                                </AnchorPane>
+                            </content>
+                        </TitledPane>
+                    </panes>
+                </Accordion>
+            </children>
+        </VBox>
+    </content>
+</ScrollPane>
similarity index 71%
rename from src/main/resources/fx/main.fxml
rename to scsi2sd.ui/src/main/resources/fx/main.fxml
index b8d58c3745309c67778a3722b35d7efd873069d9..1d5f878534bfb2f36d3be9a36d044b490523e2bf 100644 (file)
@@ -1,34 +1,29 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import javafx.geometry.*?>
-<?import java.lang.*?>
-<?import java.util.*?>
+
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<?import javafx.scene.paint.*?>
-<?import javafx.scene.text.*?>
-
 <?import org.controlsfx.glyphfont.Glyph?>
-
-
-<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.codesrc.scsi2sd.presentation.MainController">
-
+<?import java.net.URL?>
+<VBox
+       xmlns="http://javafx.com/javafx/8"
+       xmlns:fx="http://javafx.com/fxml/1"
+       fx:controller="com.codesrc.scsi2sd.presentation.MainController"
+       fx:id="mainVbox">
     <children>
         <MenuBar VBox.vgrow="NEVER">
             <menus>
                 <Menu mnemonicParsing="false" text="File">
                     <items>
-                        <MenuItem mnemonicParsing="false" text="Open…">
+                        <MenuItem fx:id="menuOpenFile" mnemonicParsing="false" text="Open…">
                             <graphic>
                                 <Glyph fontFamily="FontAwesome" icon="FOLDER_OPEN"/>
                             </graphic>
                         </MenuItem>
-                        <Menu mnemonicParsing="false" text="Open Recent" />
+                        <Menu fx:id="menuRecent" mnemonicParsing="false" text="Open Recent" />
                         <SeparatorMenuItem mnemonicParsing="false" />
-                        <MenuItem mnemonicParsing="false" text="Close" />
-                        <MenuItem mnemonicParsing="false" text="Save" />
-                        <MenuItem mnemonicParsing="false" text="Save As…" />
-                        <MenuItem mnemonicParsing="false" text="Revert" />
+                        <MenuItem fx:id="menuSaveFile" mnemonicParsing="false" text="Save As…" />
+                        <MenuItem fx:id="menuRevert" mnemonicParsing="false" text="Revert" />
                         <SeparatorMenuItem mnemonicParsing="false" />
                         <MenuItem fx:id="menuQuit" mnemonicParsing="false" text="Quit" />
                     </items>
@@ -57,9 +52,9 @@
                         <Glyph fontFamily="FontAwesome" icon="SAVE"/>
                     </graphic>
                     <items>
-                        <MenuItem mnemonicParsing="false" text="Save to device" />
-                        <MenuItem mnemonicParsing="false" text="Save to SD card" />
-                        <MenuItem mnemonicParsing="false" text="Save to file ..." />
+                        <MenuItem fx:id="menuSaveDevice" mnemonicParsing="false" text="Save to device" />
+                        <!--<MenuItem mnemonicParsing="false" text="Save to SD card" />-->
+                        <MenuItem fx:id="toolbarSaveFile" mnemonicParsing="false" text="Save to file ..." />
                     </items>
                 </MenuButton>
                 <MenuButton mnemonicParsing="false" text="Open">
                         <Glyph fontFamily="FontAwesome" icon="FOLDER_OPEN"/>
                     </graphic>
                     <items>
-                        <MenuItem mnemonicParsing="false" text="Open device" />
-                        <MenuItem mnemonicParsing="false" text="Open SD card" />
-                        <MenuItem mnemonicParsing="false" text="Open file ..." />
+                        <MenuItem fx:id="menuOpenDevice" mnemonicParsing="false" text="Open device" />
+                        <!--<MenuItem mnemonicParsing="false" text="Open SD card" />-->
+                        <MenuItem fx:id="toolbarOpenFile" mnemonicParsing="false" text="Open file ..." />
                     </items>
                 </MenuButton>
-                <Button mnemonicParsing="false" text="Revert" />
+                <Button fx:id="buttonRevert" mnemonicParsing="false" text="Revert" />
                 <Separator></Separator>
                 <Button>Update Firmware...</Button>
             </items>
@@ -97,7 +92,7 @@
                     </Tab>
                     <Tab  closable="false" text="Log">
                         <content>
-                            <fx:include source="debugLog.fxml"/>
+                            <fx:include fx:id="debugLog" source="debugLog.fxml"/>
                         </content>
                     </Tab>
 
similarity index 67%
rename from src/main/resources/fx/statusTab.fxml
rename to scsi2sd.ui/src/main/resources/fx/statusTab.fxml
index a61bc8ebb82de0eb6288ac1338a48273a3c2ef0c..1210aafbe1cbcd85b7389383cafbe5d79ab5da2e 100644 (file)
@@ -1,7 +1,10 @@
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
 
-<AnchorPane>
+<AnchorPane
+        xmlns="http://javafx.com/javafx/8"
+        xmlns:fx="http://javafx.com/fxml/1"
+        fx:controller="com.codesrc.scsi2sd.presentation.StatusTabController">
     <children>
         <GridPane AnchorPane.topAnchor="15" AnchorPane.leftAnchor="15" AnchorPane.rightAnchor="15">
             <columnConstraints>
                 <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                 <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                 <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
             </rowConstraints>
             <children>
                 <Label text="Status" />
                 <Label alignment="TOP_LEFT" text="Hardware Version" GridPane.rowIndex="1" />
                 <Label text="Firmware Version" GridPane.rowIndex="2" />
                 <Label text="SD Capacity" GridPane.rowIndex="3" />
-                <Label text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
-                <Label text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1" />
-                <Label text="Label" GridPane.columnIndex="1" />
-                <Label text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3" />
-                <Label text="SCSI Disks" GridPane.rowIndex="4" />
-                <Label text="Label" GridPane.columnIndex="1" GridPane.rowIndex="4" />
+
+                <Label text="-" fx:id="status" GridPane.columnIndex="1" />
+
+
+                <Label text="-" fx:id="fwVersion" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+                <Label text="-" fx:id="hwVersion" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+
+                <Label text="-" fx:id="sdCapacity" GridPane.columnIndex="1" GridPane.rowIndex="3" />
             </children>
         </GridPane>
     </children>
diff --git a/scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-floppy-disk.svg b/scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-floppy-disk.svg
new file mode 100644 (file)
index 0000000..69750cd
--- /dev/null
@@ -0,0 +1,13 @@
+<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
+<svg viewBox="0 0 17 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-floppy-disk">
+    <!-- Generator: Sketch 3.0.3 (7891) - http://www.bohemiancoding.com/sketch -->
+    <title>832</title>
+    
+    <defs></defs>
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(0.968151, 0.000000)" fill="#434343">
+            <path d="M11.993,0.031 L4.035,0.031 L4.035,5.983 L11.993,5.983 L11.993,0.031 L11.993,0.031 Z M11.0318493,5 L9.03184926,5 L9.03184926,1 L11.0318493,1 L11.0318493,5 L11.0318493,5 Z" class="si-glyph-fill"></path>
+            <path d="M14.0196178,0.031 L13.1109039,0.031 L13.1109039,7.10112134 L2.97331513,7.10112134 L2.97331513,0.031 L2.02037174,0.031 C0.91564095,0.031 0.0209999999,0.950085607 0.0209999999,2.08335637 L0.0209999999,12.9776381 C0.0209999999,14.1109088 0.916646164,15.031 2.02037174,15.031 L14.020623,15.031 C15.1263591,15.031 16.021,14.1109088 16.021,12.9776381 L16.021,2.08335637 L14.0196178,0.031 L14.0196178,0.031 Z M12,13 L4,13 L4,12 L12,12 L12,13 L12,13 Z M12,11 L4,11 L4,10 L12,10 L12,11 L12,11 Z" class="si-glyph-fill"></path>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-folder-open.svg b/scsi2sd.ui/src/main/resources/icons/glyph.smarticons.co/si-glyph-folder-open.svg
new file mode 100644 (file)
index 0000000..d3324a8
--- /dev/null
@@ -0,0 +1,10 @@
+<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
+<svg viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-folder-open">
+    <!-- Generator: Sketch 3.0.3 (7891) - http://www.bohemiancoding.com/sketch -->
+    <title>82</title>
+    
+    <defs></defs>
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <path d="M8.03,4.042 L7.228,3.042 L1.015,3.042 L1.015,4.042 L0.00899999996,4.042 L0.00899999996,13 L1.026,13 L1.031,13.984 L14.65,13.963 L15.953,4.041 L8.03,4.041 L8.03,4.042 Z M13.82,13.041 L1.711,13.041 L3.1,4.953 L15.032,4.953 L13.82,13.041 L13.82,13.041 Z" fill="#434343" class="si-glyph-fill"></path>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/scsi2sd.ui/src/main/resources/icons/icon-16.png b/scsi2sd.ui/src/main/resources/icons/icon-16.png
new file mode 100644 (file)
index 0000000..70c32d8
Binary files /dev/null and b/scsi2sd.ui/src/main/resources/icons/icon-16.png differ
diff --git a/scsi2sd.ui/src/main/resources/icons/icon-256.png b/scsi2sd.ui/src/main/resources/icons/icon-256.png
new file mode 100644 (file)
index 0000000..79d6829
Binary files /dev/null and b/scsi2sd.ui/src/main/resources/icons/icon-256.png differ
diff --git a/scsi2sd.ui/src/main/resources/icons/icon-32.png b/scsi2sd.ui/src/main/resources/icons/icon-32.png
new file mode 100644 (file)
index 0000000..65342d9
Binary files /dev/null and b/scsi2sd.ui/src/main/resources/icons/icon-32.png differ
diff --git a/scsi2sd.ui/src/main/resources/icons/icon.svg b/scsi2sd.ui/src/main/resources/icons/icon.svg
new file mode 100644 (file)
index 0000000..d1a33c0
--- /dev/null
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="256"
+   height="256"
+   viewBox="0 0 67.733333 67.733335"
+   version="1.1"
+   id="svg5138"
+   sodipodi:docname="icon.svg"
+   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+   inkscape:export-filename="/home/michael/projects/scsi2sd-util/scsi2sd.ui/src/main/resources/icons/icon-16.png"
+   inkscape:export-xdpi="6"
+   inkscape:export-ydpi="6">
+  <defs
+     id="defs5132" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2"
+     inkscape:cx="16.463924"
+     inkscape:cy="191.71962"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:pagecheckerboard="true"
+     inkscape:window-width="2560"
+     inkscape:window-height="1376"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5135">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-229.26666)">
+    <path
+       style="fill:#4d4d4d;fill-rule:evenodd;stroke:#000000;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 10,230 h 40 l 10,10 v 10 h -3 v 5 h 3 v 42 H 45 v -2 H 25 v 2 H 8 v -37 h 2 V 245 H 7 v -15 z"
+       id="path6066"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccccccccccccccc" />
+    <path
+       style="fill:#999999;fill-rule:evenodd;stroke:#000000;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 7,245 h 3 v 7 H 7 Z"
+       id="path6068"
+       inkscape:connector-curvature="0" />
+    <rect
+       style="fill:#4d4d4d;fill-opacity:1;stroke:#fffffd;stroke-width:0.26458335;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+       id="rect6070"
+       width="44"
+       height="55"
+       x="12"
+       y="237" />
+    <rect
+       style="fill:#4c4c8e;fill-opacity:1;stroke:#fffffd;stroke-width:0.26458335;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+       id="rect6105"
+       width="40"
+       height="23.479628"
+       x="14"
+       y="267" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.17500019px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="15.5"
+       y="280"
+       id="text6074"><tspan
+         sodipodi:role="line"
+         id="tspan6072"
+         x="15.5"
+         y="280.21353"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.46666718px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px">SCSI2SD</tspan></text>
+  </g>
+</svg>
diff --git a/scsi2sd.ui/src/main/resources/icons/scsi2sd-util.ico b/scsi2sd.ui/src/main/resources/icons/scsi2sd-util.ico
new file mode 100644 (file)
index 0000000..9f4d338
Binary files /dev/null and b/scsi2sd.ui/src/main/resources/icons/scsi2sd-util.ico differ
diff --git a/scsi2sd.ui/src/main/resources/img/splash.png b/scsi2sd.ui/src/main/resources/img/splash.png
new file mode 100644 (file)
index 0000000..a602a33
Binary files /dev/null and b/scsi2sd.ui/src/main/resources/img/splash.png differ
diff --git a/scsi2sd.ui/src/main/resources/img/splash.svg b/scsi2sd.ui/src/main/resources/img/splash.svg
new file mode 100644 (file)
index 0000000..66e43bd
--- /dev/null
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="600"
+   height="300"
+   viewBox="0 0 158.75 79.375002"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+   sodipodi:docname="splash.svg"
+   inkscape:export-filename="/home/michael/projects/scsi2sd-util/scsi2sd.ui/src/main/resources/img/splash.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#646464"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.4"
+     inkscape:cx="224.74084"
+     inkscape:cy="234.28571"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:pagecheckerboard="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1376"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-217.62498)">
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.17499995px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="5.2941761"
+       y="234.63391"
+       id="text1368"><tspan
+         sodipodi:role="line"
+         x="5.2941761"
+         y="234.63391"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.28888893px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#c8c8c8;fill-opacity:1;stroke-width:0.26458332px"
+         id="tspan1374">SCSI2SD</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.17500019px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="5.943749"
+       y="244.27229"
+       id="text1372"><tspan
+         sodipodi:role="line"
+         id="tspan1370"
+         x="5.943749"
+         y="244.27229"
+         style="fill:#c8c8c8;fill-opacity:1;stroke-width:0.26458335px">Configuration and Firmware Update Utility</tspan><tspan
+         sodipodi:role="line"
+         x="5.943749"
+         y="248.24104"
+         style="fill:#c8c8c8;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1408">v1.0-BETA</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.17500019px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="5.291667"
+       y="272.9985"
+       id="text1380"><tspan
+         sodipodi:role="line"
+         id="tspan1378"
+         x="5.291667"
+         y="275.80765"
+         style="stroke-width:0.26458335px" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:3.17500019px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#969696;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="6.0476184"
+       y="254.47769"
+       id="text1384"><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="254.47769"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1386">scsi2sd-util  Copyright Â© 2019 Michael McMaster</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="258.44644"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1406" /><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="262.41519"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1388">This program comes with ABSOLUTELY NO WARRANTY.</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="266.38394"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1392">This program is free software: you can redistribute it and/or modify</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="270.35269"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1398">it under the terms of the GNU General Public License as published by</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="274.32144"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1400">the Free Software Foundation, either version 3 of the License, or</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="278.29019"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1402">(at your option) any later version.</tspan><tspan
+         sodipodi:role="line"
+         x="6.0476184"
+         y="282.25894"
+         style="fill:#969696;fill-opacity:1;stroke-width:0.26458335px"
+         id="tspan1404" /></text>
+  </g>
+</svg>
diff --git a/src/main/java/com/codesrc/scsi2sd/App.java b/src/main/java/com/codesrc/scsi2sd/App.java
deleted file mode 100644 (file)
index 78296a3..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd;
-
-import javafx.application.Application;
-import javafx.stage.Stage;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-public class App extends Application {
-
-
-    @Override
-    public void start(Stage stage) throws Exception {
-        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
-        ScreensConfiguration screens = context.getBean(ScreensConfiguration.class);
-        screens.setPrimaryStage(stage);
-        screens.mainWindow().show();
-    }
-
-
-    public static void main(String[] args) {
-
-        Properties appProperties = new Properties();
-        try (InputStream is = App.class.getResourceAsStream("/app.properties")) {
-            appProperties.load(is);
-        } catch (IOException e)
-        {
-            System.err.println("Could not load resources");
-            System.exit(1);
-        }
-
-
-        System.out.println(
-                "    SCSI2SD Utility v" + appProperties.getProperty("com.codesrc.scsi2sd.app.version") + "\n" +
-                "    Copyright (C) 2017  Michael McMaster <michael@codesrc.com>\n" +
-                "\n" +
-                "    This program is free software: you can redistribute it and/or modify\n" +
-                "    it under the terms of the GNU General Public License as published by\n" +
-                "    the Free Software Foundation, either version 3 of the License, or\n" +
-                "    (at your option) any later version.\n" +
-                "\n" +
-                "    This program is distributed in the hope that it will be useful,\n" +
-                "    but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
-                "    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
-                "    GNU General Public License for more details.\n" +
-                "\n" +
-                "    You should have received a copy of the GNU General Public License\n" +
-                "    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n");
-
-
-        launch(args);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/codesrc/scsi2sd/AppConfiguration.java b/src/main/java/com/codesrc/scsi2sd/AppConfiguration.java
deleted file mode 100644 (file)
index 27856fc..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.ImportResource;
-
-/**
- * Created by michael on 27/05/17.
- */
-@Configuration
-@ImportResource("/context/applicationContext.xml")
-public class AppConfiguration {
-
-
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java b/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java
deleted file mode 100644 (file)
index c3786af..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd;
-
-import com.codesrc.scsi2sd.presentation.AboutController;
-import com.codesrc.scsi2sd.presentation.FXWindow;
-import com.codesrc.scsi2sd.presentation.MainController;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.stage.Stage;
-import javafx.stage.StageStyle;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.context.annotation.Scope;
-
-/**
- * Created by michael on 27/05/17.
- */
-@Configuration
-@Lazy
-public class ScreensConfiguration {
-    private Stage primaryStage;
-
-    public void setPrimaryStage(Stage primaryStage) {
-        this.primaryStage = primaryStage;
-    }
-
-    public void showScreen(Parent screen) {
-        primaryStage.setScene(new Scene(screen));
-        primaryStage.show();
-    }
-
-    @Bean
-    @Scope("prototype")
-    public FXWindow mainWindow() {
-        return new FXWindow(mainController(), getClass().getResource("/fx/main.fxml"), "codesrc SCSI2SD", primaryStage, StageStyle.DECORATED);
-    }
-
-    @Bean
-    @Scope("prototype")
-    public FXWindow aboutWindow(FXWindow parentWindow) {
-        return new FXWindow(aboutController(), getClass().getResource("/fx/about.fxml"), "About SCSI2SD", parentWindow, StageStyle.DECORATED);
-    }
-
-    @Bean
-    @Scope("prototype")
-    public MainController mainController() {
-        return new MainController(this);
-    }
-
-
-    @Bean
-    @Scope("prototype")
-    public AboutController aboutController() {
-        return new AboutController(this);
-    }
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java b/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java
deleted file mode 100644 (file)
index 0b8c95b..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.model;
-
-public interface BitEnum {
-    int getBit();
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java b/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java
deleted file mode 100644 (file)
index 0408b7c..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.model;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlType;
-import java.util.HashSet;
-import java.util.Set;
-
-@XmlType(name = "S2S_BoardCfg")
-@XmlAccessorType(XmlAccessType.NONE)
-public class BoardConfig {
-    public static final int LENGTH = 128;
-
-    private Set<Flag> flags;
-
-    private int startupDelay; // Seconds
-
-    private int selectionDelay; // milliseconds
-
-    private Set<Flag6> flags6;
-
-       private Speed scsiSpeed;
-
-       public BoardConfig() {
-           this.flags = new HashSet<>();
-           this.flags6 = new HashSet<>();
-           this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR);
-
-           this.selectionDelay = 255; // "Auto".
-        this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit;
-    }
-
-
-    public BoardConfig(byte[] in) {
-        if (in.length != LENGTH) {
-            throw new IllegalArgumentException("BoardConfig size incorrect");
-        }
-
-        flags = Flag.fromBitmask(in[4]);
-        startupDelay = (int)in[5] & 0xFF;
-        selectionDelay = (int)in[6] & 0xFF;
-        flags6 = Flag6.fromBitmask(in[7]);
-        scsiSpeed = Speed.fromBits(in[8]);
-    }
-
-    public byte[] asBytes() {
-        byte[] out = new byte[LENGTH];
-
-        out[0] = 'B'; out[1] = 'C'; out[2] = 'F'; out[3] = 'G';
-
-        out[4] = getMask(flags);
-        out[5] = (byte) startupDelay;
-        out[6] = (byte) selectionDelay;
-        out[7] = getMask(flags6);
-        out[8] = (byte) scsiSpeed.getValue();
-        return out;
-    }
-
-    private <T extends BitEnum> byte getMask(Set<T> flags) {
-        int result = 0;
-        for (T item : flags) {
-            result |= item.getBit();
-        }
-        return (byte) result;
-    }
-
-    public Set<Flag> getFlags() {
-        return flags;
-    }
-
-    public void setFlags(Set<Flag> flags) {
-        this.flags = flags;
-    }
-
-    @XmlElement(name = "startupDelay")
-    public int getStartupDelay() {
-        return startupDelay;
-    }
-
-    public void setStartupDelay(int startupDelay) {
-        this.startupDelay = startupDelay;
-    }
-
-    @XmlElement(name = "selectionDelay")
-    public int getSelectionDelay() {
-        return selectionDelay;
-    }
-
-    public void setSelectionDelay(int selectionDelay) {
-        this.selectionDelay = selectionDelay;
-    }
-
-    public Set<Flag6> getFlags6() {
-        return flags6;
-    }
-
-    public void setFlag6(Set<Flag6> flags6) {
-        this.flags6 = flags6;
-    }
-
-    public Speed getScsiSpeed() {
-        return scsiSpeed;
-    }
-
-    public void setScsiSpeed(Speed scsiSpeed) {
-        this.scsiSpeed = scsiSpeed;
-    }
-
-    @XmlElement(name = "enableTerminator")
-    public boolean isEnableTerminator() {
-        return flags6.contains(Flag6.S2S_CFG_ENABLE_TERMINATOR);
-    }
-
-    public void setEnableTerminator(boolean enableTerminator) {
-        this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR);
-    }
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java b/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java
deleted file mode 100644 (file)
index 414ce9d..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.model;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-
-@XmlRootElement(name = "SCSI2SD")
-@XmlAccessorType(XmlAccessType.NONE)
-public class ConfigRoot {
-    private BoardConfig boardConfig;
-
-    @XmlElement(name = "S2S_BoardCfg")
-    public BoardConfig getBoardConfig() {
-        return boardConfig;
-    }
-
-    public void setBoardConfig(BoardConfig boardConfig) {
-        this.boardConfig = boardConfig;
-    }
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/model/Flag6.java b/src/main/java/com/codesrc/scsi2sd/model/Flag6.java
deleted file mode 100644 (file)
index 6decc28..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.model;
-
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
-public enum Flag6 implements BitEnum {
-    S2S_CFG_ENABLE_TERMINATOR(1);
-
-    static Set<Flag6> fromBitmask(byte bitmask) {
-        Set<Flag6> result = new HashSet<>();
-        for (Flag6 flag : EnumSet.allOf(Flag6.class)) {
-            if ((((int) bitmask & 0xFF) & flag.getBit()) != 0) {
-                result.add(flag);
-            }
-        }
-        return result;
-    }
-
-    private int bit;
-    Flag6(int bit) {
-        this.bit = bit;
-    }
-
-    @Override
-    public int getBit() { return bit; }
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/model/Speed.java b/src/main/java/com/codesrc/scsi2sd/model/Speed.java
deleted file mode 100644 (file)
index 45db134..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.model;
-
-import java.util.EnumSet;
-
-public enum Speed {
-    S2S_CFG_SPEED_NoLimit(0),
-    S2S_CFG_SPEED_ASYNC_15(1),
-    S2S_CFG_SPEED_ASYNC_33(2),
-    S2S_CFG_SPEED_ASYNC_50(3),
-    S2S_CFG_SPEED_SYNC_5(4),
-    S2S_CFG_SPEED_SYNC_10(5),
-    S2S_CFG_SPEED_TURBO(6);
-
-    static Speed fromBits(byte bits) {
-
-        for (Speed speed : EnumSet.allOf(Speed.class)) {
-            if (speed.getValue() == ((int) bits & 0xFF)) {
-                return speed;
-            }
-        }
-        return S2S_CFG_SPEED_NoLimit;
-    }
-
-
-    private int value;
-
-    Speed(int value) {
-        this.value = value;
-    }
-
-    int getValue() {
-        return value;
-    }
-}
-
-
diff --git a/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java b/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java
deleted file mode 100644 (file)
index 6e847d0..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.presentation;
-
-import com.codesrc.scsi2sd.ScreensConfiguration;
-import javafx.fxml.FXML;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.control.TextArea;
-import javafx.scene.control.Tooltip;
-import org.controlsfx.control.HyperlinkLabel;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.ResourceBundle;
-import java.util.stream.Collectors;
-
-/**
- * Created by michael on 27/05/17.
- */
-public class AboutController implements FXController {
-    private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AboutController.class.getName());
-
-    private ScreensConfiguration screens;
-    private FXWindow window;
-
-
-    @FXML private TextArea aboutText;
-    @FXML private Label labelVersion;
-    @FXML private HyperlinkLabel licenceLink;
-    @FXML private Button buttonClose;
-
-    @Autowired
-    private Desktop desktop;
-
-    @Value("${com.codesrc.scsi2sd.app.licence.url}")
-    private String licenseURL;
-
-    @Value("${com.codesrc.scsi2sd.app.version}")
-    private String appVersion;
-
-    public AboutController(ScreensConfiguration screens) {
-        this.screens = screens;
-    }
-
-    @Override
-    public void setWindow(FXWindow window) {
-        this.window = window;
-    }
-
-    public void initialize(URL url, ResourceBundle resourceBundle) {
-        String about = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/licence.txt")))
-                .lines().collect(Collectors.joining("\n"));
-
-        aboutText.setText(about);
-        labelVersion.setText(labelVersion.getText() + appVersion);
-
-        licenceLink.setTooltip(new Tooltip(licenseURL));
-        licenceLink.setOnAction(h -> desktop.openBrowser(licenseURL));
-
-        buttonClose.setOnAction(h -> window.close());
-    }
-
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java b/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java
deleted file mode 100644 (file)
index d9a88eb..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.presentation;
-
-import javafx.fxml.Initializable;
-
-/**
- * Created by michael on 27/05/17.
- */
-public interface FXController extends Initializable {
-    void setWindow(FXWindow window);
-}
diff --git a/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java b/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java
deleted file mode 100644 (file)
index 0683fda..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.presentation;
-
-import com.codesrc.scsi2sd.App;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.scene.image.Image;
-import javafx.stage.Modality;
-import javafx.stage.Stage;
-import javafx.stage.StageStyle;
-import javafx.stage.Window;
-
-import java.io.IOException;
-import java.net.URL;
-
-/**
- * Created by michael on 27/05/17.
- */
-public class FXWindow extends Stage {
-    public FXWindow(FXController controller, URL fxml, String title, Window owner) {
-        this(controller, fxml, title, owner, StageStyle.DECORATED);
-    }
-
-    public FXWindow(final FXController controller, URL fxml, String title, Window owner, StageStyle style) {
-        super(style);
-        initOwner(owner);
-        initModality(Modality.WINDOW_MODAL);
-        getIcons().add(new Image(App.class.getResourceAsStream("/icons/codesrc-16x16.png")));
-        getIcons().add(new Image(App.class.getResourceAsStream("/icons/codesrc-32x32.png")));
-        setTitle(title);
-
-
-        FXMLLoader loader = new FXMLLoader(fxml);
-        try {
-            loader.setControllerFactory(a -> controller);
-            controller.setWindow(this);
-            setScene(new Scene((Parent) loader.load()));
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java b/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java
deleted file mode 100644 (file)
index d9874bd..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-//     Copyright (C) 2017 Michael McMaster <michael@codesrc.com>
-//
-//     This file is part of SCSI2SD.
-//
-//     SCSI2SD is free software: you can redistribute it and/or modify
-//     it under the terms of the GNU General Public License as published by
-//     the Free Software Foundation, either version 3 of the License, or
-//     (at your option) any later version.
-//
-//     SCSI2SD is distributed in the hope that it will be useful,
-//     but WITHOUT ANY WARRANTY; without even the implied warranty of
-//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//     GNU General Public License for more details.
-//
-//     You should have received a copy of the GNU General Public License
-//     along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
-
-package com.codesrc.scsi2sd.presentation;
-
-import com.codesrc.scsi2sd.ScreensConfiguration;
-import javafx.fxml.FXML;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.TabPane;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URL;
-import java.util.ResourceBundle;
-
-
-/**
- * Created by michael on 21/05/17.
- */
-public class MainController implements FXController {
-    private static Logger LOGGER = LoggerFactory.getLogger(MainController.class.getName());
-
-    private ScreensConfiguration screens;
-    private FXWindow window;
-
-
-    @FXML private MenuItem menuAbout;
-    @FXML private MenuItem menuQuit;
-
-    @FXML private TabPane mainTabPane;
-
-  //  @FXML private TextField cfgSelectionDelay;
-  //  @FXML private Button cfgSelectionDelayHelp;
-
-    public MainController(ScreensConfiguration screens) {
-        this.screens = screens;
-    }
-
-    @Override
-    public void setWindow(FXWindow window) {
-        this.window = window;
-    }
-
-    @Override
-    public void initialize(URL url, ResourceBundle resourceBundle) {
-        menuQuit.setOnAction(h -> System.exit(0));
-        menuAbout.setOnAction(h -> screens.aboutWindow(window).showAndWait());
-        //    testLoad();
-    }
-
-/*
-    private void testLoad() {
-        try {
-            File file = new File("/tmp/t.xml");
-            JAXBContext jaxbContext = JAXBContext.newInstance(ConfigRoot.class);
-
-            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
-            ConfigRoot customer = (ConfigRoot) jaxbUnmarshaller.unmarshal(file);
-            System.out.println(customer.getBoardConfig().getStartupDelay());
-
-            cfgSelectionDelay.setText(String.valueOf(customer.getBoardConfig().getSelectionDelay()));
-
-            Tooltip tip = new Tooltip("Delay before responding to SCSI selection.\nSCSI1 hosts usually require 1ms delay.\n\nDefault: blank for auto selection.");
-            cfgSelectionDelay.setTooltip(tip);
-            cfgSelectionDelayHelp.setOnAction(h -> {
-                Dialog<String> dialog = new Dialog<>();
-                dialog.setTitle("Selection Delay");
-                dialog.setContentText("Delay before responding to SCSI selection.\nSCSI1 hosts usually require 1ms delay.\n\nDefault: blank for auto selection.");
-                dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
-                dialog.showAndWait();
-            });
-        } catch (Exception e) {
-            // don't ever do this
-        }
-    }
-  */
-
-}
diff --git a/src/main/resources/app.properties b/src/main/resources/app.properties
deleted file mode 100644 (file)
index 8fee9eb..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-com.codesrc.scsi2sd.app.version = 0.1
-
-com.codesrc.scsi2sd.app.licence.type = GPLv3+
-com.codesrc.scsi2sd.app.licence.url = https://www.gnu.org/licenses/gpl-3.0.en.html
-
-
diff --git a/src/main/resources/context/applicationContext.xml b/src/main/resources/context/applicationContext.xml
deleted file mode 100644 (file)
index a84a739..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<beans
-        xmlns="http://www.springframework.org/schema/beans"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xmlns:context="http://www.springframework.org/schema/context"
-
-        xsi:schemaLocation="
-            http://www.springframework.org/schema/beans
-            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
-            http://www.springframework.org/schema/context
-            http://www.springframework.org/schema/context/spring-context-4.3.xsd">
-
-    <context:component-scan base-package="com.codesrc.scsi2sd" />
-
-    <bean id="appProperties"
-          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
-        <property name="location" value="app.properties" />
-    </bean>
-</beans>
diff --git a/src/main/resources/fx/configTab.fxml b/src/main/resources/fx/configTab.fxml
deleted file mode 100644 (file)
index 73e5331..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<?import javafx.geometry.*?>
-<?import javafx.scene.text.*?>
-<?import java.lang.*?>
-<?import javafx.scene.control.*?>
-<?import javafx.scene.layout.*?>
-
-<?import javafx.scene.image.ImageView?>
-<?import javafx.scene.image.Image?>
-        <ScrollPane fitToWidth="true" hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED"  xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
-            <content>
-                <VBox>
-                    <children>
-                        <GridPane hgap="15.0" vgap="5.0" VBox.vgrow="ALWAYS">
-                            <columnConstraints>
-                                <ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="380.0" minWidth="10.0" prefWidth="162.0" />
-                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="450.0" minWidth="200.0" prefWidth="329.0" />
-                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="0.0" prefWidth="215.0" />
-                            </columnConstraints>
-                            <rowConstraints>
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                <RowConstraints maxHeight="60.0" minHeight="15.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                            </rowConstraints>
-                            <children>
-                                <Label text="Enable Active Terminator" GridPane.columnIndex="1" GridPane.rowIndex="1" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="1" />
-                                <Label text="SCSI Speed Limit" GridPane.columnIndex="1" GridPane.rowIndex="3" />
-                                <ChoiceBox prefWidth="150.0" GridPane.rowIndex="3" />
-                                <Label text="Startup Delay. 0 to 255 seconds." wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="4" />
-                                <TextField alignment="CENTER_RIGHT" maxWidth="150.0" promptText="0" GridPane.rowIndex="4" />
-
-                                <Label text="Selection Delay. 0 to 255 milliseconds." GridPane.columnIndex="1" GridPane.rowIndex="5" />
-                                <TextField fx:id="cfgSelectionDelay" alignment="CENTER_RIGHT" maxWidth="150.0" promptText="auto" GridPane.rowIndex="5">
-                                </TextField>
-
-                                <Label text="Check parity" GridPane.columnIndex="1" GridPane.rowIndex="6" />
-                                <Label text="Unit Attention" GridPane.columnIndex="1" GridPane.rowIndex="7" />
-                                <Label text="SCSI2 Mode" GridPane.columnIndex="1" GridPane.rowIndex="2" />
-                                <Label text="Respond to short selection pulses" GridPane.columnIndex="1" GridPane.rowIndex="10" />
-                                <Label text="Map LUNS to IDs" GridPane.columnIndex="1" GridPane.rowIndex="11" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="6" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="7" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="2" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="10" />
-                                <CheckBox mnemonicParsing="false" GridPane.rowIndex="11" />
-                                <Label text="Default" GridPane.columnIndex="2">
-                                    <font>
-                                        <Font name="System Bold" size="13.0" />
-                                    </font>
-                                </Label>
-                                <Label text="Enabled" GridPane.columnIndex="2" GridPane.rowIndex="1" />
-                                <Label text="No Limit" GridPane.columnIndex="2" GridPane.rowIndex="3" />
-                                <Label text="0" GridPane.columnIndex="2" GridPane.rowIndex="4" />
-                                <Label text="Auto (blank)" GridPane.columnIndex="2" GridPane.rowIndex="5" />
-                                <Label text="Enabled" GridPane.columnIndex="2" GridPane.rowIndex="6" />
-                                <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="7" />
-                                <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="2" />
-                                <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="10" />
-                                <Label text="Disabled" GridPane.columnIndex="2" GridPane.rowIndex="11" />
-                                <Label text="Advanced" GridPane.columnIndex="1" GridPane.rowIndex="9">
-                                    <font>
-                                        <Font name="System Bold" size="13.0" />
-                                    </font>
-                                </Label>
-                            </children>
-                        </GridPane>
-                    </children>
-                </VBox>
-            </content>
-        </ScrollPane>
\ No newline at end of file
diff --git a/src/main/resources/fx/disksTab.fxml b/src/main/resources/fx/disksTab.fxml
deleted file mode 100644 (file)
index bbad18f..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?import javafx.scene.layout.AnchorPane?>
-<?import javafx.scene.control.Button?>
-<?import javafx.scene.control.TableView?>
-<?import javafx.scene.control.TableColumn?>
-
-<?import javafx.scene.layout.VBox?>
-<?import javafx.scene.control.Accordion?>
-<?import javafx.scene.control.TitledPane?>
-<?import javafx.scene.layout.GridPane?>
-<?import javafx.scene.layout.ColumnConstraints?>
-<?import javafx.scene.layout.RowConstraints?>
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.control.TextField?>
-<?import javafx.scene.control.ComboBox?>
-<?import javafx.scene.control.ChoiceBox?>
-<?import javafx.scene.control.ScrollPane?>
-
-
-<ScrollPane fitToWidth="true" hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
-    <content>
-
-        <VBox>
-            <children>
-                <AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" minHeight="350" prefHeight="350.0" maxHeight="350">
-                    <children>
-                        <Button AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="14.0" mnemonicParsing="false" prefWidth="100.0" text="New ..." />
-                        <Button AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="114.0" mnemonicParsing="false" prefWidth="100.0" text="Configure ..." />
-                        <Button AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="214.0"  mnemonicParsing="false" prefWidth="100.0" text="Delete" />
-                        <TableView AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="15.0" AnchorPane.rightAnchor="15.0" AnchorPane.topAnchor="10.0">
-                            <columnResizePolicy>
-                                <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
-                            </columnResizePolicy>
-                            <columns>
-                                <TableColumn editable="false" minWidth="50" prefWidth="75.0" text="Item" />
-                                <TableColumn editable="false" minWidth="75" prefWidth="261.0" text="Type" />
-                                <TableColumn editable="false" minWidth="75" prefWidth="178.0" text="Size" />
-                                <TableColumn editable="false" minWidth="50" prefWidth="100.0" text="SCSI ID" />
-                                <TableColumn minWidth="50" prefWidth="115.0" text="Enabled" />
-                            </columns>
-                        </TableView>
-                    </children>
-                </AnchorPane>
-
-                <Accordion>
-                    <panes>
-                        <TitledPane animated="false" text="Basic">
-                            <content>
-                                <AnchorPane>
-                                    <children>
-                                        <GridPane layoutX="14.0" layoutY="14.0" prefHeight="105.0" prefWidth="500.0">
-                                            <columnConstraints>
-                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
-                                                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
-                                            </columnConstraints>
-                                            <rowConstraints>
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                            </rowConstraints>
-                                            <children>
-                                                <Label text="SCSI ID" />
-                                                <Label text="Type" GridPane.rowIndex="1" />
-                                                <TextField promptText="0" GridPane.columnIndex="1" />
-                                                <ComboBox prefHeight="25.0" prefWidth="270.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
-                                            </children>
-                                        </GridPane>
-                                    </children>
-                                </AnchorPane>
-                            </content>
-                        </TitledPane>
-                        <TitledPane animated="false" text="Size">
-                            <content>
-                                <AnchorPane>
-                                    <children>
-                                        <ChoiceBox layoutX="375.0" layoutY="215.0" prefWidth="150.0" />
-                                        <Label layoutX="14.0" layoutY="14.0" text="SD Sector" />
-                                        <TextField alignment="CENTER_RIGHT" layoutX="142.0" layoutY="8.0" prefHeight="25.0" prefWidth="200.0" />
-                                        <Label layoutX="142.0" layoutY="41.0" text="Min:" />
-                                        <Label layoutX="142.0" layoutY="68.0" text="Max:" />
-                                        <Label layoutX="16.0" layoutY="221.0" text="Size" />
-                                        <TextField alignment="CENTER_RIGHT" layoutX="144.0" layoutY="215.0" prefHeight="25.0" prefWidth="200.0" />
-                                        <TextField alignment="CENTER_RIGHT" editable="false" layoutX="187.0" layoutY="36.0" prefHeight="25.0" prefWidth="155.0" />
-                                        <TextField alignment="CENTER_RIGHT" editable="false" layoutX="187.0" layoutY="63.0" prefHeight="25.0" prefWidth="155.0" />
-                                        <TextField alignment="CENTER_RIGHT" editable="false" layoutX="189.0" layoutY="274.0" prefHeight="25.0" prefWidth="155.0" />
-                                        <TextField alignment="CENTER_RIGHT" editable="false" layoutX="189.0" layoutY="247.0" prefHeight="25.0" prefWidth="155.0" />
-                                        <Label layoutX="144.0" layoutY="279.0" text="Max:" />
-                                        <Label layoutX="144.0" layoutY="252.0" text="Min:" />
-                                        <Label layoutX="12.0" layoutY="124.0" text="SCSI Sector Size" />
-                                        <TextField alignment="CENTER_RIGHT" layoutX="142.0" layoutY="118.0" prefHeight="25.0" prefWidth="200.0" promptText="512 bytes" />
-                                    </children>
-                                </AnchorPane>
-                            </content>
-                        </TitledPane>
-                        <TitledPane animated="false" text="Identification">
-                            <content>
-                                <AnchorPane>
-                                    <children>
-                                        <GridPane layoutX="14.0" layoutY="14.0" prefHeight="136.0" prefWidth="524.0">
-                                            <columnConstraints>
-                                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="255.0" minWidth="10.0" prefWidth="151.0" />
-                                                <ColumnConstraints hgrow="SOMETIMES" maxWidth="373.0" minWidth="10.0" prefWidth="373.0" />
-                                            </columnConstraints>
-                                            <rowConstraints>
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-                                            </rowConstraints>
-                                            <children>
-                                                <Label text="Vendor" />
-                                                <Label text="Product ID" GridPane.rowIndex="1" />
-                                                <Label text="Revision" GridPane.rowIndex="2" />
-                                                <Label text="Serial Number" GridPane.rowIndex="3" />
-                                                <TextField promptText="codesrc" GridPane.columnIndex="1" />
-                                                <TextField promptText="SCSI2SD" GridPane.columnIndex="1" GridPane.rowIndex="1" />
-                                                <TextField promptText="1.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
-                                                <TextField promptText="12345678" GridPane.columnIndex="1" GridPane.rowIndex="3" />
-                                            </children>
-                                        </GridPane>
-                                    </children>
-                                </AnchorPane>
-                            </content>
-                        </TitledPane>
-                    </panes>
-                </Accordion>
-            </children>
-        </VBox>
-    </content>
-</ScrollPane>
diff --git a/src/main/resources/icons/codesrc-16x16.png b/src/main/resources/icons/codesrc-16x16.png
deleted file mode 100644 (file)
index b2b2a4d..0000000
Binary files a/src/main/resources/icons/codesrc-16x16.png and /dev/null differ
diff --git a/src/main/resources/icons/codesrc-32x32.png b/src/main/resources/icons/codesrc-32x32.png
deleted file mode 100644 (file)
index 43614d1..0000000
Binary files a/src/main/resources/icons/codesrc-32x32.png and /dev/null differ
diff --git a/src/main/resources/img/codesrc-square-135.png b/src/main/resources/img/codesrc-square-135.png
deleted file mode 100644 (file)
index 2276b97..0000000
Binary files a/src/main/resources/img/codesrc-square-135.png and /dev/null differ
diff --git a/src/main/resources/licence.txt b/src/main/resources/licence.txt
deleted file mode 100644 (file)
index 94a9ed0..0000000
+++ /dev/null
@@ -1,674 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    <program>  Copyright (C) <year>  <name of author>
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
-  The GNU General Public License does not permit incorporating your program
-into proprietary programs.  If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
deleted file mode 100644 (file)
index f1b2752..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<configuration>
-    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
-        <encoder>
-            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %n
-            </Pattern>
-        </encoder>
-        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>TRACE</level>
-        </filter>
-    </appender>
-
-    <logger name="com.codesrc.scsi2sd" additivity="false">
-        <level value="DEBUG" />
-        <appender-ref ref="consoleAppender" />
-    </logger>
-
-    <root>
-        <level value="INFO" />
-        <appender-ref ref="consoleAppender" />
-    </root>
-</configuration>