From a2a646e1f5068c31ce011ae7c68b8d7f28d45dc5 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 8 Jan 2020 17:12:10 +0000 Subject: [PATCH] i2psnark: Support "preview" HTML5 play for partial files --- .../java/src/org/klomp/snark/Storage.java | 33 +++++++++++---- .../src/org/klomp/snark/web/BasicServlet.java | 42 +++++++++++++++++-- .../org/klomp/snark/web/I2PSnarkServlet.java | 31 +++++++++----- .../java/src/org/klomp/snark/web/Sorters.java | 11 +++++ 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index d3fa8df10a..59cb8d55f7 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -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; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java index 07e9bedbb8..2f36c08438 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java @@ -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; diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 6ed333882a..21b314da94 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -3375,9 +3375,11 @@ public class I2PSnarkServlet extends BasicServlet { List fileList = new ArrayList(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(""); - 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 = "
" + _t("Preview") + ": " + + (new DecimalFormat("0.00%")).format(pct); + } if (isAudio || isVideo) { // HTML5 if (isAudio) buf.append(""); + buf.append(" controls>"); } - buf.append(""); + buf.append(""); if (mime.startsWith("image/")) { // thumbnail buf.append("\"\""); + .append(ppath).append("\">"); } else { buf.append(toImg(icon, _t("Open"))).append(""); } @@ -3588,6 +3595,8 @@ public class I2PSnarkServlet extends BasicServlet { buf.append(DataHelper.escapeHTML(item.getName())); if (complete) buf.append(""); + if (preview != null) + buf.append(preview); buf.append(""); if (!item.isDirectory()) buf.append(formatSize(length)); @@ -3755,7 +3764,7 @@ public class I2PSnarkServlet extends BasicServlet { return null; List fileList = new ArrayList(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)); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java b/apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java index 6f1f386b48..56c3115e75 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java @@ -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();