1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.xnap.plugin.opennap.net;
22
23 import javax.swing.Action;
24 import javax.swing.Icon;
25
26 import java.awt.event.ActionEvent;
27 import java.io.BufferedInputStream;
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.InputStreamReader;
33 import java.io.OutputStream;
34 import java.net.Socket;
35 import java.util.Hashtable;
36
37 import org.apache.log4j.Logger;
38 import org.xnap.XNap;
39 import org.xnap.net.NetHelper;
40 import org.xnap.peer.Peer;
41 import org.xnap.plugin.Plugin;
42 import org.xnap.plugin.opennap.OpenNapPlugin;
43 import org.xnap.plugin.opennap.net.msg.ExceptionListener;
44 import org.xnap.plugin.opennap.net.msg.MessageHandler;
45 import org.xnap.plugin.opennap.net.msg.MessageListener;
46 import org.xnap.plugin.opennap.net.msg.client.DirectBrowseRequestMessage;
47 import org.xnap.plugin.opennap.net.msg.server.DirectBrowseAckMessage;
48 import org.xnap.plugin.opennap.net.msg.server.DirectBrowseErrorMessage;
49 import org.xnap.plugin.opennap.net.msg.server.ServerMessage;
50 import org.xnap.plugin.opennap.user.OpenNapUser;
51 import org.xnap.search.SearchFilter;
52 import org.xnap.search.SearchHandler;
53 import org.xnap.search.SearchResult;
54 import org.xnap.transfer.AbstractDownload;
55 import org.xnap.transfer.DefaultSegment;
56 import org.xnap.transfer.DownloadManager;
57 import org.xnap.transfer.Segment;
58 import org.xnap.transfer.action.AbstractStopAction;
59 import org.xnap.util.FiniteStateMachine;
60 import org.xnap.util.QuotedStringTokenizer;
61 import org.xnap.util.Scheduler;
62 import org.xnap.util.State;
63 import org.xnap.util.XNapTask;
64
65 public class OpenNapDirectBrowse extends AbstractDownload
66 implements ExceptionListener, MessageListener,
67 OpenNapBrowseInterface, SocketListener {
68
69
70
71 public static final int REQUEST_TIMEOUT = 2 * 60 * 1000;
72
73 public static final int SOCKET_TIMEOUT = 1 * 60 * 1000;
74
75 /***
76 * The state transition table.
77 */
78 private static final Hashtable TRANSITION_TABLE;
79 static {
80 State[][] table = new State[][] {
81 { State.NOT_STARTED,
82 State.REQUESTING, },
83 { State.REQUESTING,
84 State.CONNECTING, State.FINISHED, State.STOPPING, },
85 { State.CONNECTING,
86 State.DOWNLOADING, State.FINISHED, State.STOPPING, },
87 { State.DOWNLOADING,
88 State.FINISHED, State.STOPPING, },
89 { State.STOPPING,
90 State.FINISHED }
91 };
92
93 TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
94 }
95
96
97
98 protected static Logger logger = Logger.getLogger(OpenNapDirectBrowse.class);
99
100 private StateMachine sm = new StateMachine();
101
102 private OpenNapUser user;
103
104 private SearchHandler handler;
105
106 private BrowseDownloadSocket inSocket;
107
108 private long bytesTransferred;
109 private DefaultSegment segment;
110 private long size;
111
112 private boolean failed;
113
114
115
116 public OpenNapDirectBrowse(OpenNapUser user)
117 {
118 this.user = user;
119 }
120
121
122
123 public void exceptionThrown(Exception e)
124 {
125 failed = true;
126 setState(State.FINISHED, e.getLocalizedMessage());
127 }
128
129 public File getFile()
130 {
131 return null;
132 }
133
134 public String getFilename()
135 {
136 return XNap.tr("OpenNap Direct Browse");
137 }
138
139 /***
140 *
141 */
142 public long getFilesize()
143 {
144 return size;
145 }
146
147 /***
148 * Returns <code>null</code>.
149 */
150 public SearchFilter getFilter()
151 {
152 return null;
153 }
154
155 /***
156 * Returns the name of the user that is browsed.
157 */
158 public String getName()
159 {
160 return user.getName();
161 }
162
163 public Plugin getPlugin()
164 {
165 return OpenNapPlugin.getInstance();
166 }
167
168 /***
169 * @see xnap.transfer.Transfer#getActions()
170 */
171 public Action[] getActions()
172 {
173 return new Action[] { new StopAction() };
174 }
175
176 /***
177 *
178 */
179 public long getBytesTransferred()
180 {
181 return bytesTransferred;
182 }
183
184 public Icon getIcon()
185 {
186 return OpenNapPlugin.ICON_16;
187 }
188
189 /***
190 * @see xnap.transfer.Transfer#getPeer()
191 */
192 public Peer getPeer()
193 {
194 return user;
195 }
196
197 public Segment[] getSegments()
198 {
199 return (segment != null) ? new Segment[] { segment } : null;
200 }
201
202 /***
203 * @see xnap.transfer.Transfer#getStatus()
204 */
205 public String getStatus()
206 {
207 return sm.getDescription();
208 }
209
210 /***
211 * @see xnap.transfer.Transfer#getTotalBytesTransferred()
212 */
213 public long getTotalBytesTransferred()
214 {
215 return (isDone()) ? bytesTransferred : 0;
216 }
217
218 public boolean isDone()
219 {
220 return sm.getState() == State.FINISHED;
221 }
222
223 public boolean isFailed()
224 {
225 return failed;
226 }
227
228 public boolean isRunning()
229 {
230 return sm.getState() == State.DOWNLOADING;
231 }
232
233 /***
234 * Handles messages received from the server as response to the download
235 * request. Notifies the parent download container if download is ready to
236 * start or if it should be removed due to an error message received from
237 * the server.
238 */
239 public void messageReceived(ServerMessage msg)
240 {
241 if (msg instanceof DirectBrowseErrorMessage) {
242 DirectBrowseErrorMessage m = (DirectBrowseErrorMessage)msg;
243 if (m.nick.equals(user.getName())) {
244 msg.consume();
245 setState(State.FINISHED, m.message);
246 }
247 }
248 else if (msg instanceof DirectBrowseAckMessage) {
249 DirectBrowseAckMessage m = (DirectBrowseAckMessage)msg;
250 if (m.nick.equals(user.getName())) {
251 msg.consume();
252 user.setHost(m.ip);
253 user.setPort(m.port);
254 setState(State.CONNECTING);
255 }
256 }
257 }
258
259 /***
260 * Returns <code>true</code>.
261 */
262 public boolean showTree()
263 {
264 return true;
265 }
266
267 public boolean socketReceived(IncomingSocket s)
268 {
269 if (s instanceof BrowseDownloadSocket) {
270
271
272 BrowseDownloadSocket b = (BrowseDownloadSocket)s;
273 if (user.getName().equals(b.nick)) {
274 inSocket = b;
275 setState(State.CONNECTING);
276 return true;
277 }
278 }
279 return false;
280 }
281
282 /***
283 * Starts this search.
284 *
285 * @param handler the object that handles the results
286 */
287 public void start(SearchHandler handler)
288 {
289 this.handler = handler;
290
291 setState(State.REQUESTING);
292 }
293
294 /***
295 * Cancels this search.
296 */
297 public void stop()
298 {
299 setState(State.STOPPING);
300 }
301
302 void setState(State newState, String description)
303 {
304 sm.setState(newState, description);
305 stateChanged();
306 handler.stateChanged(this);
307 }
308
309 void setState(State newState)
310 {
311 sm.setState(newState);
312 stateChanged();
313 handler.stateChanged(this);
314 }
315
316
317
318 private class StateMachine extends FiniteStateMachine {
319
320
321
322 private OpenNapDirectBrowseDownloadRunner runner;
323 private Thread t;
324 private XNapTask watcher;
325
326
327
328 public StateMachine()
329 {
330 super(State.NOT_STARTED, TRANSITION_TABLE);
331 }
332
333
334
335 protected synchronized void stateChanged(State oldState,
336 State newState)
337 {
338 if (newState == State.REQUESTING) {
339 DownloadManager.getInstance().add(OpenNapDirectBrowse.this);
340
341 user.getServer().getListener().addSocketListener
342 (OpenNapDirectBrowse.this);
343
344 MessageHandler.subscribe(DirectBrowseAckMessage.TYPE,
345 OpenNapDirectBrowse.this);
346 MessageHandler.subscribe(DirectBrowseErrorMessage.TYPE,
347 OpenNapDirectBrowse.this);
348
349 DirectBrowseRequestMessage msg
350 = new DirectBrowseRequestMessage(user.getName());
351 msg.setExceptionListener(OpenNapDirectBrowse.this);
352 MessageHandler.send(user.getServer(), msg);
353
354 watcher = new WatchTask();
355 Scheduler.run(REQUEST_TIMEOUT, watcher);
356 }
357 else if (newState == State.CONNECTING) {
358 runner = new OpenNapDirectBrowseDownloadRunner(inSocket);
359 t = new Thread(runner, "OpenNapDirectBrowseDownload:"
360 + user.getName());
361 t.start();
362 }
363 else if (newState == State.DOWNLOADING) {
364 transferStarted();
365 }
366 else if (newState == State.STOPPING) {
367 if (oldState == State.CONNECTING
368 || oldState == State.DOWNLOADING) {
369 runner.stop();
370 t.interrupt();
371 }
372 else {
373 this.setState(State.FINISHED);
374 }
375 }
376 else if (newState == State.FINISHED) {
377 DownloadManager.getInstance().remove(OpenNapDirectBrowse.this);
378 }
379
380
381 if (oldState == State.REQUESTING) {
382 if (watcher != null) {
383 watcher.cancel();
384 watcher = null;
385 }
386
387 user.getServer().getListener().removeSocketListener
388 (OpenNapDirectBrowse.this);
389
390 MessageHandler.unsubscribe(DirectBrowseAckMessage.TYPE,
391 OpenNapDirectBrowse.this);
392 MessageHandler.unsubscribe(DirectBrowseErrorMessage.TYPE,
393 OpenNapDirectBrowse.this);
394 }
395 else if (oldState == State.DOWNLOADING) {
396 transferStopped();
397 }
398 }
399 }
400
401 private class WatchTask extends XNapTask
402 {
403 public void run()
404 {
405 synchronized (sm) {
406 if (sm.getState() == State.REQUESTING) {
407 failed = true;
408 sm.setState(State.FINISHED, "timeout");
409 }
410 }
411 stateChanged();
412 handler.stateChanged(OpenNapDirectBrowse.this);
413 }
414 }
415
416 private class StopAction extends AbstractStopAction
417 {
418 public void actionPerformed(ActionEvent e)
419 {
420 stop();
421 }
422 }
423
424 private class OpenNapDirectBrowseDownloadRunner implements Runnable
425 {
426 private boolean die;
427
428 private Socket socket;
429 private InputStream in;
430 private OutputStream out;
431
432 public OpenNapDirectBrowseDownloadRunner(BrowseDownloadSocket b)
433 {
434 if (b != null) {
435 socket = b.socket;
436 in = b.in;
437 }
438 }
439
440 public void run()
441 {
442 try {
443 if (socket != null) {
444 connect();
445 }
446 else {
447 connect(user.getHost(), user.getPort());
448 }
449
450 setState(State.DOWNLOADING);
451
452 download();
453
454 setState(State.FINISHED);
455 }
456 catch (IOException e) {
457 failed = true;
458 setState(State.FINISHED, NetHelper.getErrorMessage(e));
459 }
460 catch (InterruptedException e) {
461 failed = true;
462 setState(State.FINISHED, XNap.tr("Stopped"));
463 }
464 finally {
465 close();
466 }
467 }
468
469 public void stop()
470 {
471 die = true;
472 }
473
474 private void close()
475 {
476 try {
477 if (socket != null)
478 socket.close();
479 if (in != null)
480 in.close();
481 if (out != null)
482 out.close();
483 }
484 catch (IOException e) {
485 }
486 }
487
488 private void connect(String host, int port) throws IOException
489 {
490 logger.debug("connecting to " + host + ":" + port
491 + " for direct browse");
492 socket = new Socket(host, port);
493 socket.setSoTimeout(SOCKET_TIMEOUT);
494
495 out = socket.getOutputStream();
496 in = new BufferedInputStream(socket.getInputStream());
497
498
499 logger.debug("reading magic number");
500 char c = (char)in.read();
501 if (c != '1') {
502 throw new IOException(XNap.tr("Invalid request"));
503 }
504
505 write("GETLIST");
506
507 byte data[] = new byte[2048];
508 in.mark(2048);
509 int i = in.read(data);
510
511 if (i > 0) {
512 String nick = new String(data, 0, i);
513 int j = nick.indexOf("\n");
514 if (j == -1) {
515
516 in.read();
517 }
518 else {
519 nick = nick.substring(0, j).trim();
520
521 in.reset();
522 in.skip(j + 1);
523 }
524
525 if (!nick.equals(user.getName())) {
526 throw new IOException("Invalid user: " + nick);
527 }
528 }
529 else {
530 throw new IOException("Socket error");
531 }
532 }
533
534 private void connect() throws IOException
535 {
536 socket.setSoTimeout(SOCKET_TIMEOUT);
537
538 out = socket.getOutputStream();
539 }
540
541 private void download() throws IOException, InterruptedException
542 {
543 BufferedReader reader
544 = new BufferedReader(new InputStreamReader(in));
545
546 int count = user.getFileCount();
547 if (count != -1) {
548 segment = new DefaultSegment(count);
549 }
550
551 String s;
552 count = 0;
553 while ((s = reader.readLine()) != null) {
554 if (die) {
555 throw new InterruptedException();
556 }
557 else if (s.length() == 0) {
558 break;
559 }
560
561 bytesTransferred += s.getBytes().length;
562 if (segment != null) {
563 segment.commit(1);
564 }
565
566 QuotedStringTokenizer t = new QuotedStringTokenizer(s);
567
568 if (t.countTokens() < 6) {
569 continue;
570 }
571
572 try {
573 String filename = t.nextToken();
574 String md5 = t.nextToken();
575 long size = Long.parseLong(t.nextToken());
576 int bitrate = Integer.parseInt(t.nextToken());
577 int frequency = Integer.parseInt(t.nextToken());
578 int length = Integer.parseInt(t.nextToken());
579
580 SearchResult result = new OpenNapSearchResult
581 (null, size, bitrate, frequency, length,
582 user, filename, md5);
583 handler.resultReceived(result);
584 }
585 catch (NumberFormatException e) {
586 }
587 }
588 }
589
590 private void write(String message) throws IOException
591 {
592 logger.debug("> " + message);
593 out.write(message.getBytes());
594 out.flush();
595 }
596
597 }
598
599 }
600