/*-
*******************************************************************************
* Copyright (c) 2011, 2015 Diamond Light Source Ltd.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthew Gerring - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.dawnsci.remotedataset.client.slice;
import java.io.ObjectInputStream;
import java.net.URL;
import java.net.URLConnection;
import javax.imageio.ImageIO;
import org.eclipse.dawnsci.remotedataset.Format;
import org.eclipse.dawnsci.remotedataset.ServiceHolder;
import org.eclipse.dawnsci.remotedataset.client.URLBuilder;
import org.eclipse.dawnsci.remotedataset.client.streamer.IStreamer;
import org.eclipse.dawnsci.remotedataset.client.streamer.StreamerFactory;
/**
* <pre>
* Class to look after making a connection to the HTTP Data slice server.
* Basically it encodes the parameters if a GET is used or if a POST is used,
* it deals with that. There is no need when using the client to know how the
* HTTP connection is managed.
*
* Essential
* =========
* path` - path to file or directory for loader factory to use.
*
* Optional
* ========
* dataset - dataset name, by default the dataset at position 0 will be returned
*
* slice` - Provides the slice in the form of that required org.eclipse.dawnsci.analysis.api.dataset.Slice.convertFromString(...)
* for example: [0,:1024,:1024]. If left unset and data not too large, will send while dataset, no slice.
*
* bin - downsample As in Downsample.fromString(...) ; examples: 'MEAN:2x3', 'MAXIMUM:2x2'
* by default no downsampling is done
*
* format` - One of Format.values():
* DATA - zipped slice, binary (default)
* JPG - JPG made using IImageService to make the image
* PNG - PNG made using IImageService to make the image
* MJPG:<dim> e.g. MJPG:0 to send the first dimension as slices in a series as JPGs. NOTE slice mist be set in this case.
* MDATA:<dim> e.g. MDATA:0 to send the first dimension as slices in a series as IDatasets. NOTE slice must be set in this case.
*
* histo` - Encoding of histo to the rules of ImageServiceBean.encode(...) / ImageServiceBean.decode(...)
* Example: "MEAN", "OUTLIER_VALUES:5-95"
* Only used when an actual image is requested.
*
* sleep - Time to sleep between sending images, default 100ms.
*
* writingExpected - If you know that the remote dataset it likely to be written to, set this flag to ensure
* that limitations with SWMR datestamping and cached writable lazy datasets, do not cause the
* dataset to be incorrectly cached by the server.
*
* `URL encoded.
*
* Usage:
* Normally we simply use getImage() or get data but for a stream, the formula is:
<code>
try {
while(!client.isFinished()) {
final BufferedImage image = client.take(); // Blocks
if (image==null) break;
// Do what we want with the image, may cause exception
//...
}
} catch (Exception ne) {
client.setFinished(true);
throw ne;
}
</code>
</pre>
* @author Matthew Gerring
*
*/
public class SliceClient<T> {
public SliceClient() {
}
private int imageCache=10;
private boolean isFinished;
// Private data, not getter/setter
private IStreamer<T> streamer;
private URLBuilder urlBuilder;
/**
* Used for DataServer connections.
*
* @param serverName
* @param port
*/
public SliceClient(String serverName, int port) {
this.urlBuilder = new URLBuilder(serverName, port);
}
public SliceClient(URLBuilder urlBuilder) {
this.urlBuilder = urlBuilder;
}
/**
* Used for MJPG streams
* @param url
*/
public SliceClient(URL url) {
this.urlBuilder = new URLBuilder(url);
}
/**
* Call to take the next image for a stream (MJPG). Blocking call.
* If in JPG or PNG mode, this is the same as getImage().
* @return
* @throws Exception
*/
public T take() throws Exception {
Format format = urlBuilder.getFormat();
if (format!=Format.MJPG && format.isImage()) {
return get();
} else if (format!=Format.MDATA && (format==null || format==Format.DATA)) {
return get();
}
if (isFinished()) throw new Exception("Client has finished reading images!");
if (streamer==null) {
this.isFinished = false;
this.streamer = (IStreamer<T>)StreamerFactory.getStreamer(urlBuilder.getSliceURL(), getSleep(), imageCache, format);
streamer.start(); // Runs thread to add to queue
}
T image = streamer.take();
if (image == null) {
isFinished = true;
streamer = null; // A null image means that the connection is down.
}
return image;
}
public long getDroppedImageCount() {
return streamer.getDroppedImageCount();
}
public long getReceivedImageCount() {
return streamer.getReceivedImageCount();
}
/**
* Gets a remote, non-dynamic view of the data.
* If format is DATA, loads data and returns it
* If format is an Image returns Buffered Image
* If format is MONITOR an IRemoteDataset is returned.
*
* @return
* @throws Exception
*/
public T get() throws Exception {
Format format = urlBuilder.getFormat();
if (format==null) format = Format.DATA;
switch(format) {
case DATA:
return getData();
case JPG:
case PNG:
return getImage();
}
throw new Exception("Format '"+format+"' cannot be used with get()");
}
private T getImage() throws Exception {
isFinished = false;
try {
Format format = urlBuilder.getFormat();
if (!format.isImage()) {
throw new Exception("Cannot get image with format set to "+format);
}
final URL url = urlBuilder.getSliceURL();
URLConnection conn = url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
return (T)ImageIO.read(url.openStream());
} finally {
isFinished = true;
}
}
private T getData() throws Exception {
isFinished = false;
try {
// We are getting into serializing and deserializing IDataset which
// might come with some fruity dependencies
if (ServiceHolder.getClassLoaderService()!=null) ServiceHolder.getClassLoaderService().setDataAnalysisClassLoaderActive(true);
Format format = urlBuilder.getFormat();
if (format!=null && format!=Format.DATA) {
throw new Exception("Cannot get data with format set to "+format);
}
final URL url = urlBuilder.getSliceURL();
URLConnection conn = url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
ObjectInputStream oin=null;
try {
oin = new ObjectInputStream(url.openStream());
return (T)oin.readObject();
} finally {
if (oin!=null) oin.close();
}
} finally {
if (ServiceHolder.getClassLoaderService()!=null) ServiceHolder.getClassLoaderService().setDataAnalysisClassLoaderActive(false);
isFinished = true;
}
}
public String getPath() {
return urlBuilder.getPath();
}
public void setPath(String path) {
urlBuilder.setPath(path);
}
public String getDataset() {
return urlBuilder.getDataset();
}
public void setDataset(String dataset) {
urlBuilder.setDataset(dataset);
}
public String getSlice() {
return urlBuilder.getSlice();
}
public void setSlice(String slice) {
urlBuilder.setSlice(slice);
}
public String getBin() {
return urlBuilder.getBin();
}
public void setBin(String bin) {
urlBuilder.setBin(bin);
}
public Format getFormat() {
return urlBuilder.getFormat();
}
public void setFormat(Format format) {
urlBuilder.setFormat(format);
}
public String getHisto() {
return urlBuilder.getHisto();
}
public void setHisto(String histo) {
urlBuilder.setHisto(histo);
}
public long getSleep() {
return urlBuilder.getSleep();
}
public void setSleep(long sleep) {
urlBuilder.setSleep(sleep);
}
public boolean isFinished() {
return isFinished;
}
public void setFinished(boolean b) {
isFinished = b;
if (streamer!=null) streamer.setFinished(b);
}
public int getImageCache() {
return imageCache;
}
public void setImageCache(int imageCache) {
this.imageCache = imageCache;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (isFinished ? 1231 : 1237);
result = prime * result
+ ((urlBuilder == null) ? 0 : urlBuilder.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SliceClient other = (SliceClient) obj;
if (isFinished != other.isFinished)
return false;
if (urlBuilder == null) {
if (other.urlBuilder != null)
return false;
} else if (!urlBuilder.equals(other.urlBuilder))
return false;
return true;
}
public void setGet(boolean get) {
urlBuilder.setGet(get);
}
public boolean isWritingExpected() {
return urlBuilder.isWritingExpected();
}
public void setWritingExpected(boolean expected) {
urlBuilder.setWritingExpected(expected);
}
}