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…
Reference in new issue