// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.consoleproxy;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.List;
import org.apache.log4j.Logger;
import com.cloud.consoleproxy.util.TileInfo;
import com.cloud.consoleproxy.util.TileTracker;
import com.cloud.consoleproxy.vnc.FrameBufferCanvas;
/**
*
* @author Kelven Yang
*
* 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, ConsoleProxyClientListener {
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 ConsoleProxyClientParam clientParam;
protected String clientToken;
protected long createTime = System.currentTimeMillis();
protected long lastFrontEndActivityTime = System.currentTimeMillis();
protected boolean framebufferResized = false;
protected int resizedFramebufferWidth;
protected int resizedFramebufferHeight;
public ConsoleProxyClientBase() {
tracker = new TileTracker();
tracker.initTracking(64, 64, 800, 600);
}
//
// interface ConsoleProxyClient
//
@Override
public int getClientId() {
return clientId;
}
public abstract boolean isHostConnected();
public abstract boolean isFrontEndAlive();
@Override
public long getAjaxSessionId() {
return this.ajaxSessionId;
}
@Override
public AjaxFIFOImageCache getAjaxImageCache() {
return ajaxImageCache;
}
public Image getClientScaledImage(int width, int height) {
FrameBufferCanvas canvas = getFrameBufferCavas();
if(canvas != null)
return canvas.getFrameBufferScaledImage(width, height);
return null;
}
public abstract void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers);
public abstract void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers);
@Override
public long getClientCreateTime() {
return createTime;
}
@Override
public long getClientLastFrontEndActivityTime() {
return lastFrontEndActivityTime;
}
@Override
public String getClientHostAddress() {
return clientParam.getClientHostAddress();
}
@Override
public int getClientHostPort() {
return clientParam.getClientHostPort();
}
@Override
public String getClientHostPassword() {
return clientParam.getClientHostPassword();
}
@Override
public String getClientTag() {
if(clientParam.getClientTag() != null)
return clientParam.getClientTag();
return "";
}
@Override
public abstract void initClient(ConsoleProxyClientParam param);
@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();
sb.append("/ajaximg?token=").append(clientToken);
sb.append("&key=").append(key);
sb.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?token=").append(clientToken).append("&sess=").append(ajaxSessionId);
return sb.toString();
}
@Override
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>";
}
@Override
public String onAjaxClientStart(String title, List<String> languages, String guest) {
updateFrontEndActivityTime();
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();";
}
@Override
public String onAjaxClientUpdate() {
updateFrontEndActivityTime();
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;
}
private void signalTileDirtyEvent() {
synchronized(tileDirtyEvent) {
dirtyFlag = true;
tileDirtyEvent.notifyAll();
}
}
public void updateFrontEndActivityTime() {
lastFrontEndActivityTime = System.currentTimeMillis();
}
protected abstract FrameBufferCanvas getFrameBufferCavas();
public ConsoleProxyClientParam getClientParam() {
return clientParam;
}
public void setClientParam(ConsoleProxyClientParam clientParam) {
this.clientParam = clientParam;
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword());
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
}
}