Skip to content

Commit 9afd7b8

Browse files
committed
Initial commit
1 parent 130dfce commit 9afd7b8

File tree

7 files changed

+348
-0
lines changed

7 files changed

+348
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea/
2+
out/
3+
*.iml
4+

src/com/tomclaw/sfn/FileHelper.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.tomclaw.sfn;
2+
3+
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.security.MessageDigest;
8+
import java.security.NoSuchAlgorithmException;
9+
10+
public class FileHelper {
11+
12+
public static String calculateMD5(File file) throws NoSuchAlgorithmException, IOException {
13+
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
14+
try (InputStream fileStream = new FileInputStream(file)) {
15+
byte[] buf = new byte[10240];
16+
int len;
17+
while ((len = fileStream.read(buf)) > 0) {
18+
messageDigest.update(buf, 0, len);
19+
}
20+
}
21+
byte[] digest = messageDigest.digest();
22+
23+
StringBuilder result = new StringBuilder();
24+
for (byte b : digest) {
25+
result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
26+
}
27+
return result.toString();
28+
}
29+
30+
}

src/com/tomclaw/sfn/Main.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.tomclaw.sfn;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.security.NoSuchAlgorithmException;
6+
import java.util.Objects;
7+
8+
import static com.tomclaw.sfn.Sfn.DEFAULT_PORT_NUMBER;
9+
10+
public class Main {
11+
12+
public static void main(String[] args) {
13+
new Thread(Main::sendFiles).start();
14+
receiveFiles();
15+
}
16+
17+
private static void receiveFiles() {
18+
try {
19+
File receiveDir = new File("/Users/solkin/Downloads/sfn-files");
20+
Sfn receive = new Sfn(true);
21+
receive.listen(DEFAULT_PORT_NUMBER);
22+
receive.receiveFiles(receiveDir);
23+
} catch (IOException | SfnProtocolException | NoSuchAlgorithmException e) {
24+
e.printStackTrace();
25+
}
26+
}
27+
28+
private static void sendFiles() {
29+
try {
30+
Thread.sleep(500);
31+
} catch (InterruptedException e) {
32+
e.printStackTrace();
33+
}
34+
try {
35+
Sfn send = new Sfn(true);
36+
send.connect("127.0.0.1", DEFAULT_PORT_NUMBER);
37+
File sendDir = new File("/Users/solkin/Pictures/Test");
38+
send.sendFiles(Objects.requireNonNull(sendDir.listFiles()));
39+
} catch (IOException | NoSuchAlgorithmException e) {
40+
e.printStackTrace();
41+
}
42+
}
43+
}

src/com/tomclaw/sfn/ProgressBar.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.tomclaw.sfn;
2+
3+
public class ProgressBar {
4+
5+
private String fileName;
6+
private long fileSize;
7+
private long totalRead;
8+
private int percent;
9+
private static final char[] trailer = new char[42];
10+
private static final char[] progress = new char[22];
11+
12+
public void setupBar(String fileName, long fileSize) {
13+
this.fileName = fileName;
14+
this.fileSize = fileSize;
15+
}
16+
17+
public void showBar(long totalRead) {
18+
int percent = (int) ((totalRead * 100) / fileSize);
19+
String metrics = "Byte";
20+
if (totalRead < 1024) {
21+
metrics = "Byte";
22+
}
23+
if (totalRead >= 1024) {
24+
totalRead /= 1024;
25+
metrics = "KiB ";
26+
}
27+
if (totalRead >= 1024) {
28+
totalRead /= 1024;
29+
metrics = "MiB ";
30+
}
31+
if (totalRead >= 1024) {
32+
totalRead /= 1024;
33+
metrics = "GiB ";
34+
}
35+
if (totalRead >= 1024) {
36+
totalRead /= 1024;
37+
metrics = "TiB ";
38+
}
39+
if (this.totalRead != totalRead || this.percent != percent) {
40+
this.totalRead = totalRead;
41+
this.percent = percent;
42+
for (int c = 0; c < progress.length; c += 1) {
43+
if (c * 100 / progress.length <= percent) {
44+
progress[c] = '#';
45+
} else {
46+
progress[c] = '-';
47+
}
48+
}
49+
for (int c = 0; c < trailer.length; c += 1) {
50+
if (c < fileName.length()) {
51+
trailer[c] = fileName.charAt(c);
52+
} else {
53+
trailer[c] = ' ';
54+
}
55+
}
56+
System.out.printf(
57+
" %s %4d %s [%s] %3d %%\r",
58+
String.valueOf(trailer),
59+
(int) totalRead,
60+
metrics,
61+
String.valueOf(progress),
62+
percent
63+
);
64+
}
65+
}
66+
67+
public void done() {
68+
showBar(fileSize);
69+
System.out.println();
70+
}
71+
72+
}

src/com/tomclaw/sfn/Sfn.java

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.tomclaw.sfn;
2+
3+
import java.io.*;
4+
import java.net.ServerSocket;
5+
import java.net.Socket;
6+
import java.nio.ByteBuffer;
7+
import java.nio.ByteOrder;
8+
import java.security.DigestInputStream;
9+
import java.security.MessageDigest;
10+
import java.security.NoSuchAlgorithmException;
11+
12+
import static com.tomclaw.sfn.StreamHelper.readLine;
13+
14+
public class Sfn {
15+
16+
public static final int DEFAULT_PORT_NUMBER = 3214;
17+
18+
private static final byte FILE = 0x01;
19+
private static final byte DONE = 0x02;
20+
private static final byte MD5_WITH_FILE = 0x03;
21+
private static final byte FILE_WITH_MD5 = 0x04;
22+
23+
private boolean withIntegrityCheck;
24+
private Socket socket;
25+
private ProgressBar progressBar;
26+
27+
public Sfn(boolean withIntegrityCheck) {
28+
this.withIntegrityCheck = withIntegrityCheck;
29+
this.progressBar = new ProgressBar();
30+
}
31+
32+
public void connect(String ip, int port) throws IOException {
33+
socket = new Socket(ip, port);
34+
}
35+
36+
public void listen(int port) throws IOException {
37+
socket = new ServerSocket(port).accept();
38+
}
39+
40+
public void sendFiles(File... files) throws IOException, NoSuchAlgorithmException {
41+
ByteBuffer fileLength = ByteBuffer.allocate(8);
42+
fileLength.order(ByteOrder.LITTLE_ENDIAN);
43+
try (DataOutputStream output = new DataOutputStream(
44+
new BufferedOutputStream(
45+
socket.getOutputStream()
46+
)
47+
)) {
48+
for (File file : files) {
49+
progressBar.setupBar(file.getName(), file.length());
50+
output.writeByte(withIntegrityCheck ? FILE_WITH_MD5 : FILE);
51+
output.writeBytes(file.getName() + '\n');
52+
fileLength.putLong(file.length());
53+
output.write(fileLength.array());
54+
fileLength.clear();
55+
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
56+
try (InputStream input = new DigestInputStream(new FileInputStream(file), messageDigest)) {
57+
byte[] buf = new byte[10240];
58+
int len;
59+
long sent = 0;
60+
while ((len = input.read(buf)) > 0) {
61+
output.write(buf, 0, len);
62+
sent += len;
63+
progressBar.showBar(sent);
64+
}
65+
}
66+
progressBar.done();
67+
if (withIntegrityCheck) {
68+
byte[] digest = messageDigest.digest();
69+
output.writeBytes(arrayToHex(digest) + '\n');
70+
}
71+
}
72+
output.writeByte(DONE);
73+
output.flush();
74+
}
75+
}
76+
77+
public void receiveFiles(File dir) throws IOException, SfnProtocolException, NoSuchAlgorithmException {
78+
ByteBuffer fileLength = ByteBuffer.allocate(8);
79+
fileLength.order(ByteOrder.LITTLE_ENDIAN);
80+
try (DataInputStream input = new DataInputStream(new BufferedInputStream(socket.getInputStream()))) {
81+
boolean isActive = true;
82+
do {
83+
int type = input.readByte();
84+
switch (type) {
85+
case FILE:
86+
case MD5_WITH_FILE:
87+
case FILE_WITH_MD5:
88+
String fileName = readLine(input);
89+
if (fileName == null || fileName.length() == 0) {
90+
throw new SfnProtocolException();
91+
}
92+
input.readFully(fileLength.array(), 0, fileLength.capacity());
93+
long fileSize = fileLength.getLong();
94+
fileLength.clear();
95+
String md5 = null;
96+
if (type == MD5_WITH_FILE) {
97+
md5 = readLine(input);
98+
}
99+
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
100+
File file = new File(dir, fileName);
101+
try (OutputStream output = new FileOutputStream(file)) {
102+
byte[] buf = new byte[10240];
103+
int len;
104+
long totalRead = 0;
105+
do {
106+
int read = (int) Math.min(fileSize - totalRead, buf.length);
107+
len = input.read(buf, 0, read);
108+
if (len == -1) {
109+
throw new EOFException();
110+
}
111+
messageDigest.update(buf, 0, len);
112+
output.write(buf, 0, len);
113+
} while ((totalRead += len) < fileSize);
114+
}
115+
if (type == FILE_WITH_MD5) {
116+
md5 = readLine(input);
117+
}
118+
if (md5 != null) {
119+
byte[] digest = messageDigest.digest();
120+
String md5hex = arrayToHex(digest);
121+
boolean checkPassed = md5.equals(md5hex);
122+
if (checkPassed) {
123+
System.out.printf(" => MD5 [%s] check passed\n", md5hex);
124+
} else {
125+
System.out.printf(" => MD5 check failed!\n remote: %s\n local: %s\n", md5, md5hex);
126+
}
127+
}
128+
break;
129+
case DONE:
130+
isActive = false;
131+
break;
132+
}
133+
} while (isActive);
134+
}
135+
}
136+
137+
private static String arrayToHex(byte[] data) {
138+
StringBuilder result = new StringBuilder();
139+
for (byte b : data) {
140+
result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
141+
}
142+
return result.toString();
143+
}
144+
145+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.tomclaw.sfn;
2+
3+
public class SfnProtocolException extends Throwable {
4+
}

src/com/tomclaw/sfn/StreamHelper.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.tomclaw.sfn;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.PushbackInputStream;
6+
7+
public class StreamHelper {
8+
9+
public static String readLine(InputStream in) throws IOException {
10+
char[] lineBuffer = new char[128];
11+
char[] buf = lineBuffer;
12+
13+
int room = buf.length;
14+
int offset = 0;
15+
int c;
16+
17+
loop:
18+
while (true) {
19+
switch (c = in.read()) {
20+
case -1:
21+
case '\n':
22+
break loop;
23+
24+
case '\r':
25+
int c2 = in.read();
26+
if ((c2 != '\n') && (c2 != -1)) {
27+
if (!(in instanceof PushbackInputStream)) {
28+
in = new PushbackInputStream(in);
29+
}
30+
((PushbackInputStream) in).unread(c2);
31+
}
32+
break loop;
33+
34+
default:
35+
if (--room < 0) {
36+
buf = new char[offset + 128];
37+
room = buf.length - offset - 1;
38+
System.arraycopy(lineBuffer, 0, buf, 0, offset);
39+
lineBuffer = buf;
40+
}
41+
buf[offset++] = (char) c;
42+
break;
43+
}
44+
}
45+
if ((c == -1) && (offset == 0)) {
46+
return null;
47+
}
48+
return String.copyValueOf(buf, 0, offset);
49+
}
50+
}

0 commit comments

Comments
 (0)