826220679@qq.com
2025-10-31 9aca70f16836952e2e3462ecc69dabe679811eb7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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); // µ÷Óýø¶È»Øµ÷
            }
                    
        }
    }
 
    /**
     * ÅúÁ¿·¢ËÍÎļþ¡£<br/>
     * <p>
     * ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
     * Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
     *
     * @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); // ·¢ËͿտé±íʾÅúÁ¿´«Êä½áÊø
    }
 
    /**
     * ½ÓÊÕµ¥¸öÎļþ <br/>
     * <p>
     * ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
     * Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
     *
     * @param directory Îļþ½«±£´æµÄĿ¼
     * @return ´´½¨µÄÎļþ·¾¶
     * @throws IOException
     */
    public Path receiveSingleFileInDirectory(Path directory) throws IOException { // ½ÓÊÕµ¥¸öÎļþ·½·¨
        return receive(directory, true); // µ÷ÓýÓÊÕ·½·¨
    }
 
    /**
     * ÅúÁ¿½ÓÊÕÎļþ <br/>
     * <p>
     * ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
     * Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
     *
     * @param directory Îļþ½«±£´æµÄĿ¼
     * @throws IOException
     */
    public void receiveFilesInDirectory(Path directory) throws IOException { // ÅúÁ¿½ÓÊÕÎļþ·½·¨
        while (receive(directory, true) != null) { // Ñ­»·½ÓÊÕÎļþÖ±µ½·µ»Ønull
        }
    }
 
    /**
     * ½ÓÊÕ·¾¶ <br/>
     * <p>
     * ´Ë·½·¨Ö§³ÖÕýÈ·µÄÏß³ÌÖжϣ¬µ±Ïß³ÌÖжÏʱ½«·¢ËÍ"È¡Ïû´«Êä"¡£
     * Òò´Ë£¬Äú¿ÉÒÔ½«³¤´«ÊäÒÆ¶¯µ½ÆäËûÏ̲߳¢¸ù¾ÝÄúµÄËã·¨ÖжÏËü¡£
     *
     * @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; // ·µ»Ø´óС
    }
}