GUAC-609: Migrate outbound clipboard integration to new streams.

This commit is contained in:
Michael Jumper
2014-04-10 16:41:05 -07:00
parent 5961dde12f
commit 0537a6afbc
4 changed files with 132 additions and 20 deletions

View File

@@ -42,6 +42,7 @@ import org.glyptodon.guacamole.net.event.TunnelCloseEvent;
import org.glyptodon.guacamole.net.event.TunnelConnectEvent;
import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener;
import org.glyptodon.guacamole.net.event.listener.TunnelConnectListener;
import org.glyptodon.guacamole.properties.GuacamoleProperties;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -329,8 +330,19 @@ public class BasicTunnelRequestUtility {
@Override
public GuacamoleReader acquireReader() {
// Monitor instructions which pertain to server-side events
return new MonitoringGuacamoleReader(clipboard, super.acquireReader());
// Monitor instructions which pertain to server-side events, if necessary
try {
if (GuacamoleProperties.getProperty(CaptureClipboard.INTEGRATION_ENABLED, false))
return new MonitoringGuacamoleReader(clipboard, super.acquireReader());
}
catch (GuacamoleException e) {
logger.warn("Clipboard integration disabled due to error.", e);
}
// Pass through by default.
return super.acquireReader();
}
@Override

View File

@@ -71,8 +71,11 @@ public class CaptureClipboard extends AuthenticatingHttpServlet {
// Send clipboard contents
try {
response.setContentType("text/plain");
response.getWriter().print(clipboard.waitForContents(CLIPBOARD_TIMEOUT));
synchronized (clipboard) {
clipboard.waitForContents(CLIPBOARD_TIMEOUT);
response.setContentType(clipboard.getMimetype());
response.getOutputStream().write(clipboard.getContents());
}
}
catch (IOException e) {
throw new GuacamoleServerException("Unable to send clipboard contents", e);

View File

@@ -31,11 +31,36 @@ package org.glyptodon.guacamole.net.basic;
*/
public class ClipboardState {
/**
* The maximum number of bytes to track.
*/
private static final int MAXIMUM_LENGTH = 262144;
/**
* The mimetype of the current contents.
*/
private String mimetype = "text/plain";
/**
* The mimetype of the pending contents.
*/
private String pending_mimetype = "text/plain";
/**
* The current contents.
*/
private String contents = "";
private byte[] contents = new byte[0];
/**
* The pending clipboard contents.
*/
private final byte[] pending = new byte[MAXIMUM_LENGTH];
/**
* The length of the pending data, in bytes.
*/
private int pending_length = 0;
/**
* The timestamp of the last contents update.
*/
@@ -45,38 +70,84 @@ public class ClipboardState {
* Returns the current clipboard contents.
* @return The current clipboard contents
*/
public synchronized String getContents() {
public synchronized byte[] getContents() {
return contents;
}
/**
* Sets the current clipboard contents.
* @param contents The contents to assign to the clipboard.
* Returns the mimetype of the current clipboard contents.
* @return The mimetype of the current clipboard contents.
*/
public synchronized void setContents(String contents) {
this.contents = contents;
last_update = System.currentTimeMillis();
this.notifyAll();
public synchronized String getMimetype() {
return mimetype;
}
/**
* Wait up to the given timeout for new clipboard data. If data more recent
* than the timeout period is available, return that.
* Begins a new update of the clipboard contents. The actual contents will
* not be saved until commit() is called.
*
* @param mimetype The mimetype of the contents being added.
*/
public synchronized void begin(String mimetype) {
pending_length = 0;
this.pending_mimetype = mimetype;
}
/**
* Appends the given data to the clipboard contents.
*
* @param data The raw data to append.
*/
public synchronized void append(byte[] data) {
// Calculate size of copy
int length = data.length;
int remaining = pending.length - pending_length;
if (remaining < length)
length = remaining;
// Append data
System.arraycopy(data, 0, pending, pending_length, length);
pending_length += length;
}
/**
* Commits the pending contents to the clipboard, notifying any threads
* waiting for clipboard updates.
*/
public synchronized void commit() {
// Commit contents
mimetype = pending_mimetype;
contents = new byte[pending_length];
System.arraycopy(pending, 0, contents, 0, pending_length);
// Notify of update
last_update = System.currentTimeMillis();
this.notifyAll();
}
/**
* Wait up to the given timeout for new clipboard data.
*
* @param timeout The amount of time to wait, in milliseconds.
* @return The current clipboard contents.
* @return true if the contents were updated within the timeframe given,
* false otherwise.
*/
public synchronized String waitForContents(int timeout) {
public synchronized boolean waitForContents(int timeout) {
// Wait for new contents if it's been a while
if (System.currentTimeMillis() - last_update > timeout) {
try {
this.wait(timeout);
return true;
}
catch (InterruptedException e) { /* ignore */ }
}
return getContents();
return false;
}

View File

@@ -23,6 +23,7 @@
package org.glyptodon.guacamole.net.basic;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.io.GuacamoleReader;
import org.glyptodon.guacamole.protocol.GuacamoleInstruction;
@@ -45,6 +46,11 @@ public class MonitoringGuacamoleReader implements GuacamoleReader {
*/
private final ClipboardState clipboard;
/**
* The index of the clipboard stream, if any.
*/
private String clipboard_stream_index = null;
/**
* Creates a new MonitoringGuacamoleReader which watches the instructions
* read by the given GuacamoleReader, firing events when specific
@@ -84,11 +90,31 @@ public class MonitoringGuacamoleReader implements GuacamoleReader {
if (instruction == null)
return null;
// If clipboard changed, notify listeners
// If clipboard changing, reset clipboard state
if (instruction.getOpcode().equals("clipboard")) {
List<String> args = instruction.getArgs();
if (args.size() >= 1)
clipboard.setContents(args.get(0));
if (args.size() >= 2) {
clipboard_stream_index = args.get(0);
clipboard.begin(args.get(1));
}
}
// Add clipboard blobs to existing streams
else if (instruction.getOpcode().equals("blob")) {
List<String> args = instruction.getArgs();
if (args.size() >= 2 && args.get(0).equals(clipboard_stream_index)) {
String base64 = args.get(1);
clipboard.append(DatatypeConverter.parseBase64Binary(base64));
}
}
// Terminate and update clipboard at end of stream
else if (instruction.getOpcode().equals("end")) {
List<String> args = instruction.getArgs();
if (args.size() >= 1 && args.get(0).equals(clipboard_stream_index)) {
clipboard.commit();
clipboard_stream_index = null;
}
}
return instruction;