1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.transfer;
21
22 import javax.swing.Action;
23
24 import java.awt.event.ActionEvent;
25 import java.io.BufferedInputStream;
26 import java.io.BufferedOutputStream;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.net.Socket;
33 import java.util.Hashtable;
34
35 import org.apache.log4j.Logger;
36 import org.xnap.XNap;
37 import org.xnap.gui.util.IconHelper;
38 import org.xnap.io.ThrottledInputStream;
39 import org.xnap.net.NetHelper;
40 import org.xnap.peer.Peer;
41 import org.xnap.plugin.Plugin;
42 import org.xnap.transfer.action.AbstractStopAction;
43 import org.xnap.transfer.action.AbstractTransferAction;
44 import org.xnap.util.FileHelper;
45 import org.xnap.util.FiniteStateMachine;
46 import org.xnap.util.IllegalOperationException;
47 import org.xnap.util.State;
48
49 /***
50 * Downloads a file via the dcc protocol.
51 */
52 public class DccDownload extends AbstractTransfer implements Download {
53
54
55
56 private static final Hashtable TRANSITION_TABLE;
57 static {
58 State[][] table = new State[][] {
59 { State.NOT_STARTED,
60 State.CONNECTING, State.STOPPED },
61 { State.CONNECTING,
62 State.DOWNLOADING, State.STOPPING, State.STOPPED, },
63 { State.DOWNLOADING,
64 State.SUCCEEDED, State.STOPPING, State.STOPPED, },
65 { State.STOPPING,
66 State.SUCCEEDED, State.STOPPED }
67 };
68
69 TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
70 }
71
72
73
74 private static Logger logger = Logger.getLogger(DccDownload.class);
75
76 private File file;
77 private String filename;
78 private long filesize;
79 private String host;
80 private Peer peer;
81 private int port;
82 private long totalBytesTransferred = 0;
83 private StateMachine sm = new StateMachine();
84
85 private AbstractTransferAction acceptAction = new AcceptAction();
86 private AbstractTransferAction rejectAction = new RejectAction();
87 private AbstractTransferAction stopAction = new StopAction();
88
89
90
91 /***
92 *
93 * @param peer the peer thae file is downloaded from
94 * @param file the destination file, the downloaded data is stored here
95 * @param host the host to connect to
96 * @param port the port to connect to
97 */
98 public DccDownload(Peer peer, String filename, long filesize,
99 String host, int port)
100 {
101 this.peer = peer;
102 this.filename = filename;
103 this.filesize = filesize;
104 this.host = host;
105 this.port = port;
106
107 stopAction.setEnabledLater(false);
108 }
109
110
111
112 public long getBytesTransferred()
113 {
114 return totalBytesTransferred;
115 }
116
117 public Action[] getActions()
118 {
119 return new Action[] { acceptAction, rejectAction, stopAction, };
120 }
121
122 public File getFile()
123 {
124 return file;
125 }
126
127 public String getFilename()
128 {
129 return filename;
130 }
131
132 public long getFilesize()
133 {
134 return filesize;
135 }
136
137 public Peer getPeer()
138 {
139 return peer;
140 }
141
142 public Plugin getPlugin()
143 {
144 return null;
145 }
146
147 public String getStatus()
148 {
149 return sm.getDescription();
150 }
151
152 public long getTotalBytesTransferred()
153 {
154 return totalBytesTransferred;
155 }
156
157 public boolean isDone()
158 {
159 State s = sm.getState();
160 return s == State.SUCCEEDED || s == State.STOPPED;
161 }
162
163 public boolean isRunning()
164 {
165 State s = sm.getState();
166 return s == State.NOT_STARTED || s == State.CONNECTING
167 || s == State.DOWNLOADING;
168 }
169
170 public void reject()
171 {
172 try {
173 setState(State.STOPPED, XNap.tr("Download rejected"));
174 }
175 catch(IllegalOperationException e) {
176 logger.debug("unexpected exception", e);
177 }
178 }
179
180 public void start()
181 {
182 try {
183 setState(State.CONNECTING);
184 }
185 catch(IllegalOperationException e) {
186 logger.debug("unexpected exception", e);
187 }
188 }
189
190 private void setState(State newState, String description)
191 {
192 sm.setState(newState, description);
193 stateChanged();
194 }
195
196 private void setState(State newState)
197 {
198 sm.setState(newState);
199 stateChanged();
200 }
201
202 public void stop()
203 {
204 try {
205 setState(State.STOPPING);
206 }
207 catch(IllegalOperationException e) {
208 logger.debug("unexpected exception", e);
209 }
210 }
211
212
213
214 /***
215 * Starts the download offer.
216 */
217 public class AcceptAction extends AbstractTransferAction {
218
219
220
221 public AcceptAction()
222 {
223 putValue(Action.NAME, XNap.tr("Accept"));
224 putValue(Action.SHORT_DESCRIPTION,
225 XNap.tr("Download selected file(s)."));
226 putValue(IconHelper.XNAP_ICON, "down.png");
227 }
228
229
230
231 public boolean equals(Object o)
232 {
233 return o instanceof AcceptAction;
234 }
235
236 public void actionPerformed(ActionEvent event)
237 {
238 DccDownload.this.start();
239 }
240
241 }
242
243
244 /***
245 * Rejects the download offer.
246 */
247 public class RejectAction extends AbstractTransferAction {
248
249
250
251 public RejectAction()
252 {
253 putValue(Action.NAME, XNap.tr("Reject"));
254 putValue(Action.SHORT_DESCRIPTION,
255 XNap.tr("Rejects selected downloads."));
256 putValue(IconHelper.XNAP_ICON, "stop_hand.png");
257 }
258
259
260
261 public boolean equals(Object o)
262 {
263 return o instanceof RejectAction;
264 }
265
266 public void actionPerformed(ActionEvent event)
267 {
268 DccDownload.this.reject();
269 }
270
271 }
272
273 /***
274 * Starts the download offer.
275 */
276 public class StopAction extends AbstractStopAction {
277
278
279
280 public StopAction()
281 {
282 }
283
284
285
286 public void actionPerformed(ActionEvent event)
287 {
288 DccDownload.this.stop();
289 }
290
291 }
292
293 private class StateMachine extends FiniteStateMachine
294 {
295
296 private DownloadRunner runner;
297
298
299
300 public StateMachine()
301 {
302 super(State.NOT_STARTED, TRANSITION_TABLE);
303
304 setDescription(XNap.tr("Please accept or reject download"));
305 }
306
307
308
309 protected synchronized void stateChanged(State oldState,
310 State newState)
311 {
312 if (newState == State.CONNECTING) {
313 runner = new DownloadRunner();
314 Thread t = new Thread(runner, "DccDownload:" + getFilename());
315 t.start();
316
317 stopAction.setEnabledLater(true);
318 }
319 else if (newState == State.STOPPING) {
320 runner.die = true;
321
322 stopAction.setEnabledLater(false);
323 }
324 else if (newState == State.STOPPED || newState == State.SUCCEEDED) {
325 runner = null;
326
327 stopAction.setEnabledLater(false);
328 }
329
330 if (oldState == State.NOT_STARTED) {
331 acceptAction.setEnabledLater(false);
332 rejectAction.setEnabledLater(false);
333 }
334 }
335
336 }
337
338 private class DownloadRunner implements Runnable
339 {
340
341
342
343 private boolean die = false;
344
345
346
347 public void run()
348 {
349 InputStream in = null;
350 OutputStream out = null;
351 try {
352
353 logger.debug("connecting to " + host + ":" + port);
354 Socket socket = new Socket(host, port);
355 socket.setSoTimeout(SOCKET_TIMEOUT);
356
357 in = new ThrottledInputStream
358 (new BufferedInputStream(socket.getInputStream()));
359 out = new BufferedOutputStream(socket.getOutputStream());
360
361 download(in, out);
362
363 setState(die ? State.STOPPED : State.SUCCEEDED);
364 }
365 catch (IOException e) {
366 logger.warn("connection failed", e);
367 setState(State.STOPPED, NetHelper.getErrorMessage(e));
368 }
369 finally {
370 try {
371 if (out != null) {
372 out.close();
373 }
374 if (in != null) {
375 in.close();
376 }
377 }
378 catch (IOException e) {
379 }
380 }
381 }
382
383 public void download(InputStream in, OutputStream out) throws IOException
384 {
385 OutputStream fileOut = null;
386 try {
387 file = FileHelper.createIncompleteFile(getFilename());
388 fileOut = new BufferedOutputStream(new FileOutputStream(file));
389
390 setState(State.DOWNLOADING);
391
392 byte[] inBuffer = new byte[1024];
393 byte[] outBuffer = new byte[4];
394
395 while (!die && totalBytesTransferred < getFilesize()) {
396
397 long toRead = getFilesize() - totalBytesTransferred;
398 int len = (int)Math.min(toRead, inBuffer.length);
399
400 len = in.read(inBuffer, 0, len);
401
402 if (len == -1) {
403 break;
404 }
405
406 fileOut.write(inBuffer, 0, len);
407 totalBytesTransferred += len;
408
409
410
411 outBuffer[0] = (byte) ((totalBytesTransferred >> 24) & 0xff);
412 outBuffer[1] = (byte) ((totalBytesTransferred >> 16) & 0xff);
413 outBuffer[2] = (byte) ((totalBytesTransferred >> 8) & 0xff);
414 outBuffer[3] = (byte) ((totalBytesTransferred >> 0) & 0xff);
415 out.write(outBuffer);
416 out.flush();
417 }
418 fileOut.flush();
419 }
420 finally {
421 transferStopped();
422
423 if (fileOut != null) {
424 try {
425 fileOut.close();
426 }
427 catch (IOException e) {
428 }
429 }
430 }
431 }
432
433 }
434
435 }