/* * cognet chat app * * Copyright 2003, Brian Swetland * See LICENSE for redistribution terms * */ package org.twodot.cognet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Hashtable; import danger.ui.MarqueeAlert; import danger.ui.Menu; import danger.ui.MenuItem; import danger.ui.NotificationManager; import danger.ui.Shortcut; public class Engine implements Resources, Runnable { public Engine(Cognet theapp) { app = theapp; initSettings(); out_lock = new Object(); in_buffer = new byte[512]; state = OFFLINE; login = new LoginWindow(this); first = new ChatWindow(this,"Cognet Server","%server"); first.next = first; first.prev = first; windows = new Hashtable(); windows.put("%server",first); login.show(); } void initSettings() { String settingsHost, settingsUser, settingsPasswd; int settingsPort; //get Host settingsHost = app.cogSettings.getStringValue("host"); if (settingsHost != null) HOST = settingsHost; try { //get port PORT = app.cogSettings.getIntValue("port"); //get user settingsUser = app.cogSettings.getStringValue("user"); if (settingsUser != null) USER = settingsUser; //get pass settingsPasswd = app.cogSettings.getStringValue("password"); if (settingsPasswd != null) PASSWD = settingsPasswd; boolean settingsSmiley, settingsMarquee; boolean settingsActivity, settingsAutoConnect; //get prefs settingsSmiley = 1 == app.cogSettings.getIntValue("doSmileys"); settingsMarquee = 1 == app.cogSettings.getIntValue("doMarquee"); settingsActivity = 1 == app.cogSettings.getIntValue("doActivity"); settingsAutoConnect = 1 == app.cogSettings.getIntValue("doAutoConnect"); app.doSmileys = settingsSmiley; app.doMarquee = settingsMarquee; app.doActivity = settingsActivity; app.doAutoConnect = settingsAutoConnect; } catch (Throwable e) {} } void AddWindows(Menu a) { synchronized(lock) { ChatWindow w = first; do { if(!w.special){ Character c = (Character) windowQuickKeys.get(w.key); MenuItem mi = a.addItem(w.context, Cognet.kGotoWindow, 0, w.key, app); mi.setIcon(w.mailbox ? app.bm_mail : app.bm_plain); if(c != null) mi.setShortcut(c.charValue()); mi.setChecked(w == active); } w = w.next; } while(w != first); w = first; do { if(w.special){ Character c = (Character) windowQuickKeys.get(w.key); MenuItem mi = a.addItem(w.context, Cognet.kGotoWindow, 0, w.key, app); mi.setIcon(w.mailbox ? app.bm_mail : app.bm_plain); if(c != null) mi.setShortcut(c.charValue()); mi.setChecked(w == active); } w = w.next; } while(w != first); } } ChatWindow NextMailbox(ChatWindow start) { synchronized(lock) { ChatWindow w = start.next; while(w != start){ if(w.mailbox) return w; w = w.next; } } return null; } void sendSuspend() { try { //Write("suspend","%server",""); isSuspended = true; } catch( Exception e ) {}; } void sendResume() { if( isSuspended ) { try { //Write("resume","%server",""); } catch( Exception e ) {}; } isSuspended = false; } void nuke() { ChatWindow w,n; synchronized(lock){ w = first; w.CLR(); w = w.next; while(w != first){ w.hide(); w = w.next; } first.next = first; first.prev = first; windows = new Hashtable(); windows.put("%server",first); } RX_SERIAL="0"; } public void run() { System.err.println("cognet: starting..."); login.SendEvent(5039); // if we don't have a last window selected // or the select fails to find a window, then show // the server window. if (lastWindowSelected == null || !Select(lastWindowSelected,false)) Select("%server",true); try { synchronized(out_lock) { s = new Socket(HOST, PORT); System.err.println("cognet: Connected to "+HOST+":"+PORT); in = s.getInputStream(); out = s.getOutputStream(); } System.err.println("cognet: Signing On"); state = ONLINE; SignOn(); System.err.println("cognet: IO Loop Starting"); login.SendEvent(5039); HandleIO(); } catch (Throwable e){ System.err.println("cognet: whoops: " + e); e.printStackTrace(); try { s.close(); } catch (Exception x){ } } state = OFFLINE; login.SendEvent(5039); login.show(); System.err.println("cognet: Network Thread Exiting"); } void login() { synchronized(lock){ if(state == OFFLINE){ state = CONNECTING; try { (new Thread(this,"cognet: engine")).start(); } catch (Throwable t){ state = OFFLINE; System.err.println("cognet: can't start engine thread"); } } } } void SignOn() throws IOException { Write("serial",RX_SERIAL,TOKEN); Write("test",USER,PASSWD); /* if( isSuspended ) Write("suspend","%server",""); else Write("resume","%server",""); */ } void logout() { System.err.println("cognet: logout"); state = DISCONNECT; PASSWD=""; try { s.close(); } catch (Throwable t){ } try { in.close(); } catch (Throwable t){ } try { out.close(); } catch (Throwable t){ } System.err.println("cognet: logged out"); } void Close(String context) { String key = context.toLowerCase(); if(key.equals("%server")) return; ChatWindow w = null; ChatWindow nextup = null; synchronized(lock){ w = (ChatWindow) windows.remove(key); if( w == null ) return; nextup = w.next; if(w != null){ w.Remove(); } } if(w != null) { nextup.show(); try { Write("clear",context,""); } catch (IOException iox) { } } } void Command(String context, String str) { int l = str.length(); if((l > 0) && (str.charAt(l - 1) < ' ')){ /* Why textfield likes to tack a return on the end, I'll never know... */ str = str.substring(0, l - 1); } try { if(str.charAt(0) == '/'){ if(str.startsWith("/clear")){ Write("clear",context,""); return; } if(str.startsWith("/bind ")){ char c = str.charAt(6); System.err.println("cognet: bind '"+c+"' to '"+context+"'"); saveQuickKey(context, c); return; } if(str.startsWith("/win ")){ Select(str.substring(5), false); return; } if(str.startsWith("/zap")) { logout(); return; } // if(str.startsWith("/query ")) { // Select(str.substring(7),true); // return; // } } /* if (str.startsWith("/font ")) { String fontName = str.substring(6); ChatWindow w = (ChatWindow)windows.get(context.toLowerCase()); if (w != null) { Font font = Font.findFont(fontName); w.setFont(font); MSG(context, "<*> Font set to " + fontName); } else { MSG(context, "<*> Unknown font."); } } */ /* all other commands are handled on the proxy */ if( str.startsWith("/") ){ int n = str.indexOf(' '); if(n != -1){ Write(str.substring(1,n), context, str.substring(n+1)); } else { Write(str.substring(1), context, ""); } } else { Write("send",context,str); } return; } catch (Exception e) { } } void HandleIO() throws IOException { byte[] b = in_buffer; int i, j, n, p, x; byte mark = (byte) ':'; for(;;) { /* read one line from the server */ if(state != ONLINE){ throw new IOException("not online"); } p = 0; for(;;) { x = in.read(); if(x < 0) throw new IOException("EOF"); if((x == 10) || (x == 13)){ if(p == 0) { continue; } else { break; } } /* truncate lines that are too long */ if(p == 510) { continue; } else { b[p++] = (byte) x; } } b[p] = 0; // System.err.println("LINE: " + new String(b,0,p)); String sn, tag, src, dst, txt; sn = null; tag = null; src = null; i = 0; while(i < p){ if(b[i] == mark) { sn = new String(b,0,i); break; } i++; } j = i = i + 1; while(i < p){ if(b[i] == mark) { tag = new String(b,j,i-j); break; } i++; } j = i = i + 1; while(i < p){ if(b[i] == mark) { src = new String(b,j,i-j); break; } i++; } j = i = i + 1; while(i < p){ if(b[i] == mark) { dst = new String(b,j,i-j); i++; txt = new String(b,i,p-i); if(sn.length() > 0) RX_SERIAL = sn; // System.err.println("["+sn+":"+tag+":"+src+":"+dst+":"+txt+"]"); Process(tag,src,dst,txt); break; } i++; } } } void Process(String tag, String src, String dst, String txt) { //System.err.println(tag+":"+src+":"+dst+":"+txt); if(tag.equals("fmsg")){ MSG(dst, txt); } else if(tag.equals("dmsg")){ MSG(dst,"<"+src+"> "+txt); } else if(tag.equals("dmsg/act")){ MSG(dst, "* "+src+" "+txt); } else if(tag.equals("smsg")){ MSG(src,"<"+src+"> "+txt); } else if(tag.equals("smsg/act")){ MSG(src, "* "+src+" "+txt); } else if(tag.equals("show")){ Select(dst, true); } else if(tag.equals("info")){ MSG(dst, "<-> " + txt); } else if(tag.equals("repl")){ MSG(dst, "<::::> " + txt); } else if(tag.equals("fail")){ MSG(dst, "<::::> " + txt); } else if(tag.equals("clear")){ CLR(dst); } else if(tag.equals("kill")){ Close(dst); } else if(tag.equals("url")){ URL(dst, src, txt); } else if(tag.equals("set")){ if (dst.startsWith("quick/")) bindQuickKey(txt, dst.charAt(6)); } else if(tag.equals("sync")){ System.err.println("Got sync token '"+dst+"'"); System.err.println("Have token '"+TOKEN+"'"); if(!TOKEN.equals(dst)){ System.err.println("Token mismatch. Clearing"); nuke(); } TOKEN = dst; } } ChatWindow Create(String context, String key) { ChatWindow w = new ChatWindow(this, context, key); windows.put(key, w); Character qk = (Character) windowQuickKeys.get(key); if(qk != null) w.SetQuickKey(qk.charValue()); w.next = first; w.prev = first.prev; first.prev.next = w; first.prev = w; return w; } boolean Select(String context, boolean create) { ChatWindow w; String key = context.toLowerCase(); synchronized (lock) { w = (ChatWindow) windows.get(key); if(w != null){ w.show(); lastWindowSelected = context; return true; } else { if(create){ w = Create(context, key); w.show(); lastWindowSelected = context; return true; } } } return false; } void MSG(String context, String text) { ChatWindow w; String key = context.toLowerCase(); synchronized (lock) { w = (ChatWindow) windows.get(key); if (w == null) { w = Create(context, key); if (!app.fgapp && app.doMarquee) { String msg = text; NotificationManager.marqueeAlertNotify(new MarqueeAlert(msg, app.bm_notify,1)); } app.Notify(); } if(text != null) w.MSG(text); } } void CLR(String context) { ChatWindow w; String key = context.toLowerCase(); synchronized (lock) { w = (ChatWindow) windows.get(key); if(w != null){ w.CLR(); } } } void URL(String context, String src, String text) { ChatWindow w; String key = context.toLowerCase(); synchronized (lock) { w = (ChatWindow) windows.get(key); if( w == null ){ w = Create(context, key); } if( text != null ) { String URL, Name, Category; int splitLoc; splitLoc = text.indexOf(' '); if( -1 == splitLoc ) { URL = text; Name = text; Category = src; } else { URL = text.substring(0,splitLoc); Name = text.substring(splitLoc+1); Category = src; } w.URL(URL,Category,Name); } } } void Write(String tag, String dst, String txt) throws IOException { String mark = ":"; String line = TX_SERIAL++ + mark + tag + mark + dst + mark + txt + "\n"; if(state == ONLINE){ byte[] bytes = line.getBytes(); synchronized (out_lock) { try { out.write(bytes, 0, bytes.length); } catch (IOException io) { s.close(); } } } } private void bindQuickKey(String context, char quickKey) { String quickKeyWindow = context.toLowerCase(); Character qkChar = new Character(quickKey); synchronized (lock) { String previouslyBoundWindow = (String) quickKeyWindows.get(qkChar); if (previouslyBoundWindow != null) { windowQuickKeys.remove(previouslyBoundWindow); ChatWindow cw = (ChatWindow) windows.get(previouslyBoundWindow); if (cw != null) cw.SetQuickKey('\0'); } quickKeyWindows.put(qkChar, quickKeyWindow); windowQuickKeys.put(quickKeyWindow, qkChar); ChatWindow cw = (ChatWindow) windows.get(quickKeyWindow); if (cw != null) cw.SetQuickKey(quickKey); } } boolean saveQuickKey(String context, char quickKey) { if (!Character.isLetterOrDigit(quickKey)) return false; else { quickKey = Shortcut.validateShortcut(quickKey); bindQuickKey(context, quickKey); try { Write("save", "quick/" + quickKey, context); } catch (Exception e) { // ignore save error } return true; } } /* always called from the ui thread */ void MakeActive(ChatWindow cw) { if(active != null) { active.active = false; } active = cw; if(cw != null){ cw.active = true; cw.mailbox = false; } UpdateAlerts(); } void UpdateAlerts() { boolean any = false; StringBuffer sb = new StringBuffer(64); alerts.clear(); synchronized(lock){ ChatWindow w = first; do { if(w.mailbox) { any = true; if (w.quickkey != '\0'){ alerts.put(new Character(Character.toUpperCase(w.quickkey)), w); } else { alerts.put(UNBOUND_QUICKKEY, w); } } w = w.next; } while(w != first); } if(active != null) active.invalidate(); if(any) app.Notify(); } String HOST = ""; int PORT = 3000; String USER = ""; String PASSWD = ""; String TOKEN = ""; String RX_SERIAL = "0"; long TX_SERIAL = 0; Socket s; InputStream in; OutputStream out; byte in_buffer[]; Object out_lock; Hashtable windows; LoginWindow login; static final int OFFLINE = 1; static final int ONLINE = 2; static final int CONNECTING = 3; static final int DISCONNECT = 4; static final Character UNBOUND_QUICKKEY = new Character('?'); Cognet app; int state; Hashtable windowQuickKeys = new Hashtable(); Hashtable quickKeyWindows = new Hashtable(); Hashtable alerts = new Hashtable(); ChatWindow first; ChatWindow active; boolean isSuspended; String lastWindowSelected; Object lock = new Object(); }