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

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