i2psnark: Support "preview" HTML5 play for partial files

This commit is contained in:
zzz
2020-01-08 17:12:10 +00:00
parent ea05af42fa
commit a2a646e1f5
4 changed files with 94 additions and 23 deletions

View File

@ -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;
}
/**

View File

@ -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;

View File

@ -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));
}

View File

@ -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();