_ remove gui

_ refactor readOrMakeStateFile()
pull/1/head
io42630 3 years ago
parent cd5f7be740
commit 4e6ad69877

@ -7,6 +7,31 @@
<br> <br>
<br> <br>
#### Design Goals
* Least possible intrusion.
* work on top of a FS
* can be plugged / unpluggen anytime
* Sync, not redundancy, not backup
* if user deletes File on one system, it will be deleted on all systems
* Pretty simple rules:
* if same md5 keep older file
* if diff md5 keep newer file
* if created, create everywhere
* if deleted, delete everywhere
#### Overview
```
DataRoot a data root
\_ SyncBundle : a bundle of directories on the FS to be syncronized.
\_ SyncDirectory : a directory on the FS.
\_ SyncFile : a file on the FS.
```
#### StateFile
* Used for tracking of file deletions.
* Located in each `SyncDirectory\state.ensync`
* Contains `<last edited> <relative file path>` for each file in the SyncDirectory.
#### Demo<a name="demo"></a> #### Demo<a name="demo"></a>
[![IMAGE ALT TEXT](http://img.youtube.com/vi/znR3jyM_4Ss/0.jpg)](https://youtu.be/znR3jyM_4Ss "ensync WIP Demo") [![IMAGE ALT TEXT](http://img.youtube.com/vi/znR3jyM_4Ss/0.jpg)](https://youtu.be/znR3jyM_4Ss "ensync WIP Demo")

@ -13,8 +13,12 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vaadin.version>7.3.1</vaadin.version>
<vaadin.plugin.version>${vaadin.version}</vaadin.plugin.version>
<failOnMissingWebXml>false</failOnMissingWebXml>
<maven.compiler.source>1.11</maven.compiler.source> <maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target> <maven.compiler.target>1.11</maven.compiler.target>
<kotlin.version>1.6.20-RC2</kotlin.version>
</properties> </properties>
<dependencies> <dependencies>
@ -24,74 +28,26 @@
<version>4.11</version> <version>4.11</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.base</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.base.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.fxml</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.fxml.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.controls</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.controls.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.graphics</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.graphics.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.media</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.media.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.swing</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.swing.jar</systemPath>
</dependency>
<dependency>
<groupId>javafx</groupId>
<artifactId>javafx.web</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx.web.jar</systemPath>
</dependency>
<dependency> <dependency>
<groupId>javafx</groupId> <groupId>org.json</groupId>
<artifactId>javafx.swt</artifactId> <artifactId>json</artifactId>
<version>11.0.2</version> <version>20190722</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx-swt.jar</systemPath>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>json</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>20190722</version> <version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
@ -139,5 +95,51 @@
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> </build>
</project> </project>

@ -1,34 +1,36 @@
package com.olexyn.ensync; package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.MapOfSyncMaps; import com.olexyn.ensync.artifacts.DataRoot;
import com.olexyn.ensync.artifacts.StateFile;
import com.olexyn.ensync.artifacts.SyncDirectory; import com.olexyn.ensync.artifacts.SyncDirectory;
import com.olexyn.ensync.artifacts.SyncMap; import com.olexyn.ensync.artifacts.SyncBundle;
import java.io.File; import java.io.File;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.logging.Logger;
public class Flow implements Runnable { public class Flow implements Runnable {
private static final Logger LOGGER = LogUtil.get(Flow.class);
Tools tools = new Tools(); Tools tools = new Tools();
public long pollingPause = 200; public long pollingPause = 200;
private String state;
public void run() { public void run() {
while (true) { while (true) {
synchronized (MapOfSyncMaps.get()) { synchronized(DataRoot.get()) {
readOrMakeStateFile(); readOrMakeStateFile();
for (Entry<String, SyncMap> syncMapEntry : MapOfSyncMaps.get().entrySet()) { for (Entry<String, SyncBundle> syncMapEntry : DataRoot.get().entrySet()) {
for (Entry<String, SyncDirectory> SDEntry : syncMapEntry.getValue().syncDirectories.entrySet()) { for (Entry<String, SyncDirectory> SDEntry : syncMapEntry.getValue().syncDirectories.entrySet()) {
@ -39,13 +41,13 @@ public class Flow implements Runnable {
try { try {
System.out.println("Pausing... for " + pollingPause + "ms."); System.out.println("Pausing... for " + pollingPause + "ms.");
Thread.sleep(pollingPause); Thread.sleep(pollingPause);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) { }
} }
} }
private void doSyncDirectory(SyncDirectory SD) { private void doSyncDirectory(SyncDirectory SD) {
state = "READ"; LOGGER.info("READ");
SD.readStateFromFS(); SD.readStateFromFS();
SD.listCreated = SD.makeListOfLocallyCreatedFiles(); SD.listCreated = SD.makeListOfLocallyCreatedFiles();
@ -56,36 +58,25 @@ public class Flow implements Runnable {
SD.doDeleteOpsOnOtherSDs(); SD.doDeleteOpsOnOtherSDs();
SD.doModifyOpsOnOtherSDs(); SD.doModifyOpsOnOtherSDs();
SD.writeStateFile(SD.path); SD.writeStateFile(new StateFile(SD.path));
} }
public String getState() {
return state == null ? "NONE" : state;
}
/** /**
* For every single SyncDirectory try to read it's StateFile. <p> * For every single SyncDirectory try to read it's StateFile. <p>
* If the StateFile is missing, then create a StateFile. * If the StateFile is missing, then create a StateFile.
*/ */
private void readOrMakeStateFile() { private void readOrMakeStateFile() {
for (var syncMapEntry : MapOfSyncMaps.get().entrySet()) { DataRoot.get().values().forEach(syncBundle -> {
SyncMap syncMap = syncMapEntry.getValue(); for (var sd : syncBundle.syncDirectories.values()) {
state = syncMap.toString(); var stateFile = new StateFile(sd.path);
if (stateFile.exists()) {
for (var stringSyncDirectoryEntry : syncMap.syncDirectories.entrySet()) { LOGGER.info("READ-STATE-FILE-" + sd.readStateFile());
SyncDirectory SD = stringSyncDirectoryEntry.getValue();
String path = SD.path;
String stateFilePath = tools.stateFilePath(path);
if (new File(stateFilePath).exists()) {
state = "READ-STATE-FILE-" + SD.readStateFile();
} else { } else {
SD.writeStateFile(path); sd.writeStateFile(new StateFile(sd.path));
} }
} }
});
}
} }
} }

@ -0,0 +1,50 @@
package com.olexyn.ensync;
import java.io.IOException;
import java.util.Date;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class LogUtil {
private static final String format = "[%1$tF %1$tT] [%4$-7s] %5$-120s [%2$s]\n";
public static Logger get(Class<?> c) {
return get(c, Level.INFO);
}
public static Logger get(Class<?> c, Level level) {
System.setProperty("java.util.logging.SimpleFormatter.format", format);
Logger logger = Logger.getLogger(c.getName());
try {
String dir = System.getProperty("user.dir") + "/log/main.log";
FileHandler fh = new FileHandler(dir, true);
fh.setFormatter(new SimpleFormatter() {
@Override
public synchronized String format(LogRecord logRecord) {
String msg = logRecord.getMessage();
return String.format(format,
new Date(logRecord.getMillis()),
logRecord.getSourceClassName() + " " + logRecord.getSourceMethodName(),
"",
logRecord.getLevel().getLocalizedName(),
msg
);
}
});
logger.addHandler(fh);
logger.setLevel(level);
} catch (NullPointerException | IOException e) {
e.printStackTrace();
}
return logger;
}
}

@ -1,49 +0,0 @@
package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.MapOfSyncMaps;
import com.olexyn.ensync.artifacts.SyncMap;
import com.olexyn.ensync.ui.UI;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
final public static Thread UI_THREAD = new Thread(new UI(), "ui");
final public static Thread FLOW_THREAD = new Thread(new Flow(), "flow");
final private static Tools tools = new Tools();
public static void main(String[] args) {
OperationMode operationMode = OperationMode.JSON;
switch (operationMode) {
case JAVA_FX:
UI_THREAD.start();
break;
case JSON:
String configPath = System.getProperty("user.dir") + "/src/main/resources/config.json";
String configString = tools.fileToString(new File(configPath));
JSONObject jsonMapOfSyncMaps = new JSONObject(configString).getJSONObject("jsonMapOfSyncMaps");
for (String key : jsonMapOfSyncMaps.keySet()) {
SyncMap syncMap = new SyncMap(key);
for (Object jsonSyncDirPath : jsonMapOfSyncMaps.getJSONArray(key).toList()) {
syncMap.addDirectory(jsonSyncDirPath.toString());
}
MapOfSyncMaps.get().put(key, syncMap);
}
break;
default:
}
FLOW_THREAD.start();
}
}

@ -0,0 +1,34 @@
package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.DataRoot;
import com.olexyn.ensync.artifacts.SyncBundle;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
public class MainApp {
final public static Thread FLOW_THREAD = new Thread(new Flow(), "flow");
final private static Tools tools = new Tools();
public static void main(String[] args) throws JSONException {
String configPath = System.getProperty("user.dir") + "/src/main/resources/config.json";
String configString = tools.fileToString(new File(configPath));
JSONObject dataRoot = new JSONObject(configString).getJSONObject("dataRoot");
for (String bundleKey : dataRoot.keySet()) {
SyncBundle syncBundle = new SyncBundle(bundleKey);
dataRoot.getJSONArray(bundleKey).toList()
.forEach(
directoryPath -> syncBundle.addDirectory(directoryPath.toString())
);
DataRoot.get().put(bundleKey, syncBundle);
}
FLOW_THREAD.start();
}
}

@ -125,19 +125,9 @@ public class Tools {
* @param list <i>StringBuilder</i> * @param list <i>StringBuilder</i>
*/ */
public void writeStringListToFile(String path, List<String> list) { public void writeStringListToFile(String path, List<String> list) {
File file = new File(path);
File parent = new File(file.getParent());
if (!parent.exists()) {
x.execute(new String[]{"mkdir",
"-p",
parent.getPath()});
}
try { try {
BufferedWriter bw = new BufferedWriter(new FileWriter(new File(path))); var bw = new BufferedWriter(new FileWriter(path));
StringBuilder sb = stringListToSb(list); var sb = stringListToSb(list);
bw.write(sb.toString()); bw.write(sb.toString());
bw.close(); bw.close();
} catch (Exception e) { } catch (Exception e) {
@ -145,7 +135,4 @@ public class Tools {
} }
} }
public String stateFilePath(String path) {
return "/tmp/ensync/state" + path.replace("/", "-");
}
} }

@ -0,0 +1,7 @@
package com.olexyn.ensync.artifacts;
public interface Constants {
String STATE_FILE_NAME = "state.ensync";
}

@ -2,13 +2,13 @@ package com.olexyn.ensync.artifacts;
import java.util.HashMap; import java.util.HashMap;
public class MapOfSyncMaps { public class DataRoot {
private static HashMap<String, SyncMap> mapOfSyncMaps; private static HashMap<String, SyncBundle> mapOfSyncMaps;
private MapOfSyncMaps() {} private DataRoot() {}
public static HashMap<String, SyncMap> get() { public static HashMap<String, SyncBundle> get() {
if (mapOfSyncMaps == null) { if (mapOfSyncMaps == null) {
mapOfSyncMaps = new HashMap<>(); mapOfSyncMaps = new HashMap<>();

@ -0,0 +1,19 @@
package com.olexyn.ensync.artifacts
import java.io.File
class StateFile(val targetPath: String) {
fun getPath(): String {
return targetPath + Constants.STATE_FILE_NAME
}
private fun getFile(): File {
return File(getPath())
}
fun exists(): Boolean {
return getFile().exists();
}
}

@ -11,7 +11,7 @@ import java.util.Map;
* A SyncMap is a map of SyncDirectories. <br> * A SyncMap is a map of SyncDirectories. <br>
* It synchronizes the SyncDirectories it contains. * It synchronizes the SyncDirectories it contains.
*/ */
public class SyncMap { public class SyncBundle {
public String name; public String name;
public Map<String, SyncDirectory> syncDirectories = new HashMap<>(); public Map<String, SyncDirectory> syncDirectories = new HashMap<>();
@ -19,22 +19,22 @@ public class SyncMap {
Tools tools = new Tools(); Tools tools = new Tools();
/** /**
* @see SyncMap * @see SyncBundle
*/ */
public SyncMap(String name) { public SyncBundle(String name) {
this.name = name; this.name = name;
} }
/** /**
* Creates a new Syncdirectory. <p> * Creates a new SyncDirectory. <p>
* Adds the created SyncDirectory to this SyncMap. * Adds the created SyncDirectory to this SyncBundle.
* *
* @param realPath the path from which the SyncDirectory is created. * @param path the path from which the SyncDirectory is created.
* @see SyncDirectory * @see SyncDirectory
*/ */
public void addDirectory(String realPath) { public void addDirectory(String path) {
if (new File(realPath).isDirectory()) { if (new File(path).isDirectory()) {
syncDirectories.put(realPath, new SyncDirectory(realPath, this)); syncDirectories.put(path, new SyncDirectory(path, this));
} }
} }
@ -42,5 +42,4 @@ public class SyncMap {
syncDirectories.remove(realPath); syncDirectories.remove(realPath);
} }
} }

@ -1,24 +1,33 @@
package com.olexyn.ensync.artifacts; package com.olexyn.ensync.artifacts;
import com.olexyn.ensync.Execute; import com.olexyn.ensync.Execute;
import com.olexyn.ensync.Flow;
import com.olexyn.ensync.LogUtil;
import com.olexyn.ensync.Tools; import com.olexyn.ensync.Tools;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger;
/** /**
* A SyncDirectory is a singular occurrence of a directory in the filesystems. * A SyncDirectory is a singular occurrence of a directory in the filesystems.
*/ */
public class SyncDirectory { public class SyncDirectory {
private static final Logger LOGGER = LogUtil.get(SyncDirectory.class);
private String flowState; private String flowState;
private SyncDirectory thisSD = this; private SyncDirectory thisSD = this;
private final SyncMap syncMap; private final SyncBundle syncMap;
public String path = null; public String path = null;
public Map<String, SyncFile> listCreated = new HashMap<>(); public Map<String, SyncFile> listCreated = new HashMap<>();
@ -32,9 +41,9 @@ public class SyncDirectory {
/** /**
* Create a SyncDirectory from realPath. * Create a SyncDirectory from realPath.
* *
* @see SyncMap * @see SyncBundle
*/ */
public SyncDirectory(String path, SyncMap syncMap) { public SyncDirectory(String path, SyncBundle syncMap) {
this.path = path; this.path = path;
this.syncMap = syncMap; this.syncMap = syncMap;
@ -70,7 +79,8 @@ public class SyncDirectory {
*/ */
public Map<String, SyncFile> readStateFile() { public Map<String, SyncFile> readStateFile() {
Map<String, SyncFile> filemap = new HashMap<>(); Map<String, SyncFile> filemap = new HashMap<>();
List<String> lines = tools.fileToLines(new File(tools.stateFilePath(path))); var stateFile = new StateFile(path);
List<String> lines = tools.fileToLines(new File(stateFile.getPath()));
for (String line : lines) { for (String line : lines) {
// this is a predefined format: "modification-time path" // this is a predefined format: "modification-time path"
@ -162,22 +172,24 @@ public class SyncDirectory {
* QUERY state of the filesystem at realPath. * QUERY state of the filesystem at realPath.
* WRITE the state of the filesystem to file. * WRITE the state of the filesystem to file.
*/ */
public void writeStateFile(String path) { public void writeStateFile(StateFile stateFile) {
List<String> outputList = new ArrayList<>(); List<String> outputList = new ArrayList<>();
try {
Execute.TwoBr find = x.execute(new String[]{"find", Files.walk(Paths.get(path))
path}); .filter(Files::isRegularFile)
.map(Path::toFile)
List<String> pathList = tools.brToListString(find.output); .filter(file -> !file.getName().equals(Constants.STATE_FILE_NAME))
.forEach(file -> {
String relativePath = file.getAbsolutePath()
for (String filePath : pathList) { .replace(stateFile.getTargetPath(), "");
long lastModified = new File(filePath).lastModified(); outputList.add("" + file.lastModified() + " " + relativePath);
outputList.add("" + lastModified + " " + filePath); });
} catch (IOException e) {
LOGGER.severe("Could walk the file tree : StateFile will be empty.");
} }
tools.writeStringListToFile(tools.stateFilePath(path), outputList); tools.writeStringListToFile(stateFile.getPath(), outputList);
} }

@ -1,55 +0,0 @@
package com.olexyn.ensync.ui;
import com.olexyn.ensync.artifacts.MapOfSyncMaps;
import com.olexyn.ensync.artifacts.SyncMap;
import java.io.File;
/**
* Connect the Controller and the Flow
*/
public class Bridge {
void newCollection(String collectionName) {
synchronized (MapOfSyncMaps.get()) {
MapOfSyncMaps.get().put(collectionName, new SyncMap(collectionName));
}
}
void removeCollection(String collectionName) {
synchronized (MapOfSyncMaps.get()) {
MapOfSyncMaps.get().remove(collectionName);
}
}
void addDirectory(String collectionName, File diretory) {
synchronized (MapOfSyncMaps.get()) {
MapOfSyncMaps.get().get(collectionName).addDirectory(diretory.getAbsolutePath());
}
//TODO pause syning when adding
}
/**
* This works, because a directory, which here is an unique ablsolute path,
* is only supposed to present once across entire SYNC.
*/
void removeDirectory(String directoryAbsolutePath) {
//TODO fix ConcurrentModificationException. This will possibly resolve further errors.
synchronized (MapOfSyncMaps.get()) {
for (var syncMap : MapOfSyncMaps.get().entrySet()) {
syncMap.getValue().removeDirectory(directoryAbsolutePath);
}
}
}
}

@ -1,237 +0,0 @@
package com.olexyn.ensync.ui;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.DirectoryChooser;
import javafx.stage.Window;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
/***
* Controller class for JavaFX. Contains the application logic.
*/
public class Controller implements Initializable {
final static int COLUMN_COUNT = 5; // How many columns should the GridPane have? Adjust if necessary.
final static Text SPACE = new Text(""); // Placeholder
int collection_count = 0;
Bridge bridge = new Bridge();
@FXML
protected GridPane gridPane;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
Text end = new Text("");
end.setId("end");
Button newCollectionButton = new Button("New Collection");
newCollectionButton.setId("newCollectionButton");
newCollectionButton.setOnAction(event -> { this.newCollection();});
gridPane.add(end, 0, 0);
List<Node> nodeList = new ArrayList<>(gridPane.getChildren());
List<Node> payload = Arrays.asList(new Text(""), new Text(""), new Text(""), new Text(""), newCollectionButton);
insertPayload(nodeList, payload, "end", 0);
redraw(gridPane, nodeList);
}
private void newCollection() {
String collectionName = "name" + collection_count++;
bridge.newCollection(collectionName);
TextField collectionStateTextField = new TextField();
collectionStateTextField.setText("STATE");
collectionStateTextField.setStyle("-fx-text-fill: green");
collectionStateTextField.setDisable(true);
collectionStateTextField.setId("collectionStateTextField-" + collectionName);
Button removeCollectionButton = new Button("Remove Collection");
removeCollectionButton.setId("removeCollectionButton-" + collectionName);
removeCollectionButton.setOnAction(event -> { this.removeCollection(collectionName);});
TextField addDirectoryTextField = new TextField();
addDirectoryTextField.setId("addDirectoryTextField-" + collectionName);
Button addDirectoryButton = new Button("Add Directory");
addDirectoryButton.setId("addDirectoryButton-" + collectionName);
addDirectoryButton.setOnAction(event -> { this.addDirectory(collectionName);});
List<Node> nodeList = new ArrayList<>(gridPane.getChildren());
List<Node> payload = new ArrayList<>();
payload.addAll(Arrays.asList(new Text(""), new Text(""), collectionStateTextField, new Text(""), removeCollectionButton));
payload.addAll(Arrays.asList(addDirectoryTextField, new Text(""), new Text(""), new Text(""), addDirectoryButton));
insertPayload(nodeList, payload, "newCollectionButton", -4);
redraw(gridPane, nodeList);
}
/**
* For the order & number of Nodes see ui-design.png .
* Remove the "Remove-Collection-Line" and the consecutive lines until and including the "Add-Directory-Line".
* The !=null expression protects from Text("") placeholders, who have id==null.
*/
private void removeCollection(String collectionName) {
bridge.removeCollection(collectionName);
List<Node> nodeList = new ArrayList<>(gridPane.getChildren());
here:
for (Node node : nodeList) {
if (node.getId() != null && node.getId().equals("removeCollectionButton-" + collectionName)) {
int i = nodeList.indexOf(node) - 4;
while (i < nodeList.size()) {
nodeList.remove(i);
if (nodeList.get(i).getId() != null && nodeList.get(i).getId().equals("addDirectoryButton-" + collectionName)) {
nodeList.remove(i);
break here;
}
}
}
}
redraw(gridPane, nodeList);
}
/**
*
*/
private void addDirectory(String collectionName) {
// TODO throw error if other collection already contains absollutepath
Window stage = gridPane.getScene().getWindow();
final DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Select Directory.");
directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
File directory = directoryChooser.showDialog(stage);
if (directory != null) {
bridge.addDirectory(collectionName, directory);
TextField directoryPathTextField = new TextField();
directoryPathTextField.setText(directory.getAbsolutePath());
directoryPathTextField.setDisable(true);
directoryPathTextField.setId("directoryPathTextField-" + directory.getAbsolutePath());
TextField directoryStateTextField = new TextField();
directoryStateTextField.setText("STATE");
directoryStateTextField.setStyle("-fx-text-fill: green");
directoryStateTextField.setDisable(true);
directoryStateTextField.setId("directoryStateTextField-" + directory.getAbsolutePath());
Button removeDirectoryButton = new Button("Remove");
removeDirectoryButton.setId("removeDirectoryButton-" + directory.getAbsolutePath());
removeDirectoryButton.setOnAction(event -> { this.removeDirectory(directory.getAbsolutePath());});
List<Node> nodeList = new ArrayList<>(gridPane.getChildren());
List<Node> payload = Arrays.asList(directoryPathTextField, new Text(""), directoryStateTextField, new Text(""), removeDirectoryButton);
insertPayload(nodeList, payload, "addDirectoryTextField-" + collectionName, 0);
redraw(gridPane, nodeList);
}
}
/**
* Find the Node with @param id.
* Insert the contents of the @param payload starting from the last.
* This pushes the Node with @param id forward.
*/
private void insertPayload(List<Node> nodeList, List<Node> payload, String id, int offset) {
for (Node node : nodeList) {
if (node.getId() != null && node.getId().equals(id)) {
int i = nodeList.indexOf(node) + offset;
for (int j = payload.size() - 1; j >= 0; j--) {
nodeList.add(i, payload.get(j));
}
break;
}
}
}
/**
* Clear the gridPane and redraw it with contents of nodeList.
*/
private void redraw(GridPane gridPane, List<Node> nodeList) {
gridPane.getChildren().clear();
int col = 0, row = 0;
for (Node node : nodeList) {
if (nodeList.indexOf(node) % COLUMN_COUNT == 0) {
row++;
col = 0;
}
gridPane.add(node, col, row);
col++;
}
}
private void removeDirectory(String directoryAbsolutePath) {
bridge.removeDirectory(directoryAbsolutePath);
List<Node> nodeList = new ArrayList<>(gridPane.getChildren());
for (Node node : nodeList) {
if (node.getId() != null && node.getId().equals("removeDirectoryButton-" +directoryAbsolutePath)) {
int i = nodeList.indexOf(node) - 4;
for (int j = 0; j < 5; j++) {
nodeList.remove(i);
}
break;
}
}
redraw(gridPane, nodeList);
}
}

@ -1,27 +0,0 @@
package com.olexyn.ensync.ui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class UI extends Application implements Runnable {
@Override
public void start(Stage primaryStage) throws Exception {
// in IDEA add ";?.fxml;?.css" to File|Settings|Compiler settings
Parent root = FXMLLoader.load(getClass().getResource("/fxml/layout.fxml"));
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("EnSync");
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void run() {
UI.launch();
}
}

@ -1,12 +1,8 @@
{ {
"jsonMapOfSyncMaps": { "dataRoot": {
"syncMap1": [ "syncMap1": [
"/home/user/test/a", "P:\\ensync-test",
"/home/user/test/b" "C:\\Users\\user\\home\\ensync-test"
],
"syncMap2": [
"/home/user/test/c",
"/home/user/test/d"
] ]
} }
} }

@ -2,11 +2,10 @@ package com.olexyn.ensync.files;
import com.olexyn.ensync.Execute; import com.olexyn.ensync.Execute;
import com.olexyn.ensync.Flow; import com.olexyn.ensync.Flow;
import com.olexyn.ensync.OperationMode;
import com.olexyn.ensync.Tools; import com.olexyn.ensync.Tools;
import com.olexyn.ensync.artifacts.MapOfSyncMaps; import com.olexyn.ensync.artifacts.DataRoot;
import com.olexyn.ensync.artifacts.SyncMap; import com.olexyn.ensync.artifacts.SyncBundle;
import com.olexyn.ensync.ui.UI; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -24,7 +23,7 @@ public class FileTest {
final public static Thread FLOW_THREAD = new Thread(new Flow(), "flow"); final public static Thread FLOW_THREAD = new Thread(new Flow(), "flow");
final private static Tools tools = new Tools(); final private static Tools tools = new Tools();
final private HashMap<String, SyncMap> mapOfSyncMaps = MapOfSyncMaps.get(); final private HashMap<String, SyncBundle> mapOfSyncMaps = DataRoot.get();
public long fileOpsPause = 800; public long fileOpsPause = 800;
public long assertPause = 4000; public long assertPause = 4000;
@ -85,19 +84,19 @@ public class FileTest {
* Simple means with a static test-config.json, thus no SyncMaps are added at runtime. * Simple means with a static test-config.json, thus no SyncMaps are added at runtime.
*/ */
@Test @Test
public void doSimpleFileTests() { public void doSimpleFileTests() throws JSONException {
String configPath = System.getProperty("user.dir") + "/src/test/resources/test-config.json"; String configPath = System.getProperty("user.dir") + "/src/test/resources/test-config.json";
String configString = tools.fileToString(new File(configPath)); String configString = tools.fileToString(new File(configPath));
JSONObject jsonMapOfSyncMaps = new JSONObject(configString).getJSONObject("jsonMapOfSyncMaps"); JSONObject jsonMapOfSyncMaps = new JSONObject(configString).getJSONObject("jsonMapOfSyncMaps");
for (String key : jsonMapOfSyncMaps.keySet()) { // for (String key : jsonMapOfSyncMaps.keySet()) {
SyncMap syncMap = new SyncMap(key); // SyncMap syncMap = new SyncMap(key);
for (Object jsonSyncDirPath : jsonMapOfSyncMaps.getJSONArray(key).toList()) { // for (Object jsonSyncDirPath : jsonMapOfSyncMaps.getJSONArray(key).toList()) {
syncMap.addDirectory(jsonSyncDirPath.toString()); // syncMap.addDirectory(jsonSyncDirPath.toString());
} // }
MapOfSyncMaps.get().put(key, syncMap); // MapOfSyncMaps.get().put(key, syncMap);
//
} // }
FLOW_THREAD.start(); FLOW_THREAD.start();

Loading…
Cancel
Save