mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	Console proxy refactoring incremental check-in - pluggable framework for VNC and RDP engine
This commit is contained in:
		
							parent
							
								
									5f387762ed
								
							
						
					
					
						commit
						e37c99d109
					
				@ -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 +
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
}
 | 
			
		||||
@ -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<TileInfo> tileList, int tileWidth, int tileHeight) {
 | 
			
		||||
		FrameBufferCanvas canvas = getFrameBufferCavas();
 | 
			
		||||
		if(canvas != null)
 | 
			
		||||
			return canvas.getTilesMergedJpeg(tileList, tileWidth, tileHeight);
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	private String prepareAjaxImage(List<TileInfo> 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 "<html><head></head><body><div id=\"main_panel\" tabindex=\"1\"><p>" + 
 | 
			
		||||
			"Unable to start console session as connection is refused by the machine you are accessing" +
 | 
			
		||||
			"</p></div></body></html>";
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public String onAjaxClientStart(String title, List<String> 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<TileInfo> 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<String> 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[] {
 | 
			
		||||
			"<html>",
 | 
			
		||||
			"<head>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/jquery.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/cloud.logger.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/ajaxviewer.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/handler.js\"></script>",
 | 
			
		||||
			"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/ajaxviewer.css\"></link>",
 | 
			
		||||
			"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/logger.css\"></link>",
 | 
			
		||||
			"<title>" + title + "</title>",
 | 
			
		||||
			"</head>",
 | 
			
		||||
			"<body>",
 | 
			
		||||
			"<div id=\"toolbar\">",
 | 
			
		||||
			"<ul>",
 | 
			
		||||
				"<li>", 
 | 
			
		||||
					"<a href=\"#\" cmd=\"sendCtrlAltDel\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/cad.gif\" alt=\"Ctrl-Alt-Del\" />Ctrl-Alt-Del</span>", 
 | 
			
		||||
					"</a>", 
 | 
			
		||||
				"</li>",
 | 
			
		||||
				"<li>", 
 | 
			
		||||
					"<a href=\"#\" cmd=\"sendCtrlEsc\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Ctrl-Esc\" style=\"width:16px;height:16px\"/>Ctrl-Esc</span>",
 | 
			
		||||
					"</a>", 
 | 
			
		||||
				"</li>",
 | 
			
		||||
				
 | 
			
		||||
				"<li class=\"pulldown\">", 
 | 
			
		||||
					"<a href=\"#\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Keyboard\" style=\"width:16px;height:16px\"/>Keyboard</span>",
 | 
			
		||||
					"</a>", 
 | 
			
		||||
					"<ul>",
 | 
			
		||||
		    			"<li><a href=\"#\" cmd=\"keyboard_us\"><span>Standard (US) keyboard</span></a></li>",
 | 
			
		||||
		    			"<li><a href=\"#\" cmd=\"keyboard_jp\"><span>Japanese keyboard</span></a></li>",
 | 
			
		||||
					"</ul>",
 | 
			
		||||
				"</li>",
 | 
			
		||||
			"</ul>",
 | 
			
		||||
			"<span id=\"light\" class=\"dark\" cmd=\"toggle_logwin\"></span>", 
 | 
			
		||||
			"</div>",
 | 
			
		||||
			"<div id=\"main_panel\" tabindex=\"1\"></div>",
 | 
			
		||||
			"<script language=\"javascript\">",
 | 
			
		||||
			"var acceptLanguages = '" + sbLanguages.toString() + "';",
 | 
			
		||||
			"var tileMap = [ " + tileSequence + " ];",
 | 
			
		||||
			"var ajaxViewer = new AjaxViewer('main_panel', '" + imgUrl + "', '" + updateUrl + "', tileMap, ", 
 | 
			
		||||
				String.valueOf(width) + ", " + String.valueOf(height) + ", " + String.valueOf(tileWidth) + ", " + String.valueOf(tileHeight) + ");",
 | 
			
		||||
 | 
			
		||||
			"$(function() {",
 | 
			
		||||
				"ajaxViewer.start();",
 | 
			
		||||
			"});",
 | 
			
		||||
 | 
			
		||||
			"</script>",
 | 
			
		||||
			"</body>",
 | 
			
		||||
			"</html>"	
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		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<TileInfo> 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();
 | 
			
		||||
}
 | 
			
		||||
@ -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<ConsoleProxyConnection> connections;
 | 
			
		||||
	
 | 
			
		||||
	public ConsoleProxyClientStatsCollector() {
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
 | 
			
		||||
		setConnections(connMap);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public String getStatsReport() {
 | 
			
		||||
		Gson gson = new GsonBuilder().setPrettyPrinting().create();
 | 
			
		||||
		return gson.toJson(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
 | 
			
		||||
		
 | 
			
		||||
		ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
 | 
			
		||||
		Enumeration<String> 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() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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<String, ConsoleProxyClient> connMap;
 | 
			
		||||
	private long lastLogScan = 0;
 | 
			
		||||
	
 | 
			
		||||
	public ConsoleProxyGCThread(Hashtable<String, ConsoleProxyClient> 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<String> 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) {}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
	private VncClient client;
 | 
			
		||||
 | 
			
		||||
	boolean dirtyFlag = false;
 | 
			
		||||
	private Object tileDirtyEvent = new Object();
 | 
			
		||||
	private AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
 | 
			
		||||
	public ConsoleProxyVncClient() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onFramebufferSizeChange(int w, int h) {
 | 
			
		||||
	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<TileInfo> 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<TileInfo> 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 "<html><head></head><body><div id=\"main_panel\" tabindex=\"1\"><p>" + 
 | 
			
		||||
			"Unable to start console session as connection is refused by the machine you are accessing" +
 | 
			
		||||
			"</p></div></body></html>";
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public String onAjaxClientStart(String title, List<String> 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<TileInfo> 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<String> 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[] {
 | 
			
		||||
			"<html>",
 | 
			
		||||
			"<head>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/jquery.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/cloud.logger.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/ajaxviewer.js\"></script>",
 | 
			
		||||
			"<script type=\"text/javascript\" language=\"javascript\" src=\"/resource/js/handler.js\"></script>",
 | 
			
		||||
			"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/ajaxviewer.css\"></link>",
 | 
			
		||||
			"<link rel=\"stylesheet\" type=\"text/css\" href=\"/resource/css/logger.css\"></link>",
 | 
			
		||||
			"<title>" + title + "</title>",
 | 
			
		||||
			"</head>",
 | 
			
		||||
			"<body>",
 | 
			
		||||
			"<div id=\"toolbar\">",
 | 
			
		||||
			"<ul>",
 | 
			
		||||
				"<li>", 
 | 
			
		||||
					"<a href=\"#\" cmd=\"sendCtrlAltDel\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/cad.gif\" alt=\"Ctrl-Alt-Del\" />Ctrl-Alt-Del</span>", 
 | 
			
		||||
					"</a>", 
 | 
			
		||||
				"</li>",
 | 
			
		||||
				"<li>", 
 | 
			
		||||
					"<a href=\"#\" cmd=\"sendCtrlEsc\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Ctrl-Esc\" style=\"width:16px;height:16px\"/>Ctrl-Esc</span>",
 | 
			
		||||
					"</a>", 
 | 
			
		||||
				"</li>",
 | 
			
		||||
				
 | 
			
		||||
				"<li class=\"pulldown\">", 
 | 
			
		||||
					"<a href=\"#\">", 
 | 
			
		||||
						"<span><img align=\"left\" src=\"/resource/images/winlog.png\" alt=\"Keyboard\" style=\"width:16px;height:16px\"/>Keyboard</span>",
 | 
			
		||||
					"</a>", 
 | 
			
		||||
					"<ul>",
 | 
			
		||||
		    			"<li><a href=\"#\" cmd=\"keyboard_us\"><span>Standard (US) keyboard</span></a></li>",
 | 
			
		||||
		    			"<li><a href=\"#\" cmd=\"keyboard_jp\"><span>Japanese keyboard</span></a></li>",
 | 
			
		||||
					"</ul>",
 | 
			
		||||
				"</li>",
 | 
			
		||||
			"</ul>",
 | 
			
		||||
			"<span id=\"light\" class=\"dark\" cmd=\"toggle_logwin\"></span>", 
 | 
			
		||||
			"</div>",
 | 
			
		||||
			"<div id=\"main_panel\" tabindex=\"1\"></div>",
 | 
			
		||||
			"<script language=\"javascript\">",
 | 
			
		||||
			"var acceptLanguages = '" + sbLanguages.toString() + "';",
 | 
			
		||||
			"var tileMap = [ " + tileSequence + " ];",
 | 
			
		||||
			"var ajaxViewer = new AjaxViewer('main_panel', '" + imgUrl + "', '" + updateUrl + "', tileMap, ", 
 | 
			
		||||
				String.valueOf(width) + ", " + String.valueOf(height) + ", " + String.valueOf(tileWidth) + ", " + String.valueOf(tileHeight) + ");",
 | 
			
		||||
 | 
			
		||||
			"$(function() {",
 | 
			
		||||
				"ajaxViewer.start();",
 | 
			
		||||
			"});",
 | 
			
		||||
 | 
			
		||||
			"</script>",
 | 
			
		||||
			"</body>",
 | 
			
		||||
			"</html>"	
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		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<TileInfo> 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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
*/	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ import com.cloud.consoleproxy.util.ImageHelper;
 | 
			
		||||
 * A <code>BuffereImageCanvas</code> 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<TileInfo> tileList, int tileWidth, int tileHeight) {
 | 
			
		||||
	int width = Math.max(tileWidth, tileWidth*tileList.size());
 | 
			
		||||
	BufferedImage bufferedImage = new BufferedImage(width, tileHeight,
 | 
			
		||||
 | 
			
		||||
@ -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<TileInfo> tileList, int tileWidth, int tileHeight);
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
@ -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 {
 | 
			
		||||
@ -357,4 +356,10 @@ public class VncClient {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  public FrameBufferCanvas getFrameBufferCanvas() {
 | 
			
		||||
    if(receiver != null)
 | 
			
		||||
      return receiver.getCanvas();
 | 
			
		||||
	  
 | 
			
		||||
	return null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
@ -29,6 +29,10 @@ public class VncServerPacketReceiver implements Runnable {
 | 
			
		||||
    this.clientListener = clientListener;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  public BufferedImageCanvas getCanvas() { 
 | 
			
		||||
	return canvas; 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void run() {
 | 
			
		||||
    try {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user