package Ymodem; // ¶¨Òå°üÃûΪYmodem
import java.io.*; // µ¼ÈëJavaÊäÈëÊä³ö°ü
import java.nio.file.Files; // µ¼ÈëNIOÎļþ²Ù×÷Àà
import java.nio.file.Path; // µ¼ÈëNIO·¾¶Àà
import java.nio.file.attribute.BasicFileAttributes; // µ¼ÈëÎļþÊôÐÔÀà
import java.util.Arrays; // µ¼ÈëÊý×鹤¾ßÀà
public class YModem { // ¶¨ÒåYModemÀà
private Modem modem; // ÉùÃ÷Modem¶ÔÏó
static int size; // ¾²Ì¬´óС±äÁ¿
/**
* ¹¹Ô캯Êý
* @param inputStream Á÷£¬ÓÃÓÚ´ÓÁíÒ»¶Ë¶ÁÈ¡½ÓÊÕµ½µÄÊý¾Ý
* @param outputStream ÓÃÓÚ½«Êý¾ÝдÈëÁíÒ»¶ËµÄÁ÷
*/
public YModem(InputStream inputStream, OutputStream outputStream) { // ¹¹Ô캯Êý
this.modem = new Modem(inputStream, outputStream); // ³õʼ»¯Modem¶ÔÏó
}
// ½ø¶È»Øµ÷½Ó¿Ú
public interface ProgressCallback { // ¶¨Òå½ø¶È»Øµ÷½Ó¿Ú
void onProgress(int currentBlock, int totalBlocks); // ½ø¶È»Øµ÷·½·¨
}
/**
* ·¢ËÍÎļþ¡£
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
* Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
* @param file Îļþ·¾¶
* @throws IOException
*/
public void send(Path file) throws IOException { // ·¢ËÍÎļþ·½·¨
send(file, null); // µ÷Óôø½ø¶È»Øµ÷µÄ·¢ËÍ·½·¨
}
/**
* ·¢ËÍÎļþ£¨´ø½ø¶È»Øµ÷£©¡£
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
* Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
* @param file Îļþ·¾¶
* @param progressCallback ½ø¶È»Øµ÷
* @throws IOException
*/
public void send(Path file, ProgressCallback progressCallback) throws IOException { // ´ø½ø¶È»Øµ÷µÄ·¢ËÍ·½·¨
try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) { // ´´½¨ÎļþÊäÈëÁ÷
Timer timer = new Timer(Modem.WAIT_FOR_RECEIVER_TIMEOUT).start(); // ´´½¨²¢Æô¶¯¼ÆÊ±Æ÷
boolean useCRC16 = modem.waitReceiverRequest(timer); // µÈ´ý½ÓÊÕ·½ÇëÇó²¢È·¶¨CRCÀàÐÍ
CRC crc; // ÉùÃ÷CRC¶ÔÏó
if (useCRC16) // Èç¹ûʹÓÃCRC16
crc = new CRC16(); // ´´½¨CRC16¶ÔÏó
else // ·ñÔò
crc = new CRC8(); // ´´½¨CRC8¶ÔÏó
// ¼ÆËã×Ü¿éÊý
long fileSize = Files.size(file); // »ñÈ¡Îļþ´óС
int totalDataBlocks = (int) Math.ceil(fileSize / 128.0); // ¼ÆËãÊý¾Ý¿é×ÜÊý
int totalBlocks = 2 + totalDataBlocks; // ÎļþÍ·¿é + Êý¾Ý¿é + ½áÊø¿é
//·¢ËÍ¿é0£¨ÎļþÍ·£©
BasicFileAttributes readAttributes = Files.readAttributes(file, BasicFileAttributes.class); // ¶ÁÈ¡ÎļþÊôÐÔ
String name=file.getFileName().toString(); // »ñÈ¡ÎļþÃû
String fileNameString = name + (char)0 + fileSize + " "+ Long.toOctalString(readAttributes.lastModifiedTime().toMillis() / 1000); // ¹¹½¨ÎļþÃû×Ö·û´®
byte[] fileNameBytes = Arrays.copyOf(fileNameString.getBytes(), 128); // ½«ÎļþÃûת»»Îª×Ö½ÚÊý×é
modem.sendBlock(0, Arrays.copyOf(fileNameBytes, 128), 128, crc); // ·¢ËÍÎļþÍ·¿é
// ¸üнø¶È£¨ÎļþÍ·¿é£©
if (progressCallback != null) { // Èç¹û½ø¶È»Øµ÷²»Îª¿Õ
progressCallback.onProgress(1, totalBlocks); // µ÷Óýø¶È»Øµ÷
}
modem.waitReceiverRequest(timer); // µÈ´ý½ÓÊÕ·½ÇëÇó
byte[] block = new byte[128]; // ´´½¨Êý¾Ý¿éÊý×é
// ·¢ËÍÊý¾Ý¿é
int blockNumber = 1; // ³õʼ»¯¿é±àºÅ
int bytesRead; // ÉùÃ÷¶ÁÈ¡×Ö½ÚÊý±äÁ¿
while ((bytesRead = dataStream.read(block)) != -1) { // ¶ÁÈ¡ÎļþÊý¾Ý
if (bytesRead < 128) { // Èç¹û¶ÁÈ¡×Ö½ÚÊýСÓÚ128
// Ìî³ä²»×㲿·Ö
Arrays.fill(block, bytesRead, 128, Modem.CPMEOF); // ÓÃCPMEOFÌî³äÊ£Óಿ·Ö
}
modem.sendBlock(blockNumber, block, 128, crc); // ·¢ËÍÊý¾Ý¿é
// ¸üнø¶È£¨Êý¾Ý¿é£©
if (progressCallback != null) { // Èç¹û½ø¶È»Øµ÷²»Îª¿Õ
progressCallback.onProgress(1 + blockNumber, totalBlocks); // µ÷Óýø¶È»Øµ÷
}
blockNumber++; // ¿é±àºÅµÝÔö
}
modem.sendEOT(); // ·¢ËÍ´«Êä½áÊøÐźÅ
modem.waitReceiverRequest(timer); // µÈ´ý½ÓÊÕ·½ÇëÇó
byte[] bytes = new byte[128]; // ´´½¨¿Õ×Ö½ÚÊý×é
modem.sendBlock(0, bytes, 128, crc); // ·¢ËͿտé±íʾ´«Êä½áÊø
// ¸üнø¶È£¨½áÊø¿é£©
if (progressCallback != null) { // Èç¹û½ø¶È»Øµ÷²»Îª¿Õ
progressCallback.onProgress(totalBlocks, totalBlocks); // µ÷Óýø¶È»Øµ÷
}
}
}
/**
* ÅúÁ¿·¢ËÍÎļþ¡£
*
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
* Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
*
* @param files
* @throws IOException
*/
public void batchSend(Path... files) throws IOException { // ÅúÁ¿·¢ËÍÎļþ·½·¨
for (Path file : files) { // ±éÀúÎļþÊý×é
send(file); // ·¢ËÍÿ¸öÎļþ
}
sendBatchStop(); // ·¢ËÍÅúÁ¿´«Êä½áÊøÐźÅ
}
private void sendBatchStop() throws IOException { // ·¢ËÍÅúÁ¿´«Êä½áÊø·½·¨
Timer timer = new Timer(Modem.WAIT_FOR_RECEIVER_TIMEOUT).start(); // ´´½¨²¢Æô¶¯¼ÆÊ±Æ÷
boolean useCRC16 = modem.waitReceiverRequest(timer); // µÈ´ý½ÓÊÕ·½ÇëÇó²¢È·¶¨CRCÀàÐÍ
CRC crc; // ÉùÃ÷CRC¶ÔÏó
if (useCRC16) // Èç¹ûʹÓÃCRC16
crc = new CRC16(); // ´´½¨CRC16¶ÔÏó
else // ·ñÔò
crc = new CRC8(); // ´´½¨CRC8¶ÔÏó
//·¢ËÍ¿é0
byte[] bytes = new byte[128]; // ´´½¨¿Õ×Ö½ÚÊý×é
modem.sendBlock(0, bytes, bytes.length, crc); // ·¢ËͿտé±íʾÅúÁ¿´«Êä½áÊø
}
/**
* ½ÓÊÕµ¥¸öÎļþ
*
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
* Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
*
* @param directory Îļþ½«±£´æµÄĿ¼
* @return ´´½¨µÄÎļþ·¾¶
* @throws IOException
*/
public Path receiveSingleFileInDirectory(Path directory) throws IOException { // ½ÓÊÕµ¥¸öÎļþ·½·¨
return receive(directory, true); // µ÷ÓýÓÊÕ·½·¨
}
/**
* ÅúÁ¿½ÓÊÕÎļþ
*
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
* Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
*
* @param directory Îļþ½«±£´æµÄĿ¼
* @throws IOException
*/
public void receiveFilesInDirectory(Path directory) throws IOException { // ÅúÁ¿½ÓÊÕÎļþ·½·¨
while (receive(directory, true) != null) { // Ñ»·½ÓÊÕÎļþÖ±µ½·µ»Ønull
}
}
/**
* ½ÓÊÕ·¾¶
*
* ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£ * Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£ * * @param path Êý¾Ý½«±£´æµÄÎļþ·¾¶ * @return Îļþ·¾¶ * @throws IOException */ public Path receive(Path path) throws IOException { // ½ÓÊÕÎļþ·½·¨ return receive(path, false); // µ÷ÓýÓÊÕ·½·¨ } private Path receive(Path path, boolean inDirectory) throws IOException { // ˽ÓнÓÊÕ·½·¨ DataOutputStream dataOutput = null; // ÉùÃ÷Êý¾ÝÊä³öÁ÷ Path filePath; // ÉùÃ÷Îļþ·¾¶ try { CRC crc = new CRC16(); // ´´½¨CRC16¶ÔÏó int errorCount = 0; // ³õʼ»¯´íÎó¼ÆÊý // ´¦Àí¿é0 byte[] block; // ÉùÃ÷Êý¾Ý¿éÊý×é int character; // ÉùÃ÷×Ö·û±äÁ¿ while (true) { // Ñ»·´¦Àí¿é0 character = modem.requestTransmissionStart(true); // ÇëÇó´«Ê俪ʼ try { // ´ÓÁã¿é¶ÁÈ¡ÎļþÃû block = modem.readBlock(0, (character == Modem.SOH), crc); // ¶ÁÈ¡¿é0 if (inDirectory) { // Èç¹ûÔÚĿ¼ÖÐ StringBuilder sb = new StringBuilder(); // ´´½¨×Ö·û´®¹¹½¨Æ÷ if (block[0] == 0) { // Èç¹û¿éÊ××Ö½ÚΪ0 //ÕâÊÇÅúÁ¿Îļþ´«ÊäµÄÍ£Ö¹¿é modem.sendByte(Modem.ACK); // ·¢ËÍACK return null; // ·µ»Ønull } for (int i = 0; i < block.length; i++) { // ±éÀú¿éÊý¾Ý if (block[i] == 0) { // Èç¹ûÓöµ½0×Ö½Ú break; // Ìø³öÑ»· } sb.append((char) block[i]); // ×·¼Ó×Ö·ûµ½×Ö·û´®¹¹½¨Æ÷ } filePath = path.resolve(sb.toString()); // ½âÎöÎļþ·¾¶ } else { // ·ñÔò filePath = path; // Ö±½ÓʹÓ÷¾¶ } dataOutput = new DataOutputStream(Files.newOutputStream(filePath)); // ´´½¨ÎļþÊä³öÁ÷ modem.sendByte(Modem.ACK); // ·¢ËÍACK break; // Ìø³öÑ»· } catch (TimeoutException | Modem.InvalidBlockException e) { // ²¶»ñ³¬Ê±ºÍÎÞЧ¿éÒì³£ errorCount++; // ´íÎó¼ÆÊýÔö¼Ó if (errorCount == Modem.MAXERRORS) { // Èç¹û´íÎó¼ÆÊý´ïµ½×î´óÖµ modem.interruptTransmission(); // Öжϴ«Êä throw new IOException("Transmission aborted, error count exceeded max"); // Å׳öIOÒì³£ } modem.sendByte(Modem.NAK); // ·¢ËÍNAK } catch (Modem.RepeatedBlockException | Modem.SynchronizationLostException e) { // ²¶»ñÖØ¸´¿éºÍͬ²½¶ªÊ§Òì³£ //ÖÂÃü´«Êä´íÎó modem.interruptTransmission(); // Öжϴ«Êä throw new IOException("Fatal transmission error", e); // Å׳öIOÒì³£ } } //½ÓÊÕÊý¾Ý¿é modem.receive(filePath, true); // ½ÓÊÕÊý¾Ý¿é } finally { if (dataOutput != null) { // Èç¹ûÊý¾ÝÊä³öÁ÷²»Îª¿Õ dataOutput.close(); // ¹Ø±ÕÊý¾ÝÊä³öÁ÷ } } return filePath; // ·µ»ØÎļþ·¾¶ } public static int getSize() { // »ñÈ¡´óС·½·¨ return size; // ·µ»Ø´óС } }