/* * AwtSession.java * * Copyright (c) 2010 VDP <vdp DOT kindle AT gmail.com>. * * This file is part of MidpSSH. * * MidpSSH is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MidpSSH is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MidpSSH. If not, see <http ://www.gnu.org/licenses/>. */ package awt; import app.session.SessionIOHandler; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import org.apache.log4j.Appender; import org.apache.log4j.Logger; import terminal.VT320; /** * Reimplements {@link app.session.Session} using J2SE(not J2ME) means. * * @author VDP <vdp DOT kindle AT gmail.com> */ public abstract class AwtSession { /** * After this count of millisends without communication on connection we will * send something just to keep connection alive */ public static final int keepAliveTime = 1000 * 60 * 1; // 1 minute protected VT320 emulation; protected SessionIOHandler filter; //private AwtTerminal terminal; private boolean disconnecting, erroredDisconnect; private boolean forcedDisconnect; private boolean pollingIO; /** * Holds the socket connetion object (from the Generic Connection Framework) * that is the basis of this connection. */ private Socket connection; /** * Holds the InputStream associated with the socket. */ private InputStream in; /** * Holds the OutputStream associated with the socket. */ private OutputStream out; //private SessionSpec spec; protected String host, username, password; protected int port; protected boolean usePublicKey = false; private Thread reader, writer; /** * We will collect here data for writing. Data will be sent when nothing is * in input stream, otherwise midlet hung up. * * @see #run */ private byte[] outputBuffer = new byte[16]; // this will grow if needed private final Object writerMutex = new Object(); /** * Number of bytes to be written, from output array, because it has fixed * lenght. */ private int outputCount = 0; private int bytesWritten = 0, bytesRead = 0; private Logger log; public AwtSession() { log = Logger.getLogger(AwtSession.class.getName()); emulation = new VT320() { public void sendData(byte[] b, int offset, int length) throws IOException { filter.handleSendData(b, offset, length); } public void beep() { //MainMenu.getDisplay().vibrate(200); } }; pollingIO = true; //Settings.pollingIO; //terminal = new AwtTerminal(emulation, this); reader = new Reader(); writer = new Writer(); } protected void connect(String host, int port , String username, String password, SessionIOHandler filter) { this.host = host; this.port = port; this.username = username; this.password = password; this.filter = filter; writer.start(); } protected abstract int defaultPort(); /* * (non-Javadoc) * * @see telnet.TelnetIOListener#receiveData(byte[]) */ protected void receiveData(byte[] buffer, int offset, int length) throws IOException { if (buffer != null && length > 0) { try { emulation.putString(new String(buffer, offset, length)); } catch (Exception e) { } } } /* * (non-Javadoc) * * @see telnet.TelnetIOListener#sendData(byte[]) */ protected void sendData(byte[] b, int offset, int length) throws IOException { synchronized (writerMutex) { if (outputCount + length > outputBuffer.length) { byte[] newOutput = new byte[outputCount + length]; System.arraycopy(outputBuffer, 0, newOutput, 0, outputCount); outputBuffer = newOutput; } System.arraycopy(b, offset, outputBuffer, outputCount, length); outputCount += length; writerMutex.notify(); } } public void typeString(String str) { emulation.stringTyped(str); } public void typeChar(char c, int modifiers) { emulation.keyTyped(0, c, modifiers); } public void typeKey(int keyCode, int modifiers) { emulation.keyPressed(keyCode, modifiers); } private boolean connect() throws IOException { emulation.putString("Connecting to " + host + ':' + port + " ..."); connection = new Socket(host, port); //(StreamConnection) Connector.open(conn.toString(), Connector.READ_WRITE, false); log.debug("Socket timeout is " + connection.getSoTimeout()); connection.setSoTimeout(0); in = connection.getInputStream(); //openDataInputStream(); out = connection.getOutputStream(); //openDataOutputStream(); emulation.putString("OK\r\n"); return true; } /** * Continuously read from remote host and display the data on screen. */ private void read() throws IOException { byte[] buf; buf = new byte[512]; // try a smaller buffer, maybe works better on some phones int n = 0; while (n != -1) { bytesRead += n; filter.handleReceiveData(buf, 0, n); int a = in.available(); if (pollingIO) { while (a == 0 && !disconnecting) { try { Thread.sleep(100); } catch (InterruptedException e) { } a = in.available(); } } // Read at least 1 byte, and at most the number of bytes available n = in.read(buf, 0, Math.max(1, Math.min(a, buf.length))); } } private void write() throws IOException { final byte[] empty = new byte[0]; while (!disconnecting) { synchronized (writerMutex) { while (outputCount == 0 && !disconnecting) { try { writerMutex.wait(keepAliveTime); } catch (InterruptedException e) { } if (outputCount == 0 && !disconnecting) { // No data to send after timeout so send an empty array through the filter which will trigger the // sending of a NOOP (see TelnetSession and SshSession) - this has the effect of a keepalive //emulation.putString( "NOOP\r\n" ); filter.handleSendData(empty, 0, 0); } } if (!disconnecting) { bytesWritten += outputCount; out.write(outputBuffer, 0, outputCount); out.flush(); outputCount = 0; } } } } private void handleException(String where, Throwable t) { if (!disconnecting) { log.error("Exception in " + where + ": " + t.toString()); StackTraceElement[] stack = t.getStackTrace(); for (int i = 0; i < stack.length; i++) log.error(stack[i].toString()); } else { log.debug("Exception in " + where + ": " + t.getMessage()); } } public VT320 getEmulation() { return emulation; } /** * @return Returns the terminal. */ // public AwtTerminal getTerminal() { // return terminal; // } /* * (non-Javadoc) * * @see telnet.Session#disconnect() */ public void disconnect() { forcedDisconnect = true; doDisconnect(); } private void doDisconnect() { if (!disconnecting) { synchronized (writerMutex) { disconnecting = true; try { if (in != null) { in.close(); } if (out != null) { out.close(); } if (connection != null) { connection.close(); } } catch (IOException e) { handleException("Disconnect", e); } writerMutex.notify(); } } } private String bytesToString(int bytes) { if (bytes < 1024) { return bytes + " bytes"; } else if (bytes < 1024 * 1024) { return to2dp(bytes * 100 / 1024) + " KB"; } else { return to2dp(bytes * 100 / (1024 * 1024)) + " MB"; } } private String to2dp(int i) { String str = "" + i; return str.substring(0, str.length() - 2) + "." + str.substring(str.length() - 2); } private class Reader extends Thread { public void run() { try { log.debug("Reader thread started"); emulation.putString("Reader started.\r\n"); read(); doDisconnect(); log.debug("Reader thread exited normally"); } catch (Exception e) { handleException("Reader", e); erroredDisconnect = true; //doDisconnect(); } } } private class Writer extends Thread { public void run() { try { log.debug("Writer thread started"); connect(); reader.start(); emulation.putString("Writer started \r\n"); write(); doDisconnect(); log.debug("Writer thread exited normally"); } catch (Exception e) { handleException("Writer", e); erroredDisconnect = true; //doDisconnect(); } } } }