shuffle project

pull/1/head
Ivan Olexyn 4 years ago
parent 4989f1d283
commit 89f1a3f4bb

@ -0,0 +1,141 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.olexyn.ensync</groupId>
<artifactId>ensync</artifactId>
<version>0.1</version>
<name>ensync</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</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>
<groupId>javafx</groupId>
<artifactId>javafx.swt</artifactId>
<version>11.0.2</version>
<scope>system</scope>
<systemPath>/home/user/app/javafx-sdk-11.0.2/lib/javafx-swt.jar</systemPath>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

@ -0,0 +1,13 @@
package com.olexyn.ensync;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}

@ -0,0 +1,54 @@
package com.olexyn.ensync;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;
public class Execute {
/**
* @param cmd an array representing a shell command
* @return <i>TwoBr</i> class, containing two BufferedReaders,
* <i>output</i> and <i>error</i>
* @see <i>output</i> BufferedReader, corresponds to STDOUT
* <i>error</i> BufferedReader, corresponds to STDERR
*/
public TwoBr execute(String cmd[]) {
TwoBr twobr = new TwoBr();
try {
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
twobr.output = new BufferedReader(new InputStreamReader(process.getInputStream()));
twobr.error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
} catch (Exception e) {
e.printStackTrace();
}
return twobr;
}
public TwoBr execute(List<String> cmd) {
String[] cmdArr = new String[cmd.size()];
for (int i = 0; i < cmd.size(); i++) {
cmdArr[i] = cmd.get(i);
}
return execute(cmdArr);
}
public void executeBatch(List<String[]> batch) {
for (String[] strings : batch) {
execute(strings);
}
}
public class TwoBr {
public BufferedReader output;
public BufferedReader error;
}
}

@ -0,0 +1,93 @@
package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.SyncDirectory;
import com.olexyn.ensync.artifacts.SyncMap;
import java.io.File;
import static com.olexyn.ensync.Main.MAP_OF_SYNCMAPS;
public class Flow implements Runnable {
Tools tools = new Tools();
private String state;
public void run() {
while (true) {
synchronized (MAP_OF_SYNCMAPS) {
readOrMakeStateFile();
for (var syncMapEntry : MAP_OF_SYNCMAPS.entrySet()) {
for (var SDEntry : syncMapEntry.getValue().syncDirectories.entrySet()) {
SyncDirectory SD = SDEntry.getValue();
state = "READ";
SD.readFreshState();
SD.listCreated = SD.makeListCreated();
SD.listDeleted = SD.makeListDeleted();
SD.listModified = SD.makeListModified();
SD.doCreate();
SD.doDelete();
SD.doModify();
SD.writeStateFile(SD.path);
}
}
}
try {
long pause = 2000;
System.out.println("Pausing... for "+pause+ "ms.");
Thread.sleep(pause);
} catch (InterruptedException ignored) {
}
}
}
public String getState() {
return state == null ? "NONE" : state;
}
/**
* For every single SyncDirectory try to read it's StateFile. <p>
* If the StateFile is missing, then create a StateFile.
*/
private void readOrMakeStateFile() {
for (var syncMapEntry : MAP_OF_SYNCMAPS.entrySet()) {
SyncMap syncMap = syncMapEntry.getValue();
state = syncMap.toString();
for (var stringSyncDirectoryEntry : syncMap.syncDirectories.entrySet()) {
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 {
SD.writeStateFile(path);
}
}
}
}
}

@ -0,0 +1,45 @@
package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.SyncMap;
import com.olexyn.ensync.ui.UI;
import java.util.HashMap;
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 public static HashMap<String, SyncMap> MAP_OF_SYNCMAPS = new HashMap<>();
public static void main(String[] args) {
UI_THREAD.start();
FLOW_THREAD.start();
}
}

@ -0,0 +1,151 @@
package com.olexyn.ensync;
import com.olexyn.ensync.artifacts.SyncFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Tools {
private final Execute x;
public Tools() {
x = new Execute();
}
/**
* Convert BufferedReader to String.
*
* @param br BufferedReader
* @return String
*/
public String brToString(BufferedReader br) {
StringBuilder sb = new StringBuilder();
Object[] br_array = br.lines().toArray();
for (int i = 0; i < br_array.length; i++) {
sb.append(br_array[i].toString() + "\n");
}
return sb.toString();
}
/**
* Convert BufferedReader to List of Strings.
*
* @param br BufferedReader
* @return List
*/
public List<String> brToListString(BufferedReader br) {
List<String> list = new ArrayList<>();
Object[] br_array = br.lines().toArray();
for (int i = 0; i < br_array.length; i++) {
list.add(br_array[i].toString());
}
return list;
}
public List<String> fileToLines(File file) {
String filePath = file.getPath();
List<String> lines = null;
try {
lines = Files.readAllLines(Paths.get(filePath));
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
public String fileToString(File file){
List<String> lineList = fileToLines(file);
StringBuilder sb = new StringBuilder();
for (String line : lineList){
sb.append(line).append("\n");
}
return sb.toString();
}
public Map<String, SyncFile> mapMinus(Map<String, SyncFile> fromA, Map<String, SyncFile> substractB) {
Map<String, SyncFile> difference = new HashMap<>();
for (Map.Entry<String, SyncFile> entry : fromA.entrySet()) {
String key = entry.getKey();
if (fromA.containsKey(key) && !substractB.containsKey(key)) {
SyncFile file = fromA.get(key);
difference.put(key, file);
}
}
return difference;
}
public StringBuilder stringListToSb(List<String> list) {
StringBuilder sb = new StringBuilder();
for (String line : list) {
sb.append(line + "\n");
}
return sb;
}
/**
* Write sb to file at path .
*
* @param path <i>String</i>
* @param sb <i>StringBuilder</i>
*/
public void writeSbToPath(String path, StringBuilder sb) {
writeSbToFile(new File(path), sb);
}
public void writeSbToFile(File file, StringBuilder sb) {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
bw.write(sb.toString());
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Write List of String to file at path .
*
* @param path <i>String</i>
* @param list <i>StringBuilder</i>
*/
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 {
BufferedWriter bw = new BufferedWriter(new FileWriter(new File(path)));
StringBuilder sb = stringListToSb(list);
bw.write(sb.toString());
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public String stateFilePath(String path) {
return "/tmp/ensync/state" + path.replace("/", "-");
}
}

@ -0,0 +1,362 @@
package com.olexyn.ensync.artifacts;
import com.olexyn.ensync.Execute;
import com.olexyn.ensync.Tools;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A SyncDirectory is an occurrence of a particular directory somewhere across the filesystems.
*/
public class SyncDirectory {
private String flowState;
private SyncDirectory thisSD = this;
private SyncMap syncMap;
public String path = null;
public Map<String, SyncFile> listCreated = new HashMap<>();
public Map<String, SyncFile> listDeleted = new HashMap<>();
public Map<String, SyncFile> listModified = new HashMap<>();
Tools tools = new Tools();
Execute x = new Execute();
/**
* Create a SyncDirectory from realPath.
*
* @see SyncMap
*/
public SyncDirectory(String path, SyncMap syncMap) {
this.path = path;
this.syncMap = syncMap;
}
/**
* Get the current state by using the `find` command.
*/
public Map<String, SyncFile> readFreshState() {
//NOTE that the SFile().lastModifiedOld is not set here, so it is 0 by default.
Map<String, SyncFile> filemap = new HashMap<>();
Execute.TwoBr find = x.execute(new String[]{"find",
path});
List<String> pathList = tools.brToListString(find.output);
for (String filePath : pathList) {
SyncFile file = new SyncFile(this, filePath);
filemap.put(filePath, file);
}
return filemap;
}
/**
* READ the contents of StateFile to Map.
*/
public Map<String, SyncFile> readStateFile() {
Map<String, SyncFile> filemap = new HashMap<>();
List<String> lines = tools.fileToLines(new File(tools.stateFilePath(path)));
for (String line : lines) {
// this is a predefined format: "modification-time path"
String modTimeString = line.split(" ")[0];
long modTime = Long.parseLong(modTimeString);
String sFilePath = line.replace(modTimeString + " ", "");
SyncFile sfile = new SyncFile(this, sFilePath);
sfile.setTimeModifiedFromStateFile(modTime);
filemap.put(sFilePath, sfile);
}
return filemap;
}
/**
* Compare the OLD and NEW pools.
* List is cleared and created each time.
*/
public Map<String, SyncFile> makeListCreated() {
Map<String, SyncFile> fromA = readFreshState();
Map<String, SyncFile> substractB = readStateFile();
return tools.mapMinus(fromA, substractB);
}
/**
* Compare the OLD and NEW pools.
* List is cleared and created each time.
*/
public Map<String, SyncFile> makeListDeleted() {
Map<String, SyncFile> fromA = readStateFile();
Map<String, SyncFile> substractB = readFreshState();
Map<String, SyncFile> listDeleted = tools.mapMinus(fromA, substractB);
Map<String, SyncFile> swap = new HashMap<>();
for (var entry : listDeleted.entrySet()) {
String key = entry.getKey();
String parentKey = entry.getValue().getParent();
if (listDeleted.containsKey(parentKey) || swap.containsKey(parentKey)) {
swap.put(key, listDeleted.get(key));
}
}
return tools.mapMinus(listDeleted, swap);
}
/**
* Compare the OLD and NEW pools.
* List is cleared and created each time.
*/
public Map<String, SyncFile> makeListModified() {
Map<String, SyncFile> listModified = new HashMap<>();
Map<String, SyncFile> stateFileMap = readStateFile();
for (var freshFileEntry : readFreshState().entrySet()) {
String freshFileKey = freshFileEntry.getKey();
SyncFile freshFile = freshFileEntry.getValue();
if (freshFile.isDirectory()) { continue;} // no need to modify Directories, the Filesystem will do that, if a File changed.
// If KEY exists in OLD , thus FILE was NOT created.
boolean oldFileExists = stateFileMap.containsKey(freshFileKey);
boolean fileIsFresher = freshFile.getTimeModified() > freshFile.getTimeModifiedFromStateFile();
if (oldFileExists && fileIsFresher) {
listModified.put(freshFileKey, freshFile);
}
}
return listModified;
}
/**
* QUERY state of the filesystem at realPath.
* WRITE the state of the filesystem to file.
*/
public void writeStateFile(String path) {
List<String> outputList = new ArrayList<>();
Execute.TwoBr find = x.execute(new String[]{"find",
path});
List<String> pathList = tools.brToListString(find.output);
for (String filePath : pathList) {
long lastModified = new File(filePath).lastModified();
outputList.add("" + lastModified + " " + filePath);
}
tools.writeStringListToFile(tools.stateFilePath(path), outputList);
}
private class Info {
private String thisFilePath;
private String otherFilePath;
private String otherParentPath;
private File otherParentFile;
private Info(SyncDirectory thisSD, SyncFile sFile, SyncDirectory otherSD) {
// Example:
// syncDirectory /foo
// otherSyncDirectory /bar
// createdFile /foo/hello/created-file.gif
// relativePath /hello/created-file.gif
String relativePath = sFile.getPath().replace(thisSD.path, "");
this.thisFilePath = sFile.getPath();
this.otherFilePath = otherSD.path + relativePath;
File otherFile = new File(otherFilePath);
this.otherParentPath = otherFile.getParent();
this.otherParentFile = new File(otherParentPath);
}
}
public void doCreate() {
for (var entry : listCreated.entrySet()) {
SyncFile createdFile = entry.getValue();
for (var otherEntry : syncMap.syncDirectories.entrySet()) {
SyncDirectory otherSD = otherEntry.getValue();
if (this.equals(otherSD)) { continue; }
Info info = new Info(this, createdFile, otherSD);
writeFile(info, this, createdFile, otherSD);
}
}
}
/**
*
*/
public void doDelete() {
for (var entry : listDeleted.entrySet()) {
SyncFile deletedFile = entry.getValue();
for (var otherEntry : syncMap.syncDirectories.entrySet()) {
SyncDirectory otherSD = otherEntry.getValue();
if (this.equals(otherSD)) { continue; }
Info info = new Info(thisSD, deletedFile, otherSD);
deleteFile(info, thisSD, deletedFile, otherSD);
}
}
}
private void deleteFile(Info info, SyncDirectory thisSD, SyncFile thisFile, SyncDirectory otherSD) {
SyncFile otherFile = new SyncFile(otherSD, otherSD.path + thisFile.relativePath);
if (!otherFile.exists()) { return;}
// if the otherFile was created with ensync it will have the == TimeModified.
long thisFileTimeModified = thisFile.getTimeModified();
long otherFileTimeModified = otherFile.getTimeModified();
if (thisFile.getTimeModified() >= otherFile.getTimeModified()) {
List<String> cmd = List.of("rm", "-r", info.otherFilePath);
x.execute(cmd);
}
}
public void doModify() {
for (var entry : listModified.entrySet()) {
SyncFile modifiedFile = entry.getValue();
for (var otherEntry : syncMap.syncDirectories.entrySet()) {
SyncDirectory otherSD = otherEntry.getValue();
if (this.equals(otherSD)) { continue; }
Info info = new Info(this, modifiedFile, otherSD);
writeFile(info, this, modifiedFile, otherSD);
}
}
}
private void writeFile(Info info, SyncDirectory thisSD, SyncFile thisFile, SyncDirectory otherSD) {
SyncFile otherFile = new SyncFile(otherSD, otherSD.path + thisFile.relativePath);
if (otherFile.exists() && thisFile.getTimeModified() < otherFile.getTimeModified()) { return;}
if (thisFile.isDirectory() && !otherFile.exists()) {
List<String> cmd = List.of("mkdir", "-p", info.otherFilePath);
x.execute(cmd);
return;
}
if (thisFile.isFile()) {
if (!info.otherParentFile.exists()) {
makeParentChain(otherFile, thisFile);
// List<String> cmd = List.of("mkdir", "-p", info.otherParentPath);
//x.execute(cmd);
}
List<String> cmd = List.of("cp", "-p", info.thisFilePath, info.otherFilePath);
x.execute(cmd);
copyModifDate(thisFile.getParentFile(), otherFile.getParentFile());
}
}
private void makeParentChain(File otherFile, File thisFile) {
try {
File otherParent = new File(otherFile.getParent());
File thisParent = new File(thisFile.getParent());
if (!otherParent.exists()) {
makeParentChain(otherParent, thisParent);
makeParentChain(otherFile, thisFile);
} else if (thisFile.isDirectory()) {
List<String> cmd = List.of("mkdir", otherFile.getPath());
x.execute(cmd);
cmd = List.of("stat", "--format", "%y", thisFile.getPath());
String mDate = x.execute(cmd).output.readLine();
cmd = List.of("touch", "-m", "--date=" + mDate, otherFile.getPath());
String error = x.execute(cmd).error.readLine();
int br = 0;
}
} catch (Exception ignored) {}
}
private void copyModifDate(File fromFile, File toFile) {
try {
List<String> cmd = List.of("stat", "--format", "%y", fromFile.getPath());
String mDate = x.execute(cmd).output.readLine();
cmd = List.of("touch", "-m", "--date=" + mDate, toFile.getPath());
x.execute(cmd);
} catch (Exception ignored) {}
}
}

@ -0,0 +1,63 @@
package com.olexyn.ensync.artifacts;
import java.io.File;
import java.util.Map;
public class SyncFile extends File {
// Very IMPORTANT field. Allows to store lastModified as it is stored in the StateFile.
public long timeModifiedFromStateFile = 0;
public String relativePath;
private SyncDirectory sd;
public SyncFile(SyncDirectory sd , String pathname) {
super(pathname);
this.sd = sd;
relativePath = this.getPath().replace(sd.path, "");
}
public void setTimeModifiedFromStateFile(long value){
timeModifiedFromStateFile = value;
}
public long getTimeModifiedFromStateFile(){
SyncFile record = sd.readStateFile().get(this.getPath());
return record == null ? 0 : record.timeModifiedFromStateFile;
}
/**
* If File exists on Disk get the TimeModified from there.
* Else try to read it from StateFile.
* Else return 0 ( = oldest possible time - a value of 0 can be seen as equal to "never existed").
* EXAMPLES:
* If a File was deleted, then the time will be taken from statefile.
* If a File never existed, it will have time = 0, and thus will always be overwritten.
*/
public long getTimeModified(){
if (this.exists()){
return lastModified();
}
if (sd.readStateFile().get(this.getPath())!=null){
return getTimeModifiedFromStateFile();
}
return 0;
}
}

@ -0,0 +1,45 @@
package com.olexyn.ensync.artifacts;
import com.olexyn.ensync.Tools;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* A SyncMap is the set of such SyncDirectories. The purpose of the SyncMap is to help syncronize the SyncDirectories.
*/
public class SyncMap {
public String name;
public Map<String, SyncDirectory> syncDirectories = new HashMap<>();
Tools tools = new Tools();
/**
* @see SyncMap
*/
public SyncMap(String name) {
this.name = name;
}
/**
* Creates a new Syncdirectory. <p>
* Adds the created SyncDirectory to this SyncMap.
*
* @param realPath the path from which the SyncDirectory is created.
* @see SyncDirectory
*/
public void addDirectory(String realPath) {
if (new File(realPath).isDirectory()) {
syncDirectories.put(realPath, new SyncDirectory(realPath, this));
}
}
public void removeDirectory(String realPath) {
syncDirectories.remove(realPath);
}
}

@ -0,0 +1,11 @@
<syncmap>
<name>SyncMap1</name>
<syncdirectory>
<name>SyncDirectory1</name>
<path>/foo/dir</path>
</syncdirectory>
<syncdirectory>
<name>SyncDirectory2</name>
<path>/bar/dir</path>
</syncdirectory>
</syncmap>

@ -0,0 +1,6 @@
#!/bin/bash
a=$1
b=$2
$a | $b
# this is a pipe

@ -0,0 +1,7 @@
#!/bin/bash
a=$1
b=$2
c=$3
$a | $b | $c
# this is a double pipe

@ -0,0 +1,7 @@
#!/bin/bash
a=$1
b=$2
c=$3
$a $b > $c

@ -0,0 +1,54 @@
package com.olexyn.ensync.ui;
import com.olexyn.ensync.artifacts.SyncMap;
import java.io.File;
import static com.olexyn.ensync.Main.MAP_OF_SYNCMAPS;
/**
* Connect the Controller and the Flow
*/
public class Bridge {
void newCollection(String collectionName) {
synchronized (MAP_OF_SYNCMAPS) {
MAP_OF_SYNCMAPS.put(collectionName, new SyncMap(collectionName));
}
}
void removeCollection(String collectionName) {
synchronized (MAP_OF_SYNCMAPS) {
MAP_OF_SYNCMAPS.remove(collectionName);
}
}
void addDirectory(String collectionName, File diretory) {
synchronized (MAP_OF_SYNCMAPS) {
MAP_OF_SYNCMAPS.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 (MAP_OF_SYNCMAPS) {
for (var syncMap : MAP_OF_SYNCMAPS.entrySet()) {
syncMap.getValue().removeDirectory(directoryAbsolutePath);
}
}
}
}

@ -0,0 +1,237 @@
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);
}
}

@ -0,0 +1,37 @@
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 {
Parent root = FXMLLoader.load(getClass().getResource("layout.fxml"));
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("EnSync");
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void run() {
UI.launch();
}
}

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?>
<ScrollPane fitToHeight="true" fitToWidth="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="394.0" prefWidth="672.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.olexyn.ensync.ui.Controller">
<content>
<GridPane fx:id="gridPane">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="200.0" prefWidth="200.0" />
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER" maxWidth="20.0" minWidth="20.0" prefWidth="20.0" />
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER" maxWidth="100.0" minWidth="100.0" prefWidth="100.0" />
<ColumnConstraints fillWidth="false" halignment="CENTER" hgrow="NEVER" maxWidth="20.0" minWidth="20.0" prefWidth="20.0" />
<ColumnConstraints fillWidth="false" halignment="LEFT" hgrow="NEVER" maxWidth="200.0" minWidth="200.0" prefWidth="200.0" />
</columnConstraints>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
</rowConstraints>
</GridPane>
</content>
</ScrollPane>

@ -0,0 +1,20 @@
package com.olexyn.ensync;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* Unit test for simple App.
*/
public class AppTest
{
/**
* Rigorous Test :-)
*/
@Test
public void shouldAnswerWithTrue()
{
assertTrue( true );
}
}

@ -0,0 +1,148 @@
package com.olexyn.ensync.files;
import com.olexyn.ensync.Execute;
import com.olexyn.ensync.Tools;
import org.junit.Assert;
import java.io.File;
public class FileTest {
Execute x = new Execute();
Tools tools = new Tools();
private static final String PATH = System.getProperty("user.dir");
private static final String fileAPath = "asdf";
private static final String fileBPath = "asff";
private final File a = new File(fileAPath);
private final File b = new File(fileBPath);
private void createFile(File file){
}
private void updateFile(File file){
}
private void deleteFile(File file){
}
public void deleteA(){
Assert.assertFalse(a.exists());
Assert.assertFalse(b.exists());
}
/**
* Simulates user activity on disk.
*/
void createFiles() throws InterruptedException {
StringBuilder sbA = new StringBuilder("a");
StringBuilder sbB = new StringBuilder("b");
// dv (deleted-void)
// TODO
// dd
tools.writeSbToPath(PATH+"/a/dd", sbA);
Thread.sleep(10);
tools.writeSbToPath(PATH+"/b/dd", sbB);
Thread.sleep(10);Thread.sleep(10);
x.execute(new String[]{"rm", PATH+"/a/dd"});
Thread.sleep(10);
x.execute(new String[]{"rm", PATH+"/b/dd"});
Thread.sleep(10);
// dc
tools.writeSbToPath(PATH+"/a/dc", sbA);
Thread.sleep(10);
x.execute(new String[]{"rm", PATH+"/a/dc"});
Thread.sleep(10);
tools.writeSbToPath(PATH+"/b/dc", sbB);
Thread.sleep(10);
// dm
tools.writeSbToPath(PATH+"/a/dm", sbA);
Thread.sleep(10);
x.execute(new String[]{"rm", PATH+"/a/dm"});
Thread.sleep(10);
tools.writeSbToPath(PATH+"/b/dm", sbB);
Thread.sleep(10);
// dv (deleted-void)
// TODO
// cd
// TODO
// cc
// TODO
// cm
// TODO
// cv (created-void)
// TODO
// md
// TODO
// mc
// TODO
// mm
// TODO
}
/**
* Checks if end-state is as desired.
* @throws Exception otherwise.
*/
void fileTest() throws Exception {
// Files where the second (= the newer) file was deleted. Thus both files should not exist in the end-state.
String[] arrayToDelete = {"/a/dd", "/b/dd" , "/a/cd", "/b/cd", "/a/md", "/b/md"};
for (String path : arrayToDelete){
if (new TestableFile(path).exists()) throw new Exception();
}
// Files where the second (= the newer) file was created or modified. Thus both files should contain "b" in the end-state.
String[] arrayToB = {"/a/dc", "/b/dc" , "/a/dm", "/b/dm", "/a/cc", "/b/cc"};
for (String path : arrayToB){
if (!new TestableFile(path).hasContent("b")) throw new Exception();
}
}
// Assertion Exception
}

@ -0,0 +1,21 @@
package com.olexyn.ensync.files;
import com.olexyn.ensync.Tools;
import java.io.File;
public class TestableFile extends File {
Tools tools = new Tools();
public TestableFile(String pathname) {
super(pathname);
}
public boolean hasContent(String s){
String line = tools.fileToLines(this).get(0);
return line.equals(s);
}
}

@ -0,0 +1,26 @@
### Testing Scenario
Test two configs:
1. FileOps happen while System is down.
1. FileOps happen while System is running.
<br>
| Symbol | Description|
---|---
`a` | File `a` in directory `A`
`b` | File `b` in directory `B`
`d(x)` | File `x` is deleted.
`c(x)` | File `x` is created.
`m(x)` | File `x` is modified.
<br>
| `Given` | | `When` | | `Then` | |
---|---|---|---|---|---
| `A` | `B`| `A` | `B`|`A` | `B`|
| `a` | | `d(a)` | | | |
| `a` | `b` | `d(a)` | `d(b)` | | |

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save