forked from I2P_Developers/i2p.i2p
i2psnark: Support "preview" HTML5 play for partial files
This commit is contained in:
@ -427,15 +427,18 @@ public class Storage implements Closeable
|
||||
****/
|
||||
|
||||
/**
|
||||
* For efficiency, calculate remaining bytes for all files at once
|
||||
* For efficiency, calculate remaining bytes for all files at once.
|
||||
* Remaining bytes is rv[0]. Preview bytes is rv[1].
|
||||
*
|
||||
* @return number of bytes remaining for each file, use indexOf() to get index for a file
|
||||
* @return number of bytes remaining and number of bytes available for a preview for each file, use indexOf() to get index for a file
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public long[] remaining() {
|
||||
public long[][] remaining() {
|
||||
long[] rv = new long[_torrentFiles.size()];
|
||||
long[] pv = new long[_torrentFiles.size()];
|
||||
long[][] rva = new long[][] { rv, pv };
|
||||
if (complete())
|
||||
return rv;
|
||||
return rva;
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
@ -443,10 +446,23 @@ public class Storage implements Closeable
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / piece_size);
|
||||
long rvi = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rvi = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
long pvi = 0;
|
||||
long first = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
if (bitfield.get(pc))
|
||||
pvi = first;
|
||||
else
|
||||
rvi = first;
|
||||
boolean preview = true;
|
||||
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (bitfield.get(j)) {
|
||||
if (preview) {
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
pvi += piece_size;
|
||||
else
|
||||
pvi += end - (((long)j) * piece_size);
|
||||
}
|
||||
} else {
|
||||
preview = false;
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
rvi += piece_size;
|
||||
else
|
||||
@ -454,9 +470,10 @@ public class Storage implements Closeable
|
||||
}
|
||||
}
|
||||
rv[i] = rvi;
|
||||
pv[i] = pvi;
|
||||
bytes += tf.length;
|
||||
}
|
||||
return rv;
|
||||
return rva;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,7 +179,7 @@ class BasicServlet extends HttpServlet
|
||||
* @param pathInContext The path to find a resource for.
|
||||
* @return The resource to serve or null. Returns null for directories
|
||||
*/
|
||||
public HttpContent getContent(String pathInContext)
|
||||
public HttpContent getContent(String pathInContext, long limit)
|
||||
{
|
||||
HttpContent r = null;
|
||||
if (_warBase != null && pathInContext.startsWith(_warBase)) {
|
||||
@ -187,8 +187,12 @@ class BasicServlet extends HttpServlet
|
||||
} else {
|
||||
File f = getResource(pathInContext);
|
||||
// exists && !directory
|
||||
if (f != null && f.isFile())
|
||||
r = new FileContent(f);
|
||||
if (f != null && f.isFile()) {
|
||||
if (limit > 0)
|
||||
r = new LimitFileContent(f, limit);
|
||||
else
|
||||
r = new FileContent(f);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@ -202,10 +206,17 @@ class BasicServlet extends HttpServlet
|
||||
String pathInfo=request.getPathInfo();
|
||||
// ??? right??
|
||||
String pathInContext = addPaths(servletpath, pathInfo);
|
||||
long limit = 0;
|
||||
String slimit = request.getParameter("limit");
|
||||
if (slimit != null) {
|
||||
try {
|
||||
limit = Long.parseLong(slimit);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
// Find the resource and content
|
||||
try {
|
||||
HttpContent content = getContent(pathInContext);
|
||||
HttpContent content = getContent(pathInContext, limit);
|
||||
|
||||
// Handle resource
|
||||
if (content == null) {
|
||||
@ -468,6 +479,29 @@ class BasicServlet extends HttpServlet
|
||||
public String toString() { return "File \"" + _file + '"'; }
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.45
|
||||
*/
|
||||
private class LimitFileContent extends FileContent
|
||||
{
|
||||
private final long _limit;
|
||||
|
||||
/**
|
||||
* @param limit must be less than file length
|
||||
*/
|
||||
public LimitFileContent(File file, long limit)
|
||||
{
|
||||
super(file);
|
||||
_limit = Math.min(limit, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength()
|
||||
{
|
||||
return _limit;
|
||||
}
|
||||
}
|
||||
|
||||
private class JarContent implements HttpContent
|
||||
{
|
||||
private final String _path;
|
||||
|
@ -3375,9 +3375,11 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
List<Sorters.FileAndIndex> fileList = new ArrayList<Sorters.FileAndIndex>(ls.length);
|
||||
// precompute remaining for all files for efficiency
|
||||
long[] remainingArray = (storage != null) ? storage.remaining() : null;
|
||||
long[][] arrays = (storage != null) ? storage.remaining() : null;
|
||||
long[] remainingArray = (arrays != null) ? arrays[0] : null;
|
||||
long[] previewArray = (arrays != null) ? arrays[1] : null;
|
||||
for (int i = 0; i < ls.length; i++) {
|
||||
fileList.add(new Sorters.FileAndIndex(ls[i], storage, remainingArray));
|
||||
fileList.add(new Sorters.FileAndIndex(ls[i], storage, remainingArray, previewArray));
|
||||
}
|
||||
|
||||
boolean showSort = fileList.size() > 1;
|
||||
@ -3546,25 +3548,30 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
if (mime == null)
|
||||
mime = "";
|
||||
|
||||
boolean isAudio = false;
|
||||
boolean isVideo = false;
|
||||
boolean isAudio = isAudio(mime);
|
||||
boolean isVideo = !isAudio && isVideo(mime);
|
||||
buf.append("<td class=\"snarkFileIcon\">");
|
||||
if (complete) {
|
||||
isAudio = isAudio(mime);
|
||||
isVideo = !isAudio && isVideo(mime);
|
||||
String preview = null;
|
||||
if (complete || (isAudio && fai.preview > 1024*100) || (isVideo && fai.preview > 1024*1024)) {
|
||||
String ppath = complete ? path : path + "?limit=" + fai.preview;
|
||||
if (!complete) {
|
||||
double pct = fai.preview / (double) fai.length;
|
||||
preview = "<br>" + _t("Preview") + ": " +
|
||||
(new DecimalFormat("0.00%")).format(pct);
|
||||
}
|
||||
if (isAudio || isVideo) {
|
||||
// HTML5
|
||||
if (isAudio)
|
||||
buf.append("<audio");
|
||||
else
|
||||
buf.append("<video");
|
||||
buf.append(" controls><source src=\"").append(path).append("\" type=\"").append(mime).append("\">");
|
||||
buf.append(" controls><source src=\"").append(ppath).append("\" type=\"").append(mime).append("\">");
|
||||
}
|
||||
buf.append("<a href=\"").append(path).append("\">");
|
||||
buf.append("<a href=\"").append(ppath).append("\">");
|
||||
if (mime.startsWith("image/")) {
|
||||
// thumbnail
|
||||
buf.append("<img alt=\"\" border=\"0\" class=\"thumb\" src=\"")
|
||||
.append(path).append("\"></a>");
|
||||
.append(ppath).append("\"></a>");
|
||||
} else {
|
||||
buf.append(toImg(icon, _t("Open"))).append("</a>");
|
||||
}
|
||||
@ -3588,6 +3595,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append(DataHelper.escapeHTML(item.getName()));
|
||||
if (complete)
|
||||
buf.append("</a>");
|
||||
if (preview != null)
|
||||
buf.append(preview);
|
||||
buf.append("</td><td align=right class=\"snarkFileSize\">");
|
||||
if (!item.isDirectory())
|
||||
buf.append(formatSize(length));
|
||||
@ -3755,7 +3764,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
return null;
|
||||
List<Sorters.FileAndIndex> fileList = new ArrayList<Sorters.FileAndIndex>(ls.length);
|
||||
// precompute remaining for all files for efficiency
|
||||
long[] remainingArray = (storage != null) ? storage.remaining() : null;
|
||||
long[] remainingArray = (storage != null) ? storage.remaining()[0] : null;
|
||||
for (int i = 0; i < ls.length; i++) {
|
||||
fileList.add(new Sorters.FileAndIndex(ls[i], storage, remainingArray));
|
||||
}
|
||||
|
@ -374,6 +374,7 @@ class Sorters {
|
||||
public final boolean isDirectory;
|
||||
public final long length;
|
||||
public final long remaining;
|
||||
public final long preview;
|
||||
public final int priority;
|
||||
public final int index;
|
||||
|
||||
@ -382,15 +383,25 @@ class Sorters {
|
||||
* @param remainingArray precomputed, non-null iff storage is non-null
|
||||
*/
|
||||
public FileAndIndex(File file, Storage storage, long[] remainingArray) {
|
||||
this(file, storage, remainingArray, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param storage may be null
|
||||
* @param remainingArray precomputed, non-null iff storage is non-null
|
||||
*/
|
||||
public FileAndIndex(File file, Storage storage, long[] remainingArray, long[] previewArray) {
|
||||
this.file = file;
|
||||
index = storage != null ? storage.indexOf(file) : -1;
|
||||
if (index >= 0) {
|
||||
isDirectory = false;
|
||||
remaining = remainingArray[index];
|
||||
preview = previewArray != null ? previewArray[index] : 0;
|
||||
priority = storage.getPriority(index);
|
||||
} else {
|
||||
isDirectory = file.isDirectory();
|
||||
remaining = -1;
|
||||
preview = 0;
|
||||
priority = -999;
|
||||
}
|
||||
length = isDirectory ? 0 : file.length();
|
||||
|
Reference in New Issue
Block a user