source: trunk/hiptop/cognet++/org/twodot/cognet/Engine.java@ 210

Last change on this file since 210 was 210, checked in by Nicholas Riley, 18 years ago

cognet++: Brian Swetland et al.'s generic proxied hiptop chat client.
Requires cognetd to function.

File size: 14.6 KB
Line 
1/*
2 * cognet chat app
3 *
4 * Copyright 2003, Brian Swetland <swetland@frotz.net>
5 * See LICENSE for redistribution terms
6 *
7 */
8package org.twodot.cognet;
9
10import java.io.IOException;
11import java.io.InputStream;
12import java.io.OutputStream;
13import java.net.Socket;
14import java.util.Hashtable;
15
16import danger.ui.MarqueeAlert;
17import danger.ui.Menu;
18import danger.ui.MenuItem;
19import danger.ui.NotificationManager;
20import danger.ui.Shortcut;
21
22public class Engine implements Resources, Runnable
23{
24 public Engine(Cognet theapp) {
25 app = theapp;
26
27 initSettings();
28
29 out_lock = new Object();
30 in_buffer = new byte[512];
31 state = OFFLINE;
32 login = new LoginWindow(this);
33 first = new ChatWindow(this,"Cognet Server","%server");
34 first.next = first;
35 first.prev = first;
36 windows = new Hashtable();
37 windows.put("%server",first);
38
39 login.show();
40
41 }
42
43 void initSettings() {
44 String settingsHost, settingsUser, settingsPasswd;
45 int settingsPort;
46
47 //get Host
48 settingsHost = app.cogSettings.getStringValue("host");
49 if (settingsHost != null) HOST = settingsHost;
50
51 try {
52 //get port
53 PORT = app.cogSettings.getIntValue("port");
54
55 //get user
56 settingsUser = app.cogSettings.getStringValue("user");
57 if (settingsUser != null) USER = settingsUser;
58
59 //get pass
60 settingsPasswd = app.cogSettings.getStringValue("password");
61 if (settingsPasswd != null) PASSWD = settingsPasswd;
62
63 boolean settingsSmiley, settingsMarquee;
64 boolean settingsActivity, settingsAutoConnect;
65
66 //get prefs
67 settingsSmiley = 1 == app.cogSettings.getIntValue("doSmileys");
68 settingsMarquee = 1 == app.cogSettings.getIntValue("doMarquee");
69 settingsActivity = 1 == app.cogSettings.getIntValue("doActivity");
70 settingsAutoConnect = 1 == app.cogSettings.getIntValue("doAutoConnect");
71
72 app.doSmileys = settingsSmiley;
73 app.doMarquee = settingsMarquee;
74 app.doActivity = settingsActivity;
75 app.doAutoConnect = settingsAutoConnect;
76 } catch (Throwable e) {}
77 }
78
79 void AddWindows(Menu a) {
80 synchronized(lock) {
81 ChatWindow w = first;
82 do {
83 if(!w.special){
84 Character c = (Character) windowQuickKeys.get(w.key);
85 MenuItem mi = a.addItem(w.context, Cognet.kGotoWindow, 0, w.key, app);
86 mi.setIcon(w.mailbox ? app.bm_mail : app.bm_plain);
87 if(c != null) mi.setShortcut(c.charValue());
88 mi.setChecked(w == active);
89 }
90 w = w.next;
91 } while(w != first);
92 w = first;
93 do {
94 if(w.special){
95 Character c = (Character) windowQuickKeys.get(w.key);
96 MenuItem mi = a.addItem(w.context, Cognet.kGotoWindow, 0, w.key, app);
97 mi.setIcon(w.mailbox ? app.bm_mail : app.bm_plain);
98 if(c != null) mi.setShortcut(c.charValue());
99 mi.setChecked(w == active);
100 }
101 w = w.next;
102 } while(w != first);
103 }
104 }
105
106 ChatWindow NextMailbox(ChatWindow start) {
107 synchronized(lock) {
108 ChatWindow w = start.next;
109 while(w != start){
110 if(w.mailbox) return w;
111 w = w.next;
112 }
113 }
114 return null;
115 }
116
117 void sendSuspend() {
118 try {
119 //Write("suspend","%server","");
120 isSuspended = true;
121 }
122 catch( Exception e ) {};
123 }
124
125 void sendResume() {
126 if( isSuspended ) {
127 try {
128 //Write("resume","%server","");
129 }
130 catch( Exception e ) {};
131 }
132
133 isSuspended = false;
134 }
135
136
137 void nuke() {
138 ChatWindow w,n;
139
140 synchronized(lock){
141 w = first;
142 w.CLR();
143 w = w.next;
144 while(w != first){
145 w.hide();
146 w = w.next;
147 }
148
149 first.next = first;
150 first.prev = first;
151 windows = new Hashtable();
152 windows.put("%server",first);
153 }
154 RX_SERIAL="0";
155 }
156
157 public void run() {
158 System.err.println("cognet: starting...");
159 login.SendEvent(5039);
160
161 // if we don't have a last window selected
162 // or the select fails to find a window, then show
163 // the server window.
164 if (lastWindowSelected == null || !Select(lastWindowSelected,false))
165 Select("%server",true);
166
167 try {
168 synchronized(out_lock) {
169 s = new Socket(HOST, PORT);
170 System.err.println("cognet: Connected to "+HOST+":"+PORT);
171 in = s.getInputStream();
172 out = s.getOutputStream();
173 }
174 System.err.println("cognet: Signing On");
175 state = ONLINE;
176 SignOn();
177 System.err.println("cognet: IO Loop Starting");
178 login.SendEvent(5039);
179 HandleIO();
180 } catch (Throwable e){
181 System.err.println("cognet: whoops: " + e);
182 e.printStackTrace();
183 try {
184 s.close();
185 } catch (Exception x){
186 }
187 }
188
189 state = OFFLINE;
190 login.SendEvent(5039);
191 login.show();
192
193 System.err.println("cognet: Network Thread Exiting");
194 }
195
196 void login() {
197 synchronized(lock){
198 if(state == OFFLINE){
199 state = CONNECTING;
200 try {
201 (new Thread(this,"cognet: engine")).start();
202 } catch (Throwable t){
203 state = OFFLINE;
204 System.err.println("cognet: can't start engine thread");
205 }
206 }
207 }
208 }
209
210
211 void SignOn() throws IOException {
212 Write("serial",RX_SERIAL,TOKEN);
213 Write("test",USER,PASSWD);
214 /*
215 if( isSuspended )
216 Write("suspend","%server","");
217 else
218 Write("resume","%server",""); */
219 }
220
221 void logout() {
222 System.err.println("cognet: logout");
223 state = DISCONNECT;
224 PASSWD="";
225 try {
226 s.close();
227 } catch (Throwable t){
228 }
229 try {
230 in.close();
231 } catch (Throwable t){
232 }
233 try {
234 out.close();
235 } catch (Throwable t){
236 }
237 System.err.println("cognet: logged out");
238 }
239
240 void Close(String context) {
241 String key = context.toLowerCase();
242 if(key.equals("%server")) return;
243
244 ChatWindow w = null;
245 ChatWindow nextup = null;
246
247 synchronized(lock){
248 w = (ChatWindow) windows.remove(key);
249 if( w == null )
250 return;
251 nextup = w.next;
252 if(w != null){
253 w.Remove();
254 }
255 }
256
257 if(w != null) {
258 nextup.show();
259 try {
260 Write("clear",context,"");
261 } catch (IOException iox) {
262 }
263 }
264 }
265
266 void Command(String context, String str) {
267 int l = str.length();
268 if((l > 0) && (str.charAt(l - 1) < ' ')){
269 /* Why textfield likes to tack a return on the end,
270 I'll never know... */
271 str = str.substring(0, l - 1);
272 }
273
274 try {
275
276 if(str.charAt(0) == '/'){
277 if(str.startsWith("/clear")){
278 Write("clear",context,"");
279 return;
280 }
281 if(str.startsWith("/bind ")){
282 char c = str.charAt(6);
283 System.err.println("cognet: bind '"+c+"' to '"+context+"'");
284 saveQuickKey(context, c);
285 return;
286 }
287 if(str.startsWith("/win ")){
288 Select(str.substring(5), false);
289 return;
290 }
291 if(str.startsWith("/zap")) {
292 logout();
293 return;
294 }
295// if(str.startsWith("/query ")) {
296// Select(str.substring(7),true);
297// return;
298// }
299 }
300/*
301 if (str.startsWith("/font ")) {
302 String fontName = str.substring(6);
303 ChatWindow w = (ChatWindow)windows.get(context.toLowerCase());
304 if (w != null) {
305 Font font = Font.findFont(fontName);
306 w.setFont(font);
307 MSG(context, "<*> Font set to " + fontName);
308 } else {
309 MSG(context, "<*> Unknown font.");
310 }
311 }
312*/
313 /* all other commands are handled on the proxy */
314 if( str.startsWith("/") ){
315 int n = str.indexOf(' ');
316 if(n != -1){
317 Write(str.substring(1,n), context, str.substring(n+1));
318 } else {
319 Write(str.substring(1), context, "");
320 }
321 } else {
322 Write("send",context,str);
323 }
324
325 return;
326 } catch (Exception e) {
327 }
328 }
329
330
331 void HandleIO() throws IOException {
332 byte[] b = in_buffer;
333 int i, j, n, p, x;
334 byte mark = (byte) ':';
335
336 for(;;) {
337 /* read one line from the server */
338 if(state != ONLINE){
339 throw new IOException("not online");
340 }
341
342 p = 0;
343 for(;;) {
344 x = in.read();
345 if(x < 0) throw new IOException("EOF");
346 if((x == 10) || (x == 13)){
347 if(p == 0) {
348 continue;
349 } else {
350 break;
351 }
352 }
353
354 /* truncate lines that are too long */
355 if(p == 510) {
356 continue;
357 } else {
358 b[p++] = (byte) x;
359 }
360 }
361 b[p] = 0;
362
363 // System.err.println("LINE: " + new String(b,0,p));
364
365 String sn, tag, src, dst, txt;
366
367 sn = null;
368 tag = null;
369 src = null;
370
371 i = 0;
372 while(i < p){
373 if(b[i] == mark) {
374 sn = new String(b,0,i);
375 break;
376 }
377 i++;
378 }
379
380 j = i = i + 1;
381 while(i < p){
382 if(b[i] == mark) {
383 tag = new String(b,j,i-j);
384 break;
385 }
386 i++;
387 }
388
389 j = i = i + 1;
390 while(i < p){
391 if(b[i] == mark) {
392 src = new String(b,j,i-j);
393 break;
394 }
395 i++;
396 }
397
398 j = i = i + 1;
399 while(i < p){
400 if(b[i] == mark) {
401 dst = new String(b,j,i-j);
402 i++;
403 txt = new String(b,i,p-i);
404
405 if(sn.length() > 0) RX_SERIAL = sn;
406
407 // System.err.println("["+sn+":"+tag+":"+src+":"+dst+":"+txt+"]");
408
409 Process(tag,src,dst,txt);
410 break;
411 }
412 i++;
413 }
414
415 }
416 }
417
418
419 void Process(String tag, String src, String dst, String txt) {
420 //System.err.println(tag+":"+src+":"+dst+":"+txt);
421 if(tag.equals("fmsg")){
422 MSG(dst, txt);
423 } else if(tag.equals("dmsg")){
424 MSG(dst,"<"+src+"> "+txt);
425 } else if(tag.equals("dmsg/act")){
426 MSG(dst, "* "+src+" "+txt);
427 } else if(tag.equals("smsg")){
428 MSG(src,"<"+src+"> "+txt);
429 } else if(tag.equals("smsg/act")){
430 MSG(src, "* "+src+" "+txt);
431 } else if(tag.equals("show")){
432 Select(dst, true);
433 } else if(tag.equals("info")){
434 MSG(dst, "<-> " + txt);
435 } else if(tag.equals("repl")){
436 MSG(dst, "<::::> " + txt);
437 } else if(tag.equals("fail")){
438 MSG(dst, "<::::> " + txt);
439 } else if(tag.equals("clear")){
440 CLR(dst);
441 } else if(tag.equals("kill")){
442 Close(dst);
443 } else if(tag.equals("url")){
444 URL(dst, src, txt);
445 } else if(tag.equals("set")){
446 if (dst.startsWith("quick/"))
447 bindQuickKey(txt, dst.charAt(6));
448 } else if(tag.equals("sync")){
449 System.err.println("Got sync token '"+dst+"'");
450 System.err.println("Have token '"+TOKEN+"'");
451 if(!TOKEN.equals(dst)){
452 System.err.println("Token mismatch. Clearing");
453 nuke();
454 }
455 TOKEN = dst;
456 }
457 }
458
459 ChatWindow Create(String context, String key) {
460 ChatWindow w = new ChatWindow(this, context, key);
461 windows.put(key, w);
462
463 Character qk = (Character) windowQuickKeys.get(key);
464 if(qk != null) w.SetQuickKey(qk.charValue());
465
466 w.next = first;
467 w.prev = first.prev;
468
469 first.prev.next = w;
470 first.prev = w;
471
472 return w;
473 }
474
475 boolean Select(String context, boolean create) {
476 ChatWindow w;
477 String key = context.toLowerCase();
478
479 synchronized (lock) {
480 w = (ChatWindow) windows.get(key);
481 if(w != null){
482 w.show();
483 lastWindowSelected = context;
484 return true;
485 } else {
486 if(create){
487 w = Create(context, key);
488 w.show();
489 lastWindowSelected = context;
490 return true;
491 }
492 }
493 }
494 return false;
495 }
496
497 void MSG(String context, String text) {
498 ChatWindow w;
499 String key = context.toLowerCase();
500
501 synchronized (lock) {
502 w = (ChatWindow) windows.get(key);
503 if (w == null) {
504 w = Create(context, key);
505 if (!app.fgapp && app.doMarquee) {
506 String msg = text;
507 NotificationManager.marqueeAlertNotify(new MarqueeAlert(msg, app.bm_notify,1));
508 }
509 app.Notify();
510 }
511 if(text != null) w.MSG(text);
512 }
513 }
514
515 void CLR(String context) {
516 ChatWindow w;
517 String key = context.toLowerCase();
518
519 synchronized (lock) {
520 w = (ChatWindow) windows.get(key);
521 if(w != null){
522 w.CLR();
523 }
524 }
525 }
526
527 void URL(String context, String src, String text)
528 {
529 ChatWindow w;
530 String key = context.toLowerCase();
531
532 synchronized (lock) {
533 w = (ChatWindow) windows.get(key);
534 if( w == null ){
535 w = Create(context, key);
536 }
537 if( text != null )
538 {
539 String URL, Name, Category;
540 int splitLoc;
541
542 splitLoc = text.indexOf(' ');
543
544 if( -1 == splitLoc )
545 {
546 URL = text;
547 Name = text;
548 Category = src;
549 }
550 else
551 {
552 URL = text.substring(0,splitLoc);
553 Name = text.substring(splitLoc+1);
554 Category = src;
555 }
556 w.URL(URL,Category,Name);
557 }
558 }
559 }
560
561 void Write(String tag, String dst, String txt) throws IOException {
562 String mark = ":";
563 String line = TX_SERIAL++ + mark + tag + mark + dst + mark + txt + "\n";
564
565 if(state == ONLINE){
566 byte[] bytes = line.getBytes();
567
568 synchronized (out_lock) {
569 try {
570 out.write(bytes, 0, bytes.length);
571 } catch (IOException io) {
572 s.close();
573 }
574 }
575 }
576 }
577
578 private void bindQuickKey(String context, char quickKey) {
579 String quickKeyWindow = context.toLowerCase();
580 Character qkChar = new Character(quickKey);
581 synchronized (lock) {
582 String previouslyBoundWindow = (String) quickKeyWindows.get(qkChar);
583 if (previouslyBoundWindow != null) {
584 windowQuickKeys.remove(previouslyBoundWindow);
585 ChatWindow cw = (ChatWindow) windows.get(previouslyBoundWindow);
586 if (cw != null) cw.SetQuickKey('\0');
587 }
588 quickKeyWindows.put(qkChar, quickKeyWindow);
589 windowQuickKeys.put(quickKeyWindow, qkChar);
590 ChatWindow cw = (ChatWindow) windows.get(quickKeyWindow);
591 if (cw != null) cw.SetQuickKey(quickKey);
592 }
593 }
594
595 boolean saveQuickKey(String context, char quickKey) {
596 if (!Character.isLetterOrDigit(quickKey))
597 return false;
598 else {
599 quickKey = Shortcut.validateShortcut(quickKey);
600 bindQuickKey(context, quickKey);
601 try {
602 Write("save", "quick/" + quickKey, context);
603 } catch (Exception e) {
604 // ignore save error
605 }
606 return true;
607 }
608 }
609
610 /* always called from the ui thread */
611 void MakeActive(ChatWindow cw) {
612 if(active != null) {
613 active.active = false;
614 }
615 active = cw;
616 if(cw != null){
617 cw.active = true;
618 cw.mailbox = false;
619 }
620 UpdateAlerts();
621 }
622
623 void UpdateAlerts() {
624 boolean any = false;
625 StringBuffer sb = new StringBuffer(64);
626
627 alerts.clear();
628
629 synchronized(lock){
630 ChatWindow w = first;
631 do {
632 if(w.mailbox) {
633 any = true;
634 if (w.quickkey != '\0'){
635 alerts.put(new Character(Character.toUpperCase(w.quickkey)), w);
636 } else {
637 alerts.put(UNBOUND_QUICKKEY, w);
638 }
639 }
640 w = w.next;
641 } while(w != first);
642 }
643 if(active != null) active.invalidate();
644 if(any) app.Notify();
645 }
646
647
648 String HOST = "";
649 int PORT = 3000;
650 String USER = "";
651 String PASSWD = "";
652
653 String TOKEN = "";
654 String RX_SERIAL = "0";
655 long TX_SERIAL = 0;
656
657 Socket s;
658 InputStream in;
659 OutputStream out;
660 byte in_buffer[];
661 Object out_lock;
662
663 Hashtable windows;
664 LoginWindow login;
665
666 static final int OFFLINE = 1;
667 static final int ONLINE = 2;
668 static final int CONNECTING = 3;
669 static final int DISCONNECT = 4;
670
671 static final Character UNBOUND_QUICKKEY = new Character('?');
672
673 Cognet app;
674 int state;
675
676 Hashtable windowQuickKeys = new Hashtable();
677 Hashtable quickKeyWindows = new Hashtable();
678 Hashtable alerts = new Hashtable();
679 ChatWindow first;
680 ChatWindow active;
681
682 boolean isSuspended;
683
684 String lastWindowSelected;
685
686 Object lock = new Object();
687}
Note: See TracBrowser for help on using the repository browser.