From 43cf095a13e963202287b5a6322aa96a2fbcff51 Mon Sep 17 00:00:00 2001 From: Kelven Yang Date: Wed, 7 Mar 2012 16:56:06 -0800 Subject: [PATCH] Console proxy refactoring incremental check-in - pluggable framework for VNC and RDP engine --- .../com/cloud/consoleproxy/ConsoleProxy.java | 3 +- .../consoleproxy/ConsoleProxyClient.java | 25 + .../consoleproxy/ConsoleProxyClientBase.java | 409 ++++++++++++++++ .../ConsoleProxyClientStatsCollector.java | 68 +++ .../consoleproxy/ConsoleProxyGCThread.java | 87 ++++ .../consoleproxy/ConsoleProxyVncClient.java | 436 +----------------- .../consoleproxy/vnc/BufferedImageCanvas.java | 4 +- .../consoleproxy/vnc/FrameBufferCanvas.java | 10 + ...ner.java => FrameBufferEventListener.java} | 2 +- .../com/cloud/consoleproxy/vnc/VncClient.java | 13 +- .../vnc/VncServerPacketReceiver.java | 8 +- .../server/FramebufferUpdatePacket.java | 6 +- 12 files changed, 645 insertions(+), 426 deletions(-) create mode 100644 console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java create mode 100644 console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java create mode 100644 console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java create mode 100644 console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java create mode 100644 console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java rename console-proxy/src/com/cloud/consoleproxy/vnc/{VncClientListener.java => FrameBufferEventListener.java} (73%) diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java index 99f60a549b4..b05badc917e 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java @@ -629,8 +629,7 @@ public class ConsoleProxy { viewer = connMap.get(key); } - long seconds_unused = - (System.currentTimeMillis() - viewer.lastUsedTime) / 1000; + long seconds_unused = (System.currentTimeMillis() - viewer.lastUsedTime) / 1000; if (seconds_unused > viewerLinger / 2 && viewer.clientStream != null) { s_logger.info("Pinging client for " + viewer + diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java new file mode 100644 index 00000000000..69e964ed51e --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClient.java @@ -0,0 +1,25 @@ +package com.cloud.consoleproxy; + +/** + * @author Kelven Yang + * ConsoleProxyClient defines an standard interface that a console client should implement, + * example of such console clients could be a VNC client or a RDP client + * + * ConsoleProxyClient maintains a session towards the target host, it glues the session + * to a AJAX frontend viewer + */ +public interface ConsoleProxyClient { + int getClientId(); + + long getClientCreateTime(); + long getClientLastFrontEndActivityTime(); + + String getClientHostAddress(); + int getClientHostPort(); + String getClientHostPassword(); + String getClientTag(); + + void initClient(String clientHostAddress, int clientHostPort, + String clientHostPassword, String clientTag); + void closeClient(); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java new file mode 100644 index 00000000000..53315a87f8d --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientBase.java @@ -0,0 +1,409 @@ +package com.cloud.consoleproxy; + +import java.awt.Rectangle; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.cloud.console.TileInfo; +import com.cloud.console.TileTracker; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; +import com.cloud.consoleproxy.vnc.FrameBufferEventListener; + +/** + * + * @author Kelven Yang + * + * ConsoleProxyClientBase implements a base console client class that can be extended to realize + * an instance of specialized console protocol implementation, such as VNC or RDP + * + * It mainly implements the features needed by front-end AJAX viewer + * + */ +public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, FrameBufferEventListener { + private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientBase.class); + + private static int s_nextClientId = 0; + protected int clientId = getNextClientId(); + + protected long ajaxSessionId = 0; + + protected boolean dirtyFlag = false; + protected Object tileDirtyEvent = new Object(); + protected TileTracker tracker; + protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2); + + protected String host; + protected int port; + protected String passwordParam; + protected String tag = ""; + protected long createTime = System.currentTimeMillis(); + protected long lastFrontEndActivityTime = System.currentTimeMillis(); + + protected boolean framebufferResized = false; + protected int resizedFramebufferWidth; + protected int resizedFramebufferHeight; + + public ConsoleProxyClientBase() { + } + + // + // interface ConsoleProxyClient + // + @Override + public int getClientId() { + return clientId; + } + + @Override + public long getClientCreateTime() { + return createTime; + } + + @Override + public long getClientLastFrontEndActivityTime() { + return lastFrontEndActivityTime; + } + + @Override + public String getClientHostAddress() { + return host; + } + + @Override + public int getClientHostPort() { + return port; + } + + @Override + public String getClientHostPassword() { + return passwordParam; + } + + @Override + public String getClientTag() { + return tag; + } + + @Override + public abstract void initClient(String clientHostAddress, int clientHostPort, + String clientHostPassword, String clientTag); + + @Override + public abstract void closeClient(); + + // + // interface FrameBufferEventListener + // + @Override + public void onFramebufferSizeChange(int w, int h) { + tracker.resize(w, h); + + synchronized(this) { + framebufferResized = true; + resizedFramebufferWidth = w; + resizedFramebufferHeight = h; + } + + signalTileDirtyEvent(); + } + + @Override + public void onFramebufferUpdate(int x, int y, int w, int h) { + if(s_logger.isTraceEnabled()) + s_logger.trace("Frame buffer update {" + x + "," + y + "," + w + "," + h + "}"); + tracker.invalidate(new Rectangle(x, y, w, h)); + + signalTileDirtyEvent(); + } + + // + // AJAX Image manipulation + // + public byte[] getFrameBufferJpeg() { + FrameBufferCanvas canvas = getFrameBufferCavas(); + if(canvas != null) + return canvas.getFrameBufferJpeg(); + + return null; + } + + public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight) { + FrameBufferCanvas canvas = getFrameBufferCavas(); + if(canvas != null) + return canvas.getTilesMergedJpeg(tileList, tileWidth, tileHeight); + return null; + } + + private String prepareAjaxImage(List tiles, boolean init) { + byte[] imgBits; + if(init) + imgBits = getFrameBufferJpeg(); + else + imgBits = getTilesMergedJpeg(tiles, tracker.getTileWidth(), tracker.getTileHeight()); + + if(imgBits == null) { + s_logger.warn("Unable to generate jpeg image"); + } else { + if(s_logger.isTraceEnabled()) + s_logger.trace("Generated jpeg image size: " + imgBits.length); + } + + int key = ajaxImageCache.putImage(imgBits); + StringBuffer sb = new StringBuffer("/ajaximg?host="); + sb.append(host).append("&port=").append(port).append("&sid=").append(passwordParam); + sb.append("&key=").append(key).append("&ts=").append(System.currentTimeMillis()); + return sb.toString(); + } + + private String prepareAjaxSession(boolean init) { + if(init) { + synchronized(this) { + ajaxSessionId++; + } + } + + StringBuffer sb = new StringBuffer(); + + sb.append("/ajax?host=").append(host).append("&port=").append(port); + sb.append("&sid=").append(passwordParam).append("&sess=").append(ajaxSessionId); + return sb.toString(); + } + + public String onAjaxClientKickoff() { + return "onKickoff();"; + } + + private boolean waitForViewerReady() { + long startTick = System.currentTimeMillis(); + while(System.currentTimeMillis() - startTick < 5000) { + if(getFrameBufferCavas() != null) + return true; + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + return false; + } + + private String onAjaxClientConnectFailed() { + return "

" + + "Unable to start console session as connection is refused by the machine you are accessing" + + "

"; + } + + public String onAjaxClientStart(String title, List languages, String guest) { + if(!waitForViewerReady()) + return onAjaxClientConnectFailed(); + + synchronized(this) { + ajaxSessionId++; + framebufferResized = false; + } + + int tileWidth = tracker.getTileWidth(); + int tileHeight = tracker.getTileHeight(); + int width = tracker.getTrackWidth(); + int height = tracker.getTrackHeight(); + + if(s_logger.isTraceEnabled()) + s_logger.trace("Ajax client start, frame buffer w: " + width + ", " + height); + + int retry = 0; + tracker.initCoverageTest(); + while(!tracker.hasFullCoverage() && retry < 10) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + retry++; + } + + List tiles = tracker.scan(true); + String imgUrl = prepareAjaxImage(tiles, true); + String updateUrl = prepareAjaxSession(true); + + StringBuffer sbTileSequence = new StringBuffer(); + int i = 0; + for(TileInfo tile : tiles) { + sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); + if(i < tiles.size() - 1) + sbTileSequence.append(","); + + i++; + } + + return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl, + updateUrl, width, height, tileWidth, tileHeight, title, + ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW, + languages, guest); + } + + private String getAjaxViewerPageContent(String tileSequence, String imgUrl, String updateUrl, int width, + int height, int tileWidth, int tileHeight, String title, boolean rawKeyboard, List languages, String guest) { + + StringBuffer sbLanguages = new StringBuffer(""); + if(languages != null) { + for(String lang : languages) { + if(sbLanguages.length() > 0) { + sbLanguages.append(","); + } + sbLanguages.append(lang); + } + } + + String[] content = new String[] { + "", + "", + "", + "", + "", + "", + "", + "", + "" + title + "", + "", + "", + "
", + "", + "", + "
", + "
", + "", + "", + "" + }; + + StringBuffer sb = new StringBuffer(); + for(int i = 0; i < content.length; i++) + sb.append(content[i]); + + return sb.toString(); + } + + public String onAjaxClientDisconnected() { + return "onDisconnect();"; + } + + public String onAjaxClientUpdate() { + if(!waitForViewerReady()) + return onAjaxClientDisconnected(); + + synchronized(tileDirtyEvent) { + if(!dirtyFlag) { + try { + tileDirtyEvent.wait(3000); + } catch(InterruptedException e) { + } + } + } + + boolean doResize = false; + synchronized(this) { + if(framebufferResized) { + framebufferResized = false; + doResize = true; + } + } + + List tiles; + + if(doResize) + tiles = tracker.scan(true); + else + tiles = tracker.scan(false); + dirtyFlag = false; + + String imgUrl = prepareAjaxImage(tiles, false); + StringBuffer sbTileSequence = new StringBuffer(); + int i = 0; + for(TileInfo tile : tiles) { + sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); + if(i < tiles.size() - 1) + sbTileSequence.append(","); + + i++; + } + + return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize, + resizedFramebufferWidth, resizedFramebufferHeight, + tracker.getTileWidth(), tracker.getTileHeight()); + } + + private String getAjaxViewerUpdatePageContent(String tileSequence, String imgUrl, boolean resized, int width, + int height, int tileWidth, int tileHeight) { + + String[] content = new String[] { + "tileMap = [ " + tileSequence + " ];", + resized ? "ajaxViewer.resize('main_panel', " + width + ", " + height + " , " + tileWidth + ", " + tileHeight + ");" : "", + "ajaxViewer.refresh('" + imgUrl + "', tileMap, false);" + }; + + StringBuffer sb = new StringBuffer(); + for(int i = 0; i < content.length; i++) + sb.append(content[i]); + + return sb.toString(); + } + + // + // Helpers + // + private synchronized static int getNextClientId() { + return ++s_nextClientId; + } + + public long getAjaxSessionId() { + return this.ajaxSessionId; + } + + public AjaxFIFOImageCache getAjaxImageCache() { + return ajaxImageCache; + } + + private void signalTileDirtyEvent() { + synchronized(tileDirtyEvent) { + dirtyFlag = true; + tileDirtyEvent.notifyAll(); + } + } + + public void updateFrontEndActivityTime() { + lastFrontEndActivityTime = System.currentTimeMillis(); + } + + protected abstract FrameBufferCanvas getFrameBufferCavas(); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java new file mode 100644 index 00000000000..2fc181368e1 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java @@ -0,0 +1,68 @@ +package com.cloud.consoleproxy; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Hashtable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * + * @author Kelven Yang + * ConsoleProxyClientStatsCollector collects client stats for console proxy agent to report + * to external management software + */ +public class ConsoleProxyClientStatsCollector { + + ArrayList connections; + + public ConsoleProxyClientStatsCollector() { + } + + public ConsoleProxyClientStatsCollector(Hashtable connMap) { + setConnections(connMap); + } + + public String getStatsReport() { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + return gson.toJson(this); + } + + private void setConnections(Hashtable connMap) { + + ArrayList conns = new ArrayList(); + Enumeration e = connMap.keys(); + while (e.hasMoreElements()) { + synchronized (connMap) { + String key = e.nextElement(); + ConsoleProxyClient client = connMap.get(key); + + ConsoleProxyConnection conn = new ConsoleProxyConnection(); + + conn.id = client.getClientId(); + conn.clientInfo = ""; + conn.host = client.getClientHostAddress(); + conn.port = client.getClientHostPort(); + conn.tag = client.getClientTag(); + conn.createTime = client.getClientCreateTime(); + conn.lastUsedTime = client.getClientLastFrontEndActivityTime(); + conns.add(conn); + } + } + connections = conns; + } + + public static class ConsoleProxyConnection { + public int id; + public String clientInfo; + public String host; + public int port; + public String tag; + public long createTime; + public long lastUsedTime; + + public ConsoleProxyConnection() { + } + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java new file mode 100644 index 00000000000..f42f688ea50 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyGCThread.java @@ -0,0 +1,87 @@ +package com.cloud.consoleproxy; + +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.apache.log4j.Logger; + +/** + * + * @author Kelven Yang + * ConsoleProxyGCThread does house-keeping work for the process, it helps cleanup log files, + * recycle idle client sessions without front-end activities and report client stats to external + * management software + */ +public class ConsoleProxyGCThread extends Thread { + private static final Logger s_logger = Logger.getLogger(ConsoleProxyGCThread.class); + + private final static int MAX_SESSION_IDLE_SECONDS = 180; + + private Hashtable connMap; + private long lastLogScan = 0; + + public ConsoleProxyGCThread(Hashtable connMap) { + this.connMap = connMap; + } + + private void cleanupLogging() { + if(lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) + return; + + lastLogScan = System.currentTimeMillis(); + + File logDir = new File("./logs"); + File files[] = logDir.listFiles(); + if(files != null) { + for(File file : files) { + if(System.currentTimeMillis() - file.lastModified() >= 86400000L) { + try { + file.delete(); + } catch(Throwable e) { + } + } + } + } + } + + @Override + public void run() { + while (true) { + cleanupLogging(); + + s_logger.info("connMap=" + connMap); + Enumeration e = connMap.keys(); + while (e.hasMoreElements()) { + String key; + ConsoleProxyClient client; + + synchronized (connMap) { + key = e.nextElement(); + client = connMap.get(key); + } + + long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000; + if (seconds_unused < MAX_SESSION_IDLE_SECONDS) { + continue; + } + + synchronized (connMap) { + connMap.remove(key); + } + + // close the server connection + s_logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds"); + client.closeClient(); + + // report load changes + String loadInfo = new ConsoleProxyClientStatsCollector(connMap).getStatsReport(); + ConsoleProxy.reportLoadInfo(loadInfo); + if(s_logger.isDebugEnabled()) + s_logger.debug("Report load change : " + loadInfo); + } + + try { Thread.sleep(30000); } catch (InterruptedException ex) {} + } + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java index 2c66ed091e2..310ee655085 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyVncClient.java @@ -1,426 +1,36 @@ package com.cloud.consoleproxy; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; - import org.apache.log4j.Logger; -import com.cloud.console.TileInfo; -import com.cloud.console.TileTracker; -import com.cloud.consoleproxy.vnc.VncClientListener; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; +import com.cloud.consoleproxy.vnc.VncClient; -public class ConsoleProxyVncClient implements VncClientListener { +/** + * + * @author Kelven Yang + * ConsoleProxyVncClient bridges a VNC engine with the front-end AJAX viewer + * + */ +public class ConsoleProxyVncClient extends ConsoleProxyClientBase { private static final Logger s_logger = Logger.getLogger(ConsoleProxyVncClient.class); - private TileTracker tracker; - - boolean dirtyFlag = false; - private Object tileDirtyEvent = new Object(); - private AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2); - - @Override - public void onFramebufferSizeChange(int w, int h) { + private VncClient client; + + public ConsoleProxyVncClient() { + } + + public void initClient(String clientHostAddress, int clientHostPort, + String clientHostPassword, String clientTag) { // TODO } - - @Override - public void onFramebufferUpdate(int x, int y, int w, int h) { - if(s_logger.isTraceEnabled()) - s_logger.trace("Frame buffer update {" + x + "," + y + "," + w + "," + h + "}"); - tracker.invalidate(new Rectangle(x, y, w, h)); - - signalTileDirtyEvent(); + + public void closeClient() { + // TODO } - private void signalTileDirtyEvent() { - synchronized(tileDirtyEvent) { - dirtyFlag = true; - tileDirtyEvent.notifyAll(); - } + protected FrameBufferCanvas getFrameBufferCavas() { + if(client != null) + return client.getFrameBufferCanvas(); + return null; } -/* - // - // AJAX Image manipulation - // - public void copyTile(Graphics2D g, int x, int y, Rectangle rc) { - if(vc != null && vc.memImage != null) { - synchronized(vc.memImage) { - g.drawImage(vc.memImage, x, y, x + rc.width, y + rc.height, - rc.x, rc.y, rc.x + rc.width, rc.y + rc.height, null); - } - } - } - - public byte[] getFrameBufferJpeg() { - int width = 800; - int height = 600; - if(vc != null) { - width = vc.scaledWidth; - height = vc.scaledHeight; - } - - if(s_logger.isTraceEnabled()) - s_logger.trace("getFrameBufferJpeg, w: " + width + ", h: " + height); - - BufferedImage bufferedImage = new BufferedImage(width, height, - BufferedImage.TYPE_3BYTE_BGR); - if(vc != null && vc.memImage != null) { - synchronized(vc.memImage) { - Graphics2D g = bufferedImage.createGraphics(); - g.drawImage(vc.memImage, 0, 0, width, height, 0, 0, width, height, null); - } - } - - byte[] imgBits = null; - try { - imgBits = jpegFromImage(bufferedImage); - } catch (IOException e) { - } - return imgBits; - } - - public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight) { - - int width = Math.max(tileWidth, tileWidth*tileList.size()); - BufferedImage bufferedImage = new BufferedImage(width, tileHeight, - BufferedImage.TYPE_3BYTE_BGR); - - if(s_logger.isTraceEnabled()) - s_logger.trace("Create merged image, w: " + width + ", h: " + tileHeight); - - if(vc != null && vc.memImage != null) { - synchronized(vc.memImage) { - Graphics2D g = bufferedImage.createGraphics(); - int i = 0; - for(TileInfo tile : tileList) { - Rectangle rc = tile.getTileRect(); - - if(s_logger.isTraceEnabled()) - s_logger.trace("Merge tile into jpeg from (" + rc.x + "," + rc.y + "," + (rc.x + rc.width) + "," + (rc.y + rc.height) + ") to (" + i*tileWidth + ",0)" ); - - g.drawImage(vc.memImage, i*tileWidth, 0, i*tileWidth + rc.width, rc.height, - rc.x, rc.y, rc.x + rc.width, rc.y + rc.height, null); - - i++; - } - } - } - - byte[] imgBits = null; - try { - imgBits = jpegFromImage(bufferedImage); - - if(s_logger.isTraceEnabled()) - s_logger.trace("Merge jpeg image size: " + imgBits.length + ", tiles: " + tileList.size()); - } catch (IOException e) { - } - return imgBits; - } - - public byte[] jpegFromImage(BufferedImage image) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(128000); - javax.imageio.ImageIO.write(image, "jpg", bos); - - byte[] jpegBits = bos.toByteArray(); - bos.close(); - return jpegBits; - } - - private String prepareAjaxImage(List tiles, boolean init) { - byte[] imgBits; - if(init) - imgBits = getFrameBufferJpeg(); - else - imgBits = getTilesMergedJpeg(tiles, tracker.getTileWidth(), tracker.getTileHeight()); - - if(imgBits == null) { - s_logger.warn("Unable to generate jpeg image"); - } else { - if(s_logger.isTraceEnabled()) - s_logger.trace("Generated jpeg image size: " + imgBits.length); - } - - int key = ajaxImageCache.putImage(imgBits); - StringBuffer sb = new StringBuffer("/ajaximg?host="); - sb.append(host).append("&port=").append(port).append("&sid=").append(passwordParam); - sb.append("&key=").append(key).append("&ts=").append(System.currentTimeMillis()); - return sb.toString(); - } - - private String prepareAjaxSession(boolean init) { - StringBuffer sb = new StringBuffer(); - - if(init) - ajaxSessionId++; - - sb.append("/ajax?host=").append(host).append("&port=").append(port); - sb.append("&sid=").append(passwordParam).append("&sess=").append(ajaxSessionId); - return sb.toString(); - } - - public String onAjaxClientKickoff() { - return "onKickoff();"; - } - - private boolean waitForViewerReady() { - long startTick = System.currentTimeMillis(); - while(System.currentTimeMillis() - startTick < 5000) { - if(this.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) - return true; - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - return false; - } - - private String onAjaxClientConnectFailed() { - return "

" + - "Unable to start console session as connection is refused by the machine you are accessing" + - "

"; - } - - public String onAjaxClientStart(String title, List languages, String guest) { - if(!waitForViewerReady()) - return onAjaxClientConnectFailed(); - - // make sure we switch to AJAX view on start - setAjaxViewer(true); - - int tileWidth = tracker.getTileWidth(); - int tileHeight = tracker.getTileHeight(); - int width = tracker.getTrackWidth(); - int height = tracker.getTrackHeight(); - - if(s_logger.isTraceEnabled()) - s_logger.trace("Ajax client start, frame buffer w: " + width + ", " + height); - - synchronized(this) { - if(framebufferResized) { - framebufferResized = false; - } - } - - int retry = 0; - if(justCreated()) { - tracker.initCoverageTest(); - - try { - rfb.writeFramebufferUpdateRequest(0, 0, tracker.getTrackWidth(), tracker.getTrackHeight(), false); - - while(!tracker.hasFullCoverage() && retry < 10) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - retry++; - } - } catch (IOException e1) { - s_logger.warn("Connection was broken "); - } - } - - List tiles = tracker.scan(true); - String imgUrl = prepareAjaxImage(tiles, true); - String updateUrl = prepareAjaxSession(true); - - StringBuffer sbTileSequence = new StringBuffer(); - int i = 0; - for(TileInfo tile : tiles) { - sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); - if(i < tiles.size() - 1) - sbTileSequence.append(","); - - i++; - } - - return getAjaxViewerPageContent(sbTileSequence.toString(), imgUrl, - updateUrl, width, height, tileWidth, tileHeight, title, - ConsoleProxy.keyboardType == ConsoleProxy.KEYBOARD_RAW, languages, guest); - } - - private String getAjaxViewerPageContent(String tileSequence, String imgUrl, String updateUrl, int width, - int height, int tileWidth, int tileHeight, String title, boolean rawKeyboard, List languages, String guest) { - - StringBuffer sbLanguages = new StringBuffer(""); - if(languages != null) { - for(String lang : languages) { - if(sbLanguages.length() > 0) { - sbLanguages.append(","); - } - sbLanguages.append(lang); - } - } - - boolean linuxGuest = true; - if(guest != null && guest.equalsIgnoreCase("windows")) - linuxGuest = false; - - String[] content = new String[] { - "", - "", - "", - "", - "", - "", - "", - "", - "" + title + "", - "", - "", - "
", - "", - "", - "
", - "
", - "", - "", - "" - }; - - StringBuffer sb = new StringBuffer(); - for(int i = 0; i < content.length; i++) - sb.append(content[i]); - - return sb.toString(); - } - - public String onAjaxClientDisconnected() { - return "onDisconnect();"; - } - - public String onAjaxClientUpdate() { - if(!waitForViewerReady()) - return onAjaxClientDisconnected(); - - synchronized(tileDirtyEvent) { - if(!dirtyFlag) { - try { - tileDirtyEvent.wait(3000); - } catch(InterruptedException e) { - } - } - } - - boolean doResize = false; - synchronized(this) { - if(framebufferResized) { - framebufferResized = false; - doResize = true; - } - } - - List tiles; - - if(doResize) - tiles = tracker.scan(true); - else - tiles = tracker.scan(false); - dirtyFlag = false; - - String imgUrl = prepareAjaxImage(tiles, false); - StringBuffer sbTileSequence = new StringBuffer(); - int i = 0; - for(TileInfo tile : tiles) { - sbTileSequence.append("[").append(tile.getRow()).append(",").append(tile.getCol()).append("]"); - if(i < tiles.size() - 1) - sbTileSequence.append(","); - - i++; - } - - return getAjaxViewerUpdatePageContent(sbTileSequence.toString(), imgUrl, doResize, resizedFramebufferWidth, - resizedFramebufferHeight, tracker.getTileWidth(), tracker.getTileHeight()); - } - - private String getAjaxViewerUpdatePageContent(String tileSequence, String imgUrl, boolean resized, int width, - int height, int tileWidth, int tileHeight) { - - String[] content = new String[] { - "tileMap = [ " + tileSequence + " ];", - resized ? "ajaxViewer.resize('main_panel', " + width + ", " + height + " , " + tileWidth + ", " + tileHeight + ");" : "", - "ajaxViewer.refresh('" + imgUrl + "', tileMap, false);" - }; - - StringBuffer sb = new StringBuffer(); - for(int i = 0; i < content.length; i++) - sb.append(content[i]); - - return sb.toString(); - } - - - public long getAjaxSessionId() { - return this.ajaxSessionId; - } - - public AjaxFIFOImageCache getAjaxImageCache() { - return ajaxImageCache; - } - - public boolean isAjaxViewer() { - return ajaxViewer; - } - - public synchronized void setAjaxViewer(boolean ajaxViewer) { - if(this.ajaxViewer != ajaxViewer) { - if(this.ajaxViewer) { - // previous session was AJAX session - this.ajaxSessionId++; // increase the session id so that it will disconnect existing AJAX viewer - } else { - // close java client session - if(clientStream != null) { - byte[] bs = new byte[2]; - bs[0] = (byte)250; - bs[1] = 1; - writeToClientStream(bs); - - try { - clientStream.close(); - } catch (IOException e) { - } - clientStream = null; - } - } - this.ajaxViewer = ajaxViewer; - } - } -*/ } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java index c21c422eb16..44663557882 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java @@ -16,7 +16,7 @@ import com.cloud.consoleproxy.util.ImageHelper; * A BuffereImageCanvas component represents frame buffer image on the * screen. It also notifies its subscribers when screen is repainted. */ -public class BufferedImageCanvas extends Canvas { +public class BufferedImageCanvas extends Canvas implements FrameBufferCanvas { private static final long serialVersionUID = 1L; // Offline screen buffer @@ -80,6 +80,7 @@ public class BufferedImageCanvas extends Canvas { } } + @Override public byte[] getFrameBufferJpeg() { int width = 800; int height = 600; @@ -102,6 +103,7 @@ public class BufferedImageCanvas extends Canvas { return imgBits; } + @Override public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight) { int width = Math.max(tileWidth, tileWidth*tileList.size()); BufferedImage bufferedImage = new BufferedImage(width, tileHeight, diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java new file mode 100644 index 00000000000..912ad088a5f --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java @@ -0,0 +1,10 @@ +package com.cloud.consoleproxy.vnc; + +import java.util.List; + +import com.cloud.console.TileInfo; + +public interface FrameBufferCanvas { + public byte[] getFrameBufferJpeg(); + public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientListener.java b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java similarity index 73% rename from console-proxy/src/com/cloud/consoleproxy/vnc/VncClientListener.java rename to console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java index f3934f10d7b..3d17592c689 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientListener.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferEventListener.java @@ -1,6 +1,6 @@ package com.cloud.consoleproxy.vnc; -public interface VncClientListener { +public interface FrameBufferEventListener { void onFramebufferSizeChange(int w, int h); void onFramebufferUpdate(int x, int y, int w, int h); } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java index 1bdc114daea..93258421612 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java @@ -28,7 +28,7 @@ public class VncClient { private VncServerPacketReceiver receiver; private boolean noUI = false; - private VncClientListener clientListener = null; + private FrameBufferEventListener clientListener = null; public static void main(String args[]) { if (args.length < 3) { @@ -62,7 +62,7 @@ public class VncClient { /* LOG */SimpleLogger.info("Usage: HOST PORT PASSWORD."); } - public VncClient(String host, int port, String password, boolean noUI, VncClientListener clientListener) + public VncClient(String host, int port, String password, boolean noUI, FrameBufferEventListener clientListener) throws UnknownHostException, IOException { this.noUI = noUI; @@ -88,7 +88,6 @@ public class VncClient { socket.close(); } catch (Throwable e) { } - } public void connectTo(String host, int port, String password) throws UnknownHostException, IOException { @@ -356,5 +355,11 @@ public class VncClient { screen.setDesktopName(desktopName); } } - + + public FrameBufferCanvas getFrameBufferCanvas() { + if(receiver != null) + return receiver.getCanvas(); + + return null; + } } diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java index 669fb895c8a..1e8fb912dac 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java @@ -17,10 +17,10 @@ public class VncServerPacketReceiver implements Runnable { private boolean connectionAlive = true; private VncClient vncConnection; private final FrameBufferUpdateListener fburListener; - private final VncClientListener clientListener; + private final FrameBufferEventListener clientListener; public VncServerPacketReceiver(DataInputStream is, BufferedImageCanvas canvas, VncScreenDescription screen, VncClient vncConnection, - FrameBufferUpdateListener fburListener, VncClientListener clientListener) { + FrameBufferUpdateListener fburListener, FrameBufferEventListener clientListener) { this.screen = screen; this.canvas = canvas; this.is = is; @@ -28,6 +28,10 @@ public class VncServerPacketReceiver implements Runnable { this.fburListener = fburListener; this.clientListener = clientListener; } + + public BufferedImageCanvas getCanvas() { + return canvas; + } @Override public void run() { diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java index 64c184c8e72..b0ed4fdff5e 100644 --- a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java @@ -5,7 +5,7 @@ import java.io.IOException; import com.cloud.consoleproxy.vnc.BufferedImageCanvas; import com.cloud.consoleproxy.vnc.RfbConstants; -import com.cloud.consoleproxy.vnc.VncClientListener; +import com.cloud.consoleproxy.vnc.FrameBufferEventListener; import com.cloud.consoleproxy.vnc.VncScreenDescription; import com.cloud.consoleproxy.vnc.packet.server.CopyRect; import com.cloud.consoleproxy.vnc.packet.server.RawRect; @@ -15,10 +15,10 @@ public class FramebufferUpdatePacket { private final VncScreenDescription screen; private final BufferedImageCanvas canvas; - private final VncClientListener clientListener; + private final FrameBufferEventListener clientListener; public FramebufferUpdatePacket(BufferedImageCanvas canvas, VncScreenDescription screen, DataInputStream is, - VncClientListener clientListener) throws IOException { + FrameBufferEventListener clientListener) throws IOException { this.screen = screen; this.canvas = canvas;