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 java.awt.event.ActionEvent;
23 import java.io.BufferedInputStream;
24 import java.io.BufferedOutputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.net.ServerSocket;
31 import java.net.Socket;
32 import java.util.Hashtable;
33
34 import javax.swing.Action;
35
36 import org.apache.log4j.Logger;
37 import org.xnap.XNap;
38 import org.xnap.io.ThrottledOutputStream;
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.FiniteStateMachine;
45 import org.xnap.util.IllegalOperationException;
46 import org.xnap.util.State;
47
48 /***
49 * Uploads a file via the dcc protocol.
50 */
51 public class DccUpload extends AbstractUpload {
52
53
54
55 public static final int SOCKET_TIMEOUT = 60 * 1000;
56
57 private static final Hashtable TRANSITION_TABLE;
58 static {
59 State[][] table = new State[][] {
60 { State.NOT_STARTED,
61 State.CONNECTING, },
62 { State.CONNECTING,
63 State.UPLOADING, State.STOPPING, State.STOPPED, },
64 { State.UPLOADING,
65 State.SUCCEEDED, State.STOPPING, State.STOPPED, },
66 { State.STOPPING,
67 State.SUCCEEDED, State.STOPPED }
68 };
69
70 TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
71 }
72
73
74
75 private static Logger logger = Logger.getLogger(DccDownload.class);
76
77 private File file;
78 private Peer peer;
79 private int port = 0;
80 private long totalBytesTransferred = 0;
81 private StateMachine sm = new StateMachine();
82 private int localPort;
83
84 private AbstractTransferAction stopAction = new StopAction();
85
86
87
88 /***
89 *
90 * @param peer the peer thae file is downloaded from
91 * @param file the destination file, the downloaded data is stored here
92 * @param host the host to connect to
93 * @param port the port to connect to
94 */
95 public DccUpload(Peer peer, File file)
96 {
97 this.peer = peer;
98 this.file = file;
99 }
100
101
102
103 public long getBytesTransferred()
104 {
105 return totalBytesTransferred;
106 }
107
108 public Action[] getActions()
109 {
110 return new Action[] { stopAction, };
111 }
112
113 public File getFile()
114 {
115 return file;
116 }
117
118 public String getFilename()
119 {
120 return file.getName();
121 }
122
123 public long getFilesize()
124 {
125 return file.length();
126 }
127
128 public int getLocalPort()
129 {
130 return localPort;
131 }
132
133 public Peer getPeer()
134 {
135 return peer;
136 }
137
138 public Plugin getPlugin()
139 {
140 return null;
141 }
142
143 public String getStatus()
144 {
145 return sm.getDescription();
146 }
147
148 public long getTotalBytesTransferred()
149 {
150 return totalBytesTransferred;
151 }
152
153 public boolean isDone()
154 {
155 State s = sm.getState();
156 return s == State.SUCCEEDED || s == State.STOPPED;
157 }
158
159 public boolean isRunning()
160 {
161 State s = sm.getState();
162 return s == State.CONNECTING || s == State.DOWNLOADING;
163 }
164
165 public void start()
166 {
167 try {
168 setState(State.CONNECTING);
169 }
170 catch(IllegalOperationException e) {
171 logger.debug("unexpected exception", e);
172 }
173 }
174
175 private void setState(State newState, String description)
176 {
177 sm.setState(newState, description);
178 stateChanged();
179 }
180
181 private void setState(State newState)
182 {
183 sm.setState(newState);
184 stateChanged();
185 }
186
187 public void stop()
188 {
189 try {
190 setState(State.STOPPING);
191 }
192 catch(IllegalOperationException e) {
193 logger.debug("unexpected exception", e);
194 }
195 }
196
197
198
199 /***
200 * Starts the download offer.
201 */
202 public class StopAction extends AbstractStopAction {
203
204
205
206 public StopAction()
207 {
208 }
209
210
211
212 public void actionPerformed(ActionEvent event)
213 {
214 DccUpload.this.stop();
215 }
216
217 }
218
219 private class StateMachine extends FiniteStateMachine
220 {
221
222 private ServerSocket serverSocket;
223 private UploadRunner runner;
224
225
226
227 public StateMachine()
228 {
229 super(State.NOT_STARTED, TRANSITION_TABLE);
230 }
231
232
233
234 protected synchronized void stateChanged(State oldState,
235 State newState)
236 {
237 if (newState == State.CONNECTING) {
238 try {
239 serverSocket = new ServerSocket(0);
240 serverSocket.setSoTimeout(SOCKET_TIMEOUT);
241 }
242 catch (IOException e) {
243 logger.debug("Could not open listener socket", e);
244 this.setState(State.STOPPED, XNap.tr("Could not open listener"));
245 }
246
247 localPort = serverSocket.getLocalPort();
248 if (localPort != 0) {
249 logger.debug("opened socket on :" + localPort);
250
251 runner = new UploadRunner(serverSocket);
252 Thread t = new Thread(runner, "DccUpload:" + getFilename());
253 t.start();
254 }
255 }
256 else if (newState == State.STOPPING) {
257 runner.die = true;
258
259 stopAction.setEnabledLater(false);
260 }
261 else if (newState == State.STOPPED || newState == State.SUCCEEDED) {
262 runner = null;
263
264 stopAction.setEnabledLater(false);
265 }
266
267 if (oldState == State.CONNECTING) {
268 try {
269 if (serverSocket != null) {
270 serverSocket.close();
271 }
272 }
273 catch (IOException e) {
274 logger.debug("Could not close listener socket", e);
275 }
276 serverSocket = null;
277 }
278 }
279
280 }
281
282 private class UploadRunner implements Runnable
283 {
284
285 private ServerSocket serverSocket;
286 private boolean die = false;
287
288 public UploadRunner(ServerSocket serverSocket)
289 {
290 this.serverSocket = serverSocket;
291 }
292
293 public void run()
294 {
295 Socket socket = null;
296 InputStream in = null;
297 OutputStream out = null;
298 try {
299 socket = serverSocket.accept();
300 socket.setSoTimeout(SOCKET_TIMEOUT);
301
302 in = new BufferedInputStream(socket.getInputStream());
303 out = new ThrottledOutputStream
304 (new BufferedOutputStream(socket.getOutputStream()));
305
306 upload(in, out);
307
308 setState(die ? State.STOPPED : State.SUCCEEDED);
309 }
310 catch (IOException e) {
311 logger.warn("connection failed", e);
312 setState(State.STOPPED, NetHelper.getErrorMessage(e));
313 }
314 finally {
315 try {
316 if (out != null) {
317 out.close();
318 }
319 if (in != null) {
320 in.close();
321 }
322 if (socket != null) {
323 socket.close();
324 }
325 }
326 catch (IOException e) {
327 }
328 }
329 }
330
331 public void upload(InputStream in, OutputStream out) throws IOException
332 {
333 InputStream fileIn = null;
334 try {
335 fileIn = new BufferedInputStream
336 (new FileInputStream(DccUpload.this.getFile()));
337
338 setState(State.UPLOADING);
339
340 byte[] inBuffer = new byte[4];
341 byte[] outBuffer = new byte[1024];
342
343 while (!die && totalBytesTransferred < getFilesize()) {
344
345 long toRead = getFilesize() - totalBytesTransferred;
346 int len = (int)Math.min(toRead, outBuffer.length);
347
348 len = fileIn.read(outBuffer, 0, len);
349
350 if (len == -1) {
351 break;
352 }
353
354 out.write(outBuffer, 0, len);
355 totalBytesTransferred += len;
356
357
358 in.read(inBuffer, 0, inBuffer.length);
359 }
360 }
361 finally {
362 transferStopped();
363
364 if (fileIn != null) {
365 try {
366 fileIn.close();
367 }
368 catch (IOException e) {
369 }
370 }
371 }
372 }
373
374 }
375
376 }