commit
268736c6c7
@ -0,0 +1,24 @@
|
||||
package app;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Artifacts {
|
||||
|
||||
Tools tools = new Tools();
|
||||
|
||||
|
||||
public MFile getMFile(File file) {
|
||||
MFile mfile = new MFile();
|
||||
mfile.file = file;
|
||||
mfile.md5 = tools.getMd5(file.getPath());
|
||||
return mfile;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class MFile {
|
||||
public File file;
|
||||
public String md5;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package app;
|
||||
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import app.Artifacts.MFile;
|
||||
|
||||
public class Controller {
|
||||
|
||||
|
||||
Map<Integer, MFile> doubles;
|
||||
|
||||
|
||||
@FXML
|
||||
protected Text loadDirState;
|
||||
|
||||
@FXML
|
||||
protected Text calcMd5State;
|
||||
|
||||
@FXML
|
||||
protected Text sortFileState;
|
||||
|
||||
@FXML
|
||||
protected Text findDoubleState;
|
||||
|
||||
@FXML
|
||||
protected Text delDoubleState;
|
||||
|
||||
@FXML
|
||||
protected Text fileNr;
|
||||
|
||||
@FXML
|
||||
protected Text doubleNr;
|
||||
|
||||
@FXML
|
||||
protected TextField dir;
|
||||
|
||||
|
||||
@FXML
|
||||
protected void loadDir() {
|
||||
|
||||
Task<Void> loadDirTask = new Task<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
|
||||
loadDirState.setText("");
|
||||
calcMd5State.setText("");
|
||||
sortFileState.setText("");
|
||||
findDoubleState.setText("");
|
||||
delDoubleState.setText("");
|
||||
fileNr.setText("Number of Files:");
|
||||
doubleNr.setText("Number of Doubles:");
|
||||
|
||||
|
||||
Path path = Paths.get(dir.getText());
|
||||
|
||||
if (!Files.isDirectory(path)) {
|
||||
loadDirState.setText("ERROR.");
|
||||
|
||||
} else {
|
||||
|
||||
Map<Integer, File> pool = new Routines().loadPool(dir.getText(), "file");
|
||||
new Write().textPool("pool", pool);
|
||||
loadDirState.setText("OK.");
|
||||
fileNr.setText("Number of Files: " + pool.size());
|
||||
|
||||
Map<Integer, MFile> md5Pool = new Routines().md5Pool(pool);
|
||||
new Write().textMd5Pool("md5Pool", md5Pool);
|
||||
calcMd5State.setText("OK.");
|
||||
|
||||
Map<Integer, MFile> qsMd5Pool = new QuicksortMd5().quicksortMd5(md5Pool);
|
||||
new Write().textMd5Pool("qsMd5Pool", qsMd5Pool);
|
||||
sortFileState.setText("OK.");
|
||||
|
||||
doubles = new Routines().doubles(qsMd5Pool);
|
||||
new Write().textMd5Pool("doubles", doubles);
|
||||
findDoubleState.setText("OK.");
|
||||
doubleNr.setText("Number of Doubles: " + doubles.size());
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
new Thread(loadDirTask).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
protected void deleteDoubles() {
|
||||
|
||||
Task<Void> delDoubleTask = new Task<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
|
||||
for (int i = 0; i < doubles.size(); i++) {
|
||||
new Execute().execute(new String[]{"rm", doubles.get(i).file.getAbsolutePath()});
|
||||
|
||||
}
|
||||
delDoubleState.setText("OK.");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
new Thread(delDoubleTask).start();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package app;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
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 class TwoBr {
|
||||
public BufferedReader output;
|
||||
public BufferedReader error;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package app;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
public class Main extends Application {
|
||||
|
||||
static String foo;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception{
|
||||
Parent root = FXMLLoader.load(getClass().getResource("layout.fxml"));
|
||||
Scene scene = new Scene(root, 300, 275);
|
||||
|
||||
|
||||
primaryStage.setTitle("mucc");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package app;
|
||||
|
||||
import app.Artifacts.MFile;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class QuicksortMd5 {
|
||||
private Map<Integer, MFile> md5Pool;
|
||||
private int i;
|
||||
private int j;
|
||||
private int p;
|
||||
|
||||
public Map<Integer, MFile> quicksortMd5(Map<Integer, MFile> md5Pool) {
|
||||
this.md5Pool = md5Pool;
|
||||
|
||||
quicksort(0, md5Pool.size() - 1);
|
||||
return this.md5Pool;
|
||||
}
|
||||
|
||||
private void quicksort(int low, int high) {
|
||||
i = low;
|
||||
j = high;
|
||||
p = low + (high - low) / 2;
|
||||
|
||||
while (i < j) {
|
||||
|
||||
// a < b a.compareto(b) = -1
|
||||
// a = b a.compareto(b) = 0
|
||||
// a > b a.compareto(b) = 1
|
||||
|
||||
while (i < p) {
|
||||
if (imd5().compareTo(pmd5()) <= 0) {
|
||||
i++;
|
||||
} else {
|
||||
swap(i, p);
|
||||
j = high;
|
||||
}
|
||||
}
|
||||
|
||||
while (p < j) {
|
||||
if (jmd5().compareTo(pmd5()) >= 0) {
|
||||
j--;
|
||||
} else {
|
||||
swap(p, j);
|
||||
i = low;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (high - p > 2) {
|
||||
quicksort(p, high);
|
||||
}
|
||||
if (p - low > 2) {
|
||||
quicksort(low, p);
|
||||
}
|
||||
}
|
||||
|
||||
private void swap(int a, int b) {
|
||||
MFile temp = md5Pool.get(a);
|
||||
md5Pool.put(a, md5Pool.get(b));
|
||||
md5Pool.put(b, temp);
|
||||
}
|
||||
|
||||
private String imd5() {
|
||||
return md5Pool.get(i).md5;
|
||||
}
|
||||
|
||||
private String jmd5() {
|
||||
return md5Pool.get(j).md5;
|
||||
}
|
||||
|
||||
private String pmd5() {
|
||||
return md5Pool.get(p).md5;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
### What is `mucc`?
|
||||
`mucc` is an example for interactions between Linux, Java and JavaFX.
|
||||
It is a tool for purging duplicates from
|
||||
a filesystem.
|
||||
|
||||
It does this by calculating a hash of each file
|
||||
and sorting the files by hash.
|
||||
If two files have the same hash, the older file will be deleted.
|
||||
|
||||
It also displays some information about what is happening.
|
||||
This ensures the user never suspects something went horribly wrong.
|
||||
|
||||
|
||||
### Package Contents
|
||||
|
||||
| Class | Description |
|
||||
|---------------|-------------|
|
||||
| Artifacts | Simple objects used by other classes.|
|
||||
| Controller | JavaFX class containing application logic. |
|
||||
| Execute | Issues shell commands.|
|
||||
| layout.fxml | Contains layout data.|
|
||||
| Main | Main JavaFX class. Run from here.|
|
||||
| QuicksortMd5 | Quicksort algorithm.|
|
||||
| README.md | You are here.|
|
||||
| Routines | Higher level routines called by Controller.|
|
||||
| Tools | Simple tools.|
|
||||
| Write | Writes to /tmp. Used for data storage.|
|
||||
|
||||
|
||||
### Issues and Features
|
||||
- Add proper directory selection.
|
||||
- Fix issues where nested duplicates would not be deleted on first pass.
|
||||
- Make UI prettier.
|
||||
- Make code prettier.
|
||||
|
@ -0,0 +1,92 @@
|
||||
package app;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import app.Artifacts.MFile;
|
||||
|
||||
public class Routines {
|
||||
|
||||
|
||||
Execute x = null;
|
||||
|
||||
Tools tools = new Tools();
|
||||
Write write = new Write();
|
||||
|
||||
|
||||
public Routines() {
|
||||
this.x = new Execute();
|
||||
|
||||
this.tools = new Tools();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [1] Write output of <b>find srcdir</b> to <b>/tmp/find</b> <br>
|
||||
* [2] Read <b>/tmp/find</b> into <b>List>String></b> <br>
|
||||
* [3] Add <b>List>String></b> entries to <b>Map>String,File></b> , where
|
||||
* <b>String</b> is an <b>int</b> key. <br>
|
||||
*
|
||||
* @param srcdir
|
||||
* @param type file OR directory
|
||||
* @return filepool
|
||||
*/
|
||||
public Map<Integer, File> loadPool(String srcdir, String type) {
|
||||
// [1]
|
||||
x.execute(new String[]{System.getProperty("user.dir") + "/src/app/toFile.sh", "find", srcdir, "/tmp/find"});
|
||||
// [2]
|
||||
List<String> lines = null;
|
||||
try {
|
||||
lines = Files.readAllLines(Paths.get("/tmp/find"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// [3]
|
||||
Map<Integer, File> filepool = new HashMap<Integer, File>();
|
||||
int j = 0;
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
File file = new File(lines.get(i));
|
||||
if (type == "directory" && file.isDirectory() || type == "file" && file.isFile()) {
|
||||
filepool.put(j, file);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
return filepool;
|
||||
}
|
||||
|
||||
|
||||
public Map<Integer, MFile> md5Pool(Map<Integer, File> pool) {
|
||||
Map<Integer, MFile> md5Pool = new HashMap<>();
|
||||
for (int i = 0; i < pool.size(); i++) {
|
||||
File file = pool.get(i);
|
||||
md5Pool.put(i, new Artifacts().getMFile(file));
|
||||
}
|
||||
return md5Pool;
|
||||
}
|
||||
|
||||
|
||||
public Map<Integer, MFile> doubles(Map<Integer, MFile> md5Pool) {
|
||||
Map<Integer, MFile> doubles = new HashMap<>();
|
||||
int d = 0;
|
||||
for (int i = 0; i < md5Pool.size() - 1; i++) {
|
||||
MFile iF = md5Pool.get(i);
|
||||
MFile jF = md5Pool.get(i + 1);
|
||||
if (iF.md5.equals(jF.md5)) {
|
||||
if (iF.file.lastModified() >= jF.file.lastModified()) {
|
||||
doubles.put(d, iF);
|
||||
d++;
|
||||
} else {
|
||||
doubles.put(d, jF);
|
||||
d++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return doubles;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package app;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Tools {
|
||||
|
||||
Execute x;
|
||||
|
||||
public Tools() {
|
||||
x = new Execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Md5 of File at @param path
|
||||
*/
|
||||
public String getMd5(String path) {
|
||||
// output of md5sum: "md5 filepath"
|
||||
BufferedReader md5reader = x.execute(new String[] { "md5sum", path }).output;
|
||||
String md5 = null;
|
||||
try {
|
||||
md5 = md5reader.readLine().split(" ")[0];
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return md5;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package app;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.util.Map;
|
||||
import app.Artifacts.MFile;
|
||||
|
||||
public class Write {
|
||||
|
||||
/**
|
||||
* writes <b>text</b> to file at <b>path</b>
|
||||
* <p>
|
||||
*/
|
||||
public void textFile(String path, StringBuilder text) {
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(new File(path)));
|
||||
bw.write(text.toString());
|
||||
bw.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* append all elements of <b>pool</b> to StringBuilder and write to
|
||||
* <b>/tmp/name</b>
|
||||
* <p>
|
||||
*
|
||||
* @param pool
|
||||
*/
|
||||
public void textPool(String name, Map<Integer, File> pool) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (int i = 0; i < pool.size(); i++) {
|
||||
text.append(i + " " + pool.get(i) + "\n");
|
||||
}
|
||||
textFile("/tmp/" + name, text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* append all elements of <b>md5Pool</b> to StringBuilder and write to
|
||||
* <b>/tmp/name</b>
|
||||
* <p>
|
||||
*
|
||||
* @param md5Pool
|
||||
*/
|
||||
public void textMd5Pool(String name, Map<Integer, MFile> md5Pool) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (int i = 0; i < md5Pool.size(); i++) {
|
||||
text.append(i + " " + md5Pool.get(i).md5 + " " + md5Pool.get(i).file + "\n");
|
||||
}
|
||||
textFile("/tmp/" + name, text);
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
|
||||
|
||||
<GridPane fx:controller="app.Controller"
|
||||
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
|
||||
<padding>
|
||||
<Insets top="25" right="25" bottom="10" left="25"/>
|
||||
</padding>
|
||||
|
||||
<Text
|
||||
text="Task"
|
||||
GridPane.rowIndex="0"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
|
||||
<Text
|
||||
text="State"
|
||||
GridPane.rowIndex="0"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
<Text
|
||||
text="Loading Directory."
|
||||
GridPane.rowIndex="1"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="loadDirState"
|
||||
text=""
|
||||
GridPane.rowIndex="1"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
<Text
|
||||
text="Calculating Md5."
|
||||
GridPane.rowIndex="2"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="calcMd5State"
|
||||
text=""
|
||||
GridPane.rowIndex="2"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
<Text
|
||||
text="Sorting Files."
|
||||
GridPane.rowIndex="3"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="sortFileState"
|
||||
text=""
|
||||
GridPane.rowIndex="3"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
<Text
|
||||
text="Finding Doubles."
|
||||
GridPane.rowIndex="4"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="findDoubleState"
|
||||
text=""
|
||||
GridPane.rowIndex="4"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
<Text
|
||||
text="Deleting Doubles."
|
||||
GridPane.rowIndex="5"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="delDoubleState"
|
||||
text=""
|
||||
GridPane.rowIndex="5"
|
||||
GridPane.columnIndex="1"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
<TextField fx:id="dir"
|
||||
text="Select Directory."
|
||||
GridPane.rowIndex="6"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
|
||||
<Button text="Load"
|
||||
GridPane.rowIndex="6"
|
||||
GridPane.columnIndex="1"
|
||||
onAction="#loadDir"/>
|
||||
|
||||
|
||||
<Text fx:id="fileNr"
|
||||
text="Number of Files:"
|
||||
GridPane.rowIndex="7"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
<Text fx:id="doubleNr"
|
||||
text="Number of Doubles:"
|
||||
GridPane.rowIndex="8"
|
||||
GridPane.columnIndex="0"
|
||||
/>
|
||||
|
||||
|
||||
<Button text="Delete"
|
||||
GridPane.rowIndex="8"
|
||||
GridPane.columnIndex="1"
|
||||
onAction="#deleteDoubles"/>
|
||||
|
||||
|
||||
</GridPane>
|
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
a=$1
|
||||
b=$2
|
||||
c=$3
|
||||
|
||||
$a $b > $c
|
Loading…
Reference in new issue