You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DCCTransfer.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.addons.dcc.io;
  18. import com.dmdirc.addons.dcc.DCCTransferHandler;
  19. import com.dmdirc.util.collections.ListenerList;
  20. import com.dmdirc.util.io.StreamUtils;
  21. import java.io.DataInputStream;
  22. import java.io.DataOutputStream;
  23. import java.io.File;
  24. import java.io.FileInputStream;
  25. import java.io.FileNotFoundException;
  26. import java.io.FileOutputStream;
  27. import java.io.IOException;
  28. import java.util.ArrayList;
  29. import java.util.List;
  30. /**
  31. * This class handles a DCC transfer.
  32. */
  33. public class DCCTransfer extends DCC {
  34. /** List of active sends. */
  35. private static final List<DCCTransfer> TRANSFERS = new ArrayList<>();
  36. /** File Transfer Types. */
  37. public enum TransferType {
  38. SEND, RECEIVE
  39. }
  40. /** The File transfer type for this file. */
  41. private TransferType transferType = TransferType.RECEIVE;
  42. /** The handlers for this DCCSend. */
  43. private final ListenerList handlers = new ListenerList();
  44. /** Used to send data out the socket. */
  45. private DataOutputStream out;
  46. /** Used to read data from the socket. */
  47. private DataInputStream in;
  48. /** File we are using. */
  49. private File transferFile;
  50. /** Used to write data to the file. */
  51. private DataOutputStream fileOut;
  52. /** Used to read data from the file. */
  53. private DataInputStream fileIn;
  54. /** Where are we starting from? */
  55. private int startpos;
  56. /** How big is this file? */
  57. private long size = -1;
  58. /** How much of this file have we read so far? */
  59. private long readSize;
  60. /** What is the name of the file? */
  61. private String filename = "";
  62. /** What is the token for this send? */
  63. private String token = "";
  64. /** Block Size. */
  65. private final int blockSize;
  66. /** Is this a turbo dcc? */
  67. private boolean turbo = false;
  68. private boolean active = false;
  69. /** Creates a new instance of DCCTransfer with a default block size. */
  70. public DCCTransfer() {
  71. this(1024);
  72. }
  73. /**
  74. * Creates a new instance of DCCTransfer.
  75. *
  76. * @param blockSize Block size to use
  77. */
  78. public DCCTransfer(final int blockSize) {
  79. this.blockSize = blockSize;
  80. synchronized (TRANSFERS) {
  81. TRANSFERS.add(this);
  82. }
  83. }
  84. /**
  85. * Reset this send to be used again (eg a resend).
  86. */
  87. public void reset() {
  88. close();
  89. setFileName(filename);
  90. setFileStart(startpos);
  91. synchronized (TRANSFERS) {
  92. TRANSFERS.add(this);
  93. }
  94. }
  95. /**
  96. * Get a copy of the list of active sends.
  97. *
  98. * @return A copy of the list of active sends.
  99. */
  100. public static List<DCCTransfer> getTransfers() {
  101. synchronized (TRANSFERS) {
  102. return new ArrayList<>(TRANSFERS);
  103. }
  104. }
  105. /**
  106. * Called to remove this object from the sends list.
  107. */
  108. public void removeFromTransfers() {
  109. synchronized (TRANSFERS) {
  110. TRANSFERS.remove(this);
  111. }
  112. }
  113. /**
  114. * Set the filename of this file
  115. *
  116. * @param filename Filename
  117. */
  118. public void setFileName(final String filename) {
  119. this.filename = filename;
  120. if (transferType == TransferType.SEND) {
  121. transferFile = new File(filename);
  122. try {
  123. fileIn = new DataInputStream(new FileInputStream(transferFile.getAbsolutePath()));
  124. } catch (FileNotFoundException | SecurityException e) {
  125. fileIn = null;
  126. }
  127. }
  128. }
  129. /**
  130. * Get the filename of this file
  131. *
  132. * @return Filename
  133. */
  134. public String getFileName() {
  135. return filename;
  136. }
  137. /**
  138. * Get the filename of this file, without the path
  139. *
  140. * @return Filename without path
  141. */
  142. public String getShortFileName() {
  143. return new File(filename).getName();
  144. }
  145. /**
  146. * Set dcc Type.
  147. *
  148. * @param type Type of DCC transfer this is.
  149. */
  150. public void setType(final TransferType type) {
  151. this.transferType = type;
  152. }
  153. /**
  154. * Get dcc Type.
  155. *
  156. * @return Type of DCC transfer this is.
  157. */
  158. public TransferType getType() {
  159. return transferType;
  160. }
  161. /**
  162. * Set turbo mode on/off. Turbo mode doesn't wait for ack packets. Only relevant when sending.
  163. *
  164. * @param turbo True for turbo dcc, else false
  165. */
  166. public void setTurbo(final boolean turbo) {
  167. this.turbo = turbo;
  168. }
  169. /**
  170. * Is turbo mode on/off. Turbo mode doesn't wait for ack packets. Only relevant when sending.
  171. *
  172. * @return True for turbo dcc, else false
  173. */
  174. public boolean isTurbo() {
  175. return turbo;
  176. }
  177. /**
  178. * Set the Token for this send
  179. *
  180. * @param token Token for this send
  181. */
  182. public void setToken(final String token) {
  183. this.token = token;
  184. }
  185. /**
  186. * Get the Token for this send
  187. *
  188. * @return Token for this send
  189. */
  190. public String getToken() {
  191. return token;
  192. }
  193. /**
  194. * Make a Token for this send. This token will be unique compared to all the other known sends
  195. *
  196. * @return The Token for this send.
  197. */
  198. public String makeToken() {
  199. String myToken = "";
  200. do {
  201. myToken = Integer.toString(Math.abs((myToken + filename).hashCode()));
  202. } while (findByToken(myToken) != null);
  203. setToken(myToken);
  204. return myToken;
  205. }
  206. /**
  207. * Find a send based on a given token.
  208. *
  209. * @param token Token to look for. (case sensitive)
  210. *
  211. * @return The first DCCTransfer that matches the given token. null if none match, or token is
  212. * "" or null.
  213. */
  214. public static DCCTransfer findByToken(final String token) {
  215. if (token == null || token.isEmpty()) {
  216. return null;
  217. }
  218. for (DCCTransfer transfer : getTransfers()) {
  219. if (transfer.getToken().equals(token)) {
  220. return transfer;
  221. }
  222. }
  223. return null;
  224. }
  225. /**
  226. * Set the size of the file
  227. *
  228. * @param size File size
  229. */
  230. public void setFileSize(final long size) {
  231. this.size = size;
  232. }
  233. /**
  234. * Get the expected size of the file
  235. *
  236. * @return The expected File size (-1 if unknown)
  237. */
  238. public long getFileSize() {
  239. return size;
  240. }
  241. /**
  242. * Set the starting position of the file
  243. *
  244. * @param startpos Starting position
  245. *
  246. * @return -1 if fileIn is null or if dcc receive, else the result of fileIn.skipBytes()
  247. */
  248. public int setFileStart(final int startpos) {
  249. this.startpos = startpos;
  250. this.readSize = startpos;
  251. if (transferType == TransferType.SEND && fileIn != null) {
  252. try {
  253. this.startpos = fileIn.skipBytes(startpos);
  254. this.readSize = this.startpos;
  255. return this.startpos;
  256. } catch (IOException ioe) {
  257. }
  258. }
  259. return -1;
  260. }
  261. /**
  262. * Get the starting position of the file
  263. *
  264. * @return starting position of file.
  265. */
  266. public int getFileStart() {
  267. return startpos;
  268. }
  269. /**
  270. * Change the handler for this DCC Send
  271. *
  272. * @param handler A class implementing DCCTransferHandler
  273. */
  274. public void addHandler(final DCCTransferHandler handler) {
  275. handlers.add(DCCTransferHandler.class, handler);
  276. }
  277. @Override
  278. protected void socketOpened() {
  279. try {
  280. active = true;
  281. transferFile = new File(filename);
  282. if (transferType == TransferType.RECEIVE) {
  283. fileOut = new DataOutputStream(new FileOutputStream(
  284. transferFile.getAbsolutePath(), startpos > 0));
  285. }
  286. out = new DataOutputStream(socket.getOutputStream());
  287. in = new DataInputStream(socket.getInputStream());
  288. for (DCCTransferHandler handler : handlers.get(DCCTransferHandler.class)) {
  289. handler.socketOpened(this);
  290. }
  291. } catch (IOException ioe) {
  292. socketClosed();
  293. }
  294. }
  295. @Override
  296. protected void socketClosed() {
  297. // Try to close both, even if one fails.
  298. StreamUtils.close(out);
  299. StreamUtils.close(in);
  300. out = null;
  301. in = null;
  302. for (DCCTransferHandler handler : handlers.get(DCCTransferHandler.class)) {
  303. handler.socketClosed(this);
  304. }
  305. // Try to delete empty files.
  306. if (transferType == TransferType.RECEIVE && transferFile != null
  307. && transferFile.length() == 0) {
  308. transferFile.delete();
  309. }
  310. synchronized (TRANSFERS) {
  311. TRANSFERS.remove(this);
  312. }
  313. active = false;
  314. }
  315. @Override
  316. protected boolean handleSocket() {
  317. if (out == null || in == null) {
  318. return false;
  319. }
  320. if (transferType == TransferType.RECEIVE) {
  321. return handleReceive();
  322. } else {
  323. return handleSend();
  324. }
  325. }
  326. /**
  327. * Handle the socket as a RECEIVE.
  328. *
  329. * @return false when socket is closed (or should be closed), true will cause the method to be
  330. * called again.
  331. */
  332. protected boolean handleReceive() {
  333. try {
  334. final byte[] data = new byte[blockSize];
  335. final int bytesRead = in.read(data);
  336. readSize += bytesRead;
  337. if (bytesRead > 0) {
  338. for (DCCTransferHandler handler : handlers.get(DCCTransferHandler.class)) {
  339. handler.dataTransferred(this, bytesRead);
  340. }
  341. fileOut.write(data, 0, bytesRead);
  342. if (!turbo) {
  343. // Send ack
  344. out.writeInt((int) readSize);
  345. out.flush();
  346. }
  347. if (readSize == size) {
  348. fileOut.close();
  349. if (turbo) {
  350. in.close();
  351. }
  352. return false;
  353. } else {
  354. return true;
  355. }
  356. } else if (bytesRead < 0) {
  357. fileOut.close();
  358. return false;
  359. }
  360. } catch (IOException e) {
  361. return false;
  362. }
  363. return false;
  364. }
  365. /**
  366. * Handle the socket as a SEND.
  367. *
  368. * @return false when socket is closed (or should be closed), true will cause the method to be
  369. * called again.
  370. */
  371. protected boolean handleSend() {
  372. try {
  373. final byte[] data = new byte[blockSize];
  374. final int bytesRead = fileIn.read(data);
  375. readSize += bytesRead;
  376. if (bytesRead > 0) {
  377. for (DCCTransferHandler handler : handlers.get(DCCTransferHandler.class)) {
  378. handler.dataTransferred(this, bytesRead);
  379. }
  380. out.write(data, 0, bytesRead);
  381. out.flush();
  382. // Wait for acknowledgement packet.
  383. if (!turbo) {
  384. int bytesReceived;
  385. do {
  386. bytesReceived = in.readInt();
  387. } while (readSize - bytesReceived > 0);
  388. }
  389. if (readSize == size) {
  390. fileIn.close();
  391. // Process all the ack packets that may have been sent.
  392. // In true turbo dcc mode, none will have been sent and the socket
  393. // will just close, in fast-dcc mode all the acks will be here,
  394. // So keep reading acks until the socket closes (IOException) or we
  395. // have received all the acks.
  396. if (turbo) {
  397. int ack;
  398. do {
  399. try {
  400. ack = in.readInt();
  401. } catch (IOException e) {
  402. break;
  403. }
  404. } while (ack > 0 && readSize - ack > 0);
  405. }
  406. return false;
  407. }
  408. return true;
  409. } else if (bytesRead < 0) {
  410. fileIn.close();
  411. return true;
  412. }
  413. } catch (IOException e) {
  414. return false;
  415. }
  416. return false;
  417. }
  418. /**
  419. * Is this DCC transfer active.
  420. *
  421. * @return true iif active
  422. */
  423. public boolean isActive() {
  424. return active;
  425. }
  426. }