package edu.washington.cs.oneswarm.ui.gwt.server;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.gudy.azureus2.core3.util.Base32;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentException;
import org.gudy.azureus2.plugins.torrent.TorrentFile;
import org.mortbay.jetty.HttpHeaders;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import edu.washington.cs.oneswarm.ui.gwt.CoreInterface;
import edu.washington.cs.oneswarm.ui.gwt.CoreTools;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.FileTypeFilter;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.ImageConstants;
import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants;
import edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg.FFMpegAsyncOperationManager;
import edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg.FFMpegAsyncOperationManager.DataNotAvailableException;
public class PreviewImageGenerator extends javax.servlet.http.HttpServlet {
public static final int PREVIEW_MAX_WAIT_TIME = 2;
public static final String AUDIO_INFO_FILE = "audio_info.xml";
private static final long serialVersionUID = 1L;
CoreInterface coreInterface = null;
private static Logger logger = Logger.getLogger(PreviewImageGenerator.class.getName());
// Map<String, Boolean> preview_fails = Collections.synchronizedMap(new
// HashMap<String, Boolean>());
// FileOutputStream preview_fails_file = null;
public PreviewImageGenerator() {
logger.info("started preview image generator servlet (PreviewImageGenerator)");
// load_preview_fails();
}
// private void load_preview_fails() {
// String path = SystemProperties.getMetaInfoPath() + "/preview_fails";
// try {
// FileInputStream fis = new FileInputStream(path);
// BufferedReader in = new BufferedReader(new InputStreamReader(fis));
// while (in.ready()) {
// String hash = in.readLine();
// // System.out.println("preview fails line: " + hash);
// preview_fails.put(hash, false);
// }
// logger.fine("loaded " + preview_fails.size() + " failed hashes");
// fis.close();
// } catch (Exception e) {
// logger.fine("Couldn't load preview image failures: " + e.toString());
// try {
// (new File(path)).createNewFile();
// } catch (IOException e1) {
// Debug.out("couldn't _create_ preview fails file: ", e1);
// }
// }
//
// try {
// preview_fails_file = new FileOutputStream(path, true);
// } catch (FileNotFoundException e) {
// logger.fine("couldn't open preview fail file");
// preview_fails_file = null;
// }
//
// /**
// * TODO: clean up this file sometimes. if( size > 3000 ) wait 2 minutes
// * (until all previews requested) and then save all that were touched
// */
// }
Set<String> processingHashes = Collections.synchronizedSet(new HashSet<String>());
public void doGet(HttpServletRequest request, HttpServletResponse response) {
if (request.getParameter("path") != null) {
processArbitraryPathRequest(request, response);
return;
}
logger.finer("preview image generator servlet: get: " + request.getParameter("infohash"));
String hint = request.getParameter("type");
if (coreInterface == null)
coreInterface = new CoreInterface(AzureusCoreImpl.getSingleton().getPluginManager()
.getDefaultPluginInterface());
String hash = null;
try {
hash = request.getParameter("infohash");
/**
* this is not reentrant w.r.t. the same hash
*/
synchronized (processingHashes) {
while (processingHashes.contains(hash)) {
Thread.sleep(100);
}
processingHashes.add(hash);
}
InputStream inputstream;
File imageFile = getImageFile(hash);
// if (check_previous_preview_fail(hash) == false) {
// imageFile = getImageFile(hash);
// } else {
// logger.finest("skipping presumed fail preview: " + hash);
// }
if (imageFile == null) {
// check if we have gotten this from a friend
imageFile = getFromFriendImageFile(hash);
if (imageFile == null) {
logger.finest("get hash from friend: " + hash + " failed");
/**
* Don't try to generate a preview for this if we don't have
* it from a friend -- we don't have any data and the
* adapters will crash
*/
if (coreInterface.isF2FHash(hash)) {
response.setStatus(302);
if (hint != null) {
if (hint.equals("audio")) {
response.setHeader("Location", ImageConstants.ICON_AUDIO_BROWSER);
return;
} else if (hint.equals("video")) {
response.setHeader("Location", ImageConstants.ICON_VIDEO_BROWSER);
return;
}
}
response.setHeader("Location", ImageConstants.ICON_DOCUMENT_CENTER);
return;
} else {
logger.finer(hash + " not f2f, trying preview generation");
}
}
}
logger.finest("serving image: "
+ (imageFile != null ? imageFile.getAbsolutePath() : "(null)") + " / " + hash);
if (imageFile != null) {
long last_modified = imageFile.lastModified();
if (last_modified > 0) {
long if_modified = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (if_modified > 0 && last_modified / 1000 <= if_modified / 1000) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
// ((Request) request).setHandled(true);
logger.finest("not modified, " + last_modified);
return;
}
// ok, we have to serve the file, set the header
response.setDateHeader(HttpHeaders.LAST_MODIFIED, last_modified);
logger.finest("setting last modified: " + last_modified);
}
// and cache control
int secondsToCache = 24 * 60 * 60;
response.setDateHeader(HttpHeaders.EXPIRES, System.currentTimeMillis()
+ (secondsToCache * 1000));
response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=" + secondsToCache
+ ", private");
inputstream = new BufferedInputStream(new FileInputStream(imageFile));
response.setContentType("image/png");
response.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputstream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputstream.read(buffer)) > 0) {
outputstream.write(buffer, 0, len);
}
outputstream.flush();
outputstream.close();
inputstream.close();
// response.getWriter().println("<h1>Hello</h1>");
// ((Request) request).setHandled(true);
} else {
logger.finest("trying to do a redirect instead: ");
/**
* Check for audio types
*/
int audio_subfiles = 0, everything_else = 0;
Download download = coreInterface.getDownload(hash);
if (download == null) {
System.err.println("null download: " + (hash));
response.setStatus(302);
response.setHeader("Location", ImageConstants.ICON_DOCUMENT_CENTER);
return;
}
for (TorrentFile file : download.getTorrent().getFiles()) {
if (FileTypeFilter.match(file.getName(), FileTypeFilter.Audio)) {
audio_subfiles++;
} else {
everything_else++;
}
}
logger.finest("audio check : " + audio_subfiles + " / " + everything_else);
// if (download.isComplete() == true) {
// record_no_preview(hash);
// }
if (audio_subfiles > everything_else) {
response.setStatus(302);
response.setHeader("Location", ImageConstants.ICON_AUDIO_BROWSER);
} else {
// System.out.println("trying to do a redirect instead: ");
response.setStatus(302);
if (hint != null) {
if (hint.equals("audio")) {
response.setHeader("Location", ImageConstants.ICON_AUDIO_BROWSER);
return;
} else if (hint.equals("video")) {
response.setHeader("Location", ImageConstants.ICON_VIDEO_BROWSER);
return;
}
}
response.setHeader("Location", ImageConstants.ICON_DOCUMENT_CENTER);
}
return;
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (hash != null) {
processingHashes.remove(hash);
}
}
}
private void processArbitraryPathRequest(HttpServletRequest request,
HttpServletResponse response) {
int scale = -1;
if (request.getParameter("scale") != null) {
scale = Integer.parseInt(request.getParameter("scale"));
}
try {
response.setContentType("image/png");
previewImageForArbitraryPath(URLDecoder.decode(request.getParameter("path"), "UTF-8"),
scale, response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
public static void previewImageForArbitraryPath(String path, int scale, OutputStream outStream)
throws IOException {
FileInputStream fis = new FileInputStream(path);
ImageIO.setUseCache(false);
Image base = ImageIO.read(fis);
if (base == null) {
throw new IOException("unable to read image");
}
if (scale != -1) {
double resizeFactorWidth = base.getWidth(null) / (double) scale;
double resizeFactorHeight = base.getHeight(null) / (double) scale;
double resizeFactor = Math.max(resizeFactorWidth, resizeFactorHeight);
base = base.getScaledInstance((int) (base.getWidth(null) / resizeFactor),
(int) (base.getHeight(null) / resizeFactor), Image.SCALE_SMOOTH);
}
BufferedImage img = new BufferedImage(base.getWidth(null), base.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D graph = img.createGraphics();
graph.drawImage(base, null, null);
ImageIO.write(img, "png", outStream);
img.flush();
base.flush();
graph.dispose();
}
// private synchronized void record_no_preview(String hash) {
// if (preview_fails.containsKey(hash)) {
// return;
// }
//
// preview_fails.put(hash, true);
//
// if (preview_fails_file != null) {
// try {
// preview_fails_file.write((hash + "\n").getBytes());
// preview_fails_file.flush();
// } catch (IOException e) {
// e.printStackTrace();
// preview_fails_file = null;
// }
// } else {
// Debug.out("preview fails file is null!");
// }
// }
// private synchronized boolean check_previous_preview_fail(String hash) {
// if (preview_fails.containsKey(hash)) {
// preview_fails.put(hash, true);
// return true;
// }
// return false;
// }
private File getFromFriendImageFile(String torrentID) throws TorrentException {
torrentID = torrentID.substring(OneSwarmConstants.BITTORRENT_MAGNET_PREFIX.length());
byte[] torrentHash = Base32.decode(torrentID);
File metainfoDir = CoreInterface.getMetaInfoDir(torrentHash);
File imageFile = new File(metainfoDir, "preview_friend.png");
try {
logger.finest("checking: " + imageFile.getCanonicalPath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (imageFile.exists()) {
return imageFile;
} else {
return null;
}
}
public File getImageFile(String inInfohash) {
Download download = coreInterface.getDownload(inInfohash);
if (download == null) {
return null;
}
if (!download.isComplete()) {
return null;
}
try {
Torrent torrent = download.getTorrent();
TorrentFile activeFile = CoreTools.getBiggestPreviewableFile(download);
if (activeFile == null) {
logger.finest("no file found for " + inInfohash);
return null;
}
DiskManagerFileInfo fileInfo = CoreTools.getDiskManagerFileInfo(activeFile, download);
if (fileInfo == null) {
logger.finer("no disk manager file info: " + inInfohash);
return null;
}
try {
return FFMpegAsyncOperationManager.getInstance().getPreviewImage(torrent.getHash(),
fileInfo.getFile(), PREVIEW_MAX_WAIT_TIME, TimeUnit.SECONDS);
} catch (DataNotAvailableException e) {
return null;
}
} catch (TorrentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
// private boolean checkPrevImageGenerationFailed(byte[] hash) {
// String encoded = ByteFormatter.encodeString(hash);
// if (preview_fails.containsKey(encoded)) {
// preview_fails.put(encoded, true);
// return true;
// }
// return false;
// }
// public static boolean createImageFile(Download download,
// DiskManagerFileInfo fileInfo, File imageFile) throws FFMpegException,
// IOException {
// // ok, no file, lets create it if the torrent is downloaded
//
// boolean completed = false;
//
// // only cache "failures" if all files are completed
// try {
// if (download.isComplete(true)) {
// completed = true;
// }
//
// InOrderType type = InOrderType.getType(fileInfo.getFile().getName());
// if (completed && type.type.equals(FileTypeFilter.Audio)) {
// boolean good =
// MagicDirectoryManager.generate_audio_preview(fileInfo.getFile(),
// imageFile);
// if (!good) {
// FFMpegTools.setPrevImageGenerationFailed(imageFile);
// } else {
// logger.fine("generated audio preview: " + download.getName());
// DownloadManager real_dl =
// AzureusCoreImpl.getSingleton().getGlobalManager().getDownloadManager(new
// HashWrapper(download.getTorrent().getHash()));
// TOTorrent realTorrent = real_dl.getTorrent();
// if (MagicDirectoryManager.generate_audio_info_xml(new
// File(download.getSavePath()), realTorrent, new
// File(imageFile.getParentFile(), AUDIO_INFO_FILE))) {
// MagicDirectoryManager.bind_audio_xml(real_dl);
// }
// }
// logger.finest("trying to generate on-the-fly preview for audio: " +
// fileInfo.getFile().getName() + " good? " + good);
// return good;
// }
//
// if (completed) {
// FFMpegTools.createImageFile(fileInfo.getFile(), imageFile, completed);
//
// if (imageFile.exists()) {
// logger.fine("created image file: " + imageFile.getCanonicalPath());
// return true;
// } else {
// if (completed) {
// FFMpegTools.setPrevImageGenerationFailed(imageFile);
// }
// logger.fine("Image file creation failed: " +
// imageFile.getCanonicalPath());
// }
//
// }
// } catch( Exception e ) {
// e.printStackTrace();
// }
//
// return false;
// }
// private Map<String, String> parseRequestString(String target) {
// Map<String, String> map = new HashMap<String, String>();
//
// // remove the initial slash
// if (target.length() > 0 &&
// target.startsWith(OneSwarmConstants.videoImagePath)) {
// target = target.substring(OneSwarmConstants.videoImagePath.length());
// }
// String[] split = target.split("/");
// if (split.length > 0) {
// String torrentID = split[0];
// map.put(OneSwarmConstants.WEB_PARAM_TORRENT_ID, torrentID);
//
// if (split.length > 1) {
// String path = target.substring((torrentID + "/").length());
// map.put(OneSwarmConstants.WEB_PARAM_VIDEO_PATH, path);
// }
// }
// return map;
//
// }
}