1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.plugin.joscar;
21
22 import java.awt.event.ActionEvent;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.Collections;
28 import java.util.Hashtable;
29 import java.util.Iterator;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33
34 import javax.swing.AbstractAction;
35 import javax.swing.Action;
36 import javax.swing.Icon;
37 import javax.swing.JMenu;
38 import javax.swing.JOptionPane;
39
40 import org.apache.log4j.Logger;
41 import org.xnap.XNap;
42 import org.xnap.XNapFacade;
43 import org.xnap.action.*;
44 import org.xnap.chat.ChatManager;
45 import org.xnap.event.StateEvent;
46 import org.xnap.event.StateListener;
47 import org.xnap.gui.AbstractPreferencesDialog;
48 import org.xnap.gui.StatusBar;
49 import org.xnap.gui.XNapFrame;
50 import org.xnap.gui.component.XNapMenu;
51 import org.xnap.gui.component.XNapMenuItem;
52 import org.xnap.gui.event.AbstractPreferencesDialogListener;
53 import org.xnap.gui.util.IconHelper;
54 import org.xnap.peer.HotlistManager;
55 import org.xnap.plugin.AbstractPlugin;
56 import org.xnap.util.FileHelper;
57 import org.xnap.util.IllegalOperationException;
58 import org.xnap.util.State;
59
60 import JOscarLib.Integration.Event.IncomingMessageEvent;
61 import JOscarLib.Integration.Event.IncomingUrlEvent;
62 import JOscarLib.Integration.Event.IncomingUserEvent;
63 import JOscarLib.Integration.Event.MessagingListener;
64 import JOscarLib.Integration.Event.OffgoingUserEvent;
65 import JOscarLib.Integration.Event.OfflineMessageEvent;
66 import JOscarLib.Integration.Event.StatusListener;
67
68 /***
69 * Provides a simple chat plugin for ICQ.
70 *
71 * This class creates the connection to the oscar server and is responsible of
72 * managing the buddies and open chat channels, it also updates the online
73 * status of each buddy.
74 */
75 public class JOscarPlugin extends AbstractPlugin
76 implements StateListener, MessagingListener, StatusListener,
77 PropertyChangeListener
78 {
79
80
81
82 public static String ICON_FILENAME = "licq.png";
83
84 public static Icon ICON_16 = IconHelper.getIcon(ICON_FILENAME, 16, false);
85
86 public static final String BUDDY_FILE =
87 FileHelper.getHomeDir() + "joscar_buddies";
88
89
90
91 private static JOscarPlugin instance;
92
93 private PreferencesDialogListener listener;
94
95 /***
96 * The wrapped connection to the icq network.
97 */
98 private JOscarConnection connection;
99 /***
100 * The local JOscarPeer.
101 */
102 private JOscarPeer peer;
103 /***
104 * The list of active channels hashed by chat partner.
105 */
106 private Map channelsByPeer;
107 /***
108 * The buddy list, hashed by their uins.
109 */
110 private Map buddies;
111 /***
112 * Menu plugged into XNap's plugins menu.
113 */
114 private JMenu jmOscar;
115 /***
116 * Displays if we're online or offline for now.
117 */
118 private JOscarStatusPanel spOscar;
119
120 private JOscarPreferences prefs;
121
122 private static Logger logger = Logger.getLogger(JOscarPlugin.class);
123
124
125
126 public JOscarPlugin()
127 {
128 }
129
130
131
132 public static JOscarPlugin getInstance()
133 {
134 return instance;
135 }
136
137 /***
138 * Returns the name of the plugin.
139 */
140 public String getName()
141 {
142 return getInfo().getName();
143 }
144
145 /***
146 * Starts the plugin.
147 */
148 public void start()
149 {
150 instance = this;
151 prefs = JOscarPreferences.getInstance();
152 prefs.addPropertyChangeListener("username", this);
153
154 connection = new JOscarConnection();
155 connection.addStateListener(this);
156
157 peer = new JOscarPeer(prefs.getUsername());
158
159 channelsByPeer = Collections.synchronizedMap(new Hashtable());
160 buddies = Collections.synchronizedMap(new Hashtable());
161
162 readBuddiesList();
163
164 if (prefs.getAutoconnect()) {
165 connection.connect();
166 }
167 }
168
169 /***
170 * Starts the GUI of the plugin.
171 */
172 public void startGUI()
173 {
174 initializeMenu();
175 XNapFrame.getInstance().getMainMenuBar().addPluginMenu(jmOscar);
176 listener = new PreferencesDialogListener();
177 XNapFacade.addPluginPreferencesDialogListener(listener);
178
179
180 spOscar = new JOscarStatusPanel();
181 connection.addStateListener(spOscar);
182 StatusBar.getInstance().addComponent(spOscar);
183 }
184
185 private void initializeMenu()
186 {
187 jmOscar = new XNapMenu(getName());
188 jmOscar.setIcon(IconHelper.getMenuIcon(ICON_FILENAME));
189 jmOscar.add(new XNapMenuItem(new AddBuddyAction()));
190 jmOscar.addSeparator();
191 jmOscar.add(new XNapMenuItem(new ConnectAction()));
192 jmOscar.add(new XNapMenuItem(new DisconnectAction()));
193 }
194
195 /***
196 * Stops the plugin. Disposes all singletons.
197 */
198 public void stop()
199 {
200 removeChannels();
201 writeBuddiesList();
202 removeBuddies();
203
204 try {
205 connection.disconnect();
206 }
207 catch (IllegalOperationException ie) {
208 }
209
210 connection = null;
211 peer = null;
212 buddies = null;
213 channelsByPeer = null;
214 prefs.removePropertyChangeListener(this);
215 JOscarPreferences.disposeInstance();
216 prefs = null;
217 instance = null;
218 }
219
220 public synchronized void propertyChange(PropertyChangeEvent e)
221 {
222 peer = new JOscarPeer(prefs.getUsername());
223 if (connection.getState() == State.CONNECTED) {
224 connection.disconnect();
225 connection.connect();
226 }
227 }
228
229 /***
230 *
231 */
232 public void stateChanged(StateEvent event)
233 {
234 if (connection.getState() == State.CONNECTED) {
235 connection.addMessagingListener(this);
236 connection.addStatusListener(this);
237
238 synchronized(buddies) {
239 for (Iterator i = buddies.keySet().iterator(); i.hasNext();) {
240 connection.addUser((String)i.next());
241 }
242 }
243 }
244 else {
245 }
246 }
247
248 /***
249 * Stops the GUI of the plugin.
250 */
251 public void stopGUI()
252 {
253 listener.dispose();
254 XNapFacade.removePluginPreferencesDialogListener(listener);
255 listener = null;
256
257 XNapFrame.getInstance().getMainMenuBar().removePluginMenu(jmOscar);
258 jmOscar = null;
259
260 StatusBar.getInstance().removeComponent(spOscar);
261 connection.removeStateListener(spOscar);
262 spOscar = null;
263 }
264
265 /***
266 * Tells {@link ChatManager} to close all joscar channels.
267 */
268 private void removeChannels()
269 {
270 synchronized (channelsByPeer) {
271 for (Iterator i = channelsByPeer.values().iterator();
272 i.hasNext();) {
273 ChatManager.getInstance().remove((JOscarChannel)i.next());
274 }
275 }
276 }
277
278 public void sendMessage(String uin, String message)
279 {
280 connection.sendMessage(uin, message);
281 }
282
283 public void sendSMS(String uin, String message)
284 {
285 connection.sendSMS(uin, message);
286 }
287
288 /***
289 * Creates a new {@link JOscarPeer} if it's not already in the
290 * <code>buddies</code> hashtable.
291 */
292 public JOscarPeer addBuddy(String uin)
293 {
294 return addBuddy(new JOscarPeer(uin));
295 }
296
297 /***
298 * Creates a new {@link JOscarPeer} if it's not already in the
299 * <code>buddies</code> hashtable.
300 *
301 */
302 public JOscarPeer addBuddy(JOscarPeer buddy)
303 {
304 if (!buddies.containsKey(buddy.getName())) {
305 connection.addStateListener(buddy);
306 buddies.put(buddy.getName(), buddy);
307 connection.addUser(buddy.getName());
308 HotlistManager.getInstance().add(buddy);
309 return buddy;
310 }
311 return (JOscarPeer)buddies.get(buddy.getName());
312 }
313
314 /***
315 * Returns the local peer.
316 */
317 public synchronized JOscarPeer getLocalPeer()
318 {
319 return peer;
320 }
321
322 /***
323 * Returns the oscar connection for listeners to subscribe to.
324 */
325 public JOscarConnection getConnection()
326 {
327 return connection;
328 }
329
330 /***
331 * Removes a buddy.
332 */
333 public void removeBuddy(JOscarPeer buddy)
334 {
335 buddies.remove(buddy.getName());
336 connection.removeStateListener(buddy);
337 HotlistManager.getInstance().remove(buddy);
338 }
339
340 private void removeBuddies()
341 {
342 JOscarPeer[] peers =
343 (JOscarPeer[])buddies.values().toArray(new JOscarPeer[0]);
344
345 for (int i = 0; i < peers.length; i++) {
346 removeBuddy(peers[i]);
347 }
348 }
349
350 public void addChannel(JOscarChannel channel)
351 {
352 if (!channelsByPeer.containsKey(channel.getJOscarPeer())) {
353 channelsByPeer.put(channel.getJOscarPeer(), channel);
354 ChatManager.getInstance().add(channel);
355 }
356 }
357
358 public void removeChannel(JOscarChannel channel)
359 {
360 logger.debug("channel remove called for "
361 + channel.getJOscarPeer().getName());
362 channelsByPeer.remove(channel.getJOscarPeer());
363 }
364
365 private void readBuddiesList()
366 {
367 LinkedList list = new LinkedList();
368
369 try {
370 FileHelper.readBinary(new File(BUDDY_FILE), list);
371 }
372 catch (IOException ie) {
373 logger.debug("error reading buddies file", ie);
374 }
375
376 for (Iterator i = list.iterator(); i.hasNext();) {
377 addBuddy((String)i.next());
378 }
379 }
380
381 private void writeBuddiesList()
382 {
383 try {
384 FileHelper.writeBinary(new File(BUDDY_FILE), buddies.keySet());
385 }
386 catch (IOException ie) {
387 logger.debug("error writing buddies file", ie);
388 }
389 }
390
391 /***
392 * Implements the {@link OscarListener} interface.
393 */
394 public void onIncomingMessage(IncomingMessageEvent event)
395 {
396 JOscarPeer peer = addBuddy(event.getSenderID());
397 if (!channelsByPeer.containsKey(peer)) {
398 JOscarChannel channel = new JOscarChannel(peer);
399 addChannel(channel);
400
401 channel.messageReceived(peer, event.getMessage());
402 }
403 }
404
405 /***
406 * Implements the {@link OscarListener} interface.
407 */
408 public void onIncomingUrl(IncomingUrlEvent e)
409 {
410
411 }
412
413 /***
414 * Implements the {@link OscarListener} interface.
415 */
416 public void onIncomingUser(IncomingUserEvent e)
417 {
418 JOscarPeer peer = (JOscarPeer)buddies.get(e.getIncomingUserId());
419 if (peer != null) {
420 logger.debug("buddy " + peer.getName()
421 + " seems to have gone online");
422 peer.setStatus(e.getStatusMode().toString());
423 }
424 }
425
426 /***
427 * Implements the {@link OscarListener} interface.
428 */
429 public void onOffgoingUser(OffgoingUserEvent e)
430 {
431 JOscarPeer peer = (JOscarPeer)buddies.get(e.getOffgoingUserId());
432 logger.debug("buddy " + peer.getName() + " seems to have gone offline");
433 if (peer != null) {
434 peer.setStatus(XNap.tr("Offline"));
435 }
436 }
437
438 /***
439 * Implements the {@link OscarListener} interface.
440 */
441 public void onOfflineMessage(OfflineMessageEvent e)
442 {
443 logger.debug("buddy " + e.getSenderUin() + " is going offline, " +
444 e.getMessage());
445 }
446
447
448
449 private class AddBuddyAction extends AbstractAction
450 {
451 public AddBuddyAction()
452 {
453 putValue(Action.NAME, XNap.tr("Add Buddy"));
454 putValue(Action.SHORT_DESCRIPTION,
455 XNap.tr("Asks for a UIN and adds it to the Hotlist."));
456 putValue(IconHelper.XNAP_ICON, "edit_add.png");
457 }
458
459 public void actionPerformed(ActionEvent event)
460 {
461 Object[] message = new Object[] {
462 XNap.tr("Buddy UIN")
463 };
464
465 String uin = JOptionPane.showInputDialog
466 (XNapFrame.getInstance().getRootPane(), message,
467 XNap.tr("Add Buddy"), JOptionPane.PLAIN_MESSAGE);
468
469 if (uin != null) {
470 JOscarPlugin.getInstance().addBuddy(uin.trim());
471 }
472 }
473 }
474
475 private class ConnectAction extends AbstractXNapAction
476 implements StateListener
477 {
478 public ConnectAction()
479 {
480 putValue(Action.NAME, XNap.tr("Connect"));
481 putValue(IconHelper.XNAP_ICON, "connect_creating.png");
482 connection.addStateListener(this);
483 stateChanged(null);
484 }
485
486 public void actionPerformed(ActionEvent event)
487 {
488 connection.connect();
489 }
490
491 public void stateChanged(StateEvent e)
492 {
493 setEnabledLater(connection.getState() == State.DISCONNECTED);
494 }
495 }
496
497 private class DisconnectAction extends AbstractXNapAction
498 implements StateListener
499 {
500 public DisconnectAction()
501 {
502 putValue(Action.NAME, XNap.tr("Disconnect"));
503 putValue(IconHelper.XNAP_ICON, "connect_no.png");
504 connection.addStateListener(this);
505 stateChanged(null);
506 }
507
508 public void actionPerformed(ActionEvent event)
509 {
510 connection.disconnect();
511 }
512
513 public void stateChanged(StateEvent e)
514 {
515 setEnabledLater(connection.getState() == State.CONNECTED);
516 }
517 }
518
519 private class PreferencesDialogListener
520 extends AbstractPreferencesDialogListener
521 {
522 public void addPanels(AbstractPreferencesDialog dialog, List panels)
523 {
524 JOscarPreferencesPanel jpp = new JOscarPreferencesPanel();
525 panels.add(jpp);
526 panels.add(dialog.addPanel(jpp, ICON_FILENAME));
527 }
528 }
529 }