diff --git a/src/main/java/net/pms/encoders/FFmpegHlsVideo.java b/src/main/java/net/pms/encoders/FFmpegHlsVideo.java index 8c992837eea..e61e3d1b676 100644 --- a/src/main/java/net/pms/encoders/FFmpegHlsVideo.java +++ b/src/main/java/net/pms/encoders/FFmpegHlsVideo.java @@ -105,7 +105,11 @@ public synchronized ProcessWrapper launchTranscode( cmdList.add("-sn"); } cmdList.add("-i"); - cmdList.add(filename); + if (params.getStdIn() != null) { + cmdList.add("pipe:"); + } else { + cmdList.add(filename); + } if (needSubtitle) { cmdList.add("-map"); diff --git a/src/main/java/net/pms/network/mediaserver/servlets/MediaServerServlet.java b/src/main/java/net/pms/network/mediaserver/servlets/MediaServerServlet.java index 9df2d92fd06..5e0fe6a383b 100644 --- a/src/main/java/net/pms/network/mediaserver/servlets/MediaServerServlet.java +++ b/src/main/java/net/pms/network/mediaserver/servlets/MediaServerServlet.java @@ -343,13 +343,13 @@ private static void sendMediaResponse(HttpServletRequest req, HttpServletRespons //only time seek, transcoded resp.setHeader("ContentFeatures.DLNA.ORG", "DLNA.ORG_OP=10;DLNA.ORG_CI=01;DLNA.ORG_FLAGS=01700000000000000000000000000000"); - if (item.getMediaInfo().getDurationInSeconds() > 0) { + if (item.getMediaInfo() != null && item.getMediaInfo().getDurationInSeconds() > 0) { String durationStr = String.format(Locale.ENGLISH, "%.3f", item.getMediaInfo().getDurationInSeconds()); resp.setHeader("TimeSeekRange.dlna.org", "npt=0-" + durationStr + "/" + durationStr); resp.setHeader("X-AvailableSeekRange", "npt=0-" + durationStr); } } - if (samsungMediaInfo != null && item.getMediaInfo().getDurationInSeconds() > 0) { + if (samsungMediaInfo != null && item.getMediaInfo() != null && item.getMediaInfo().getDurationInSeconds() > 0) { resp.setHeader("MediaInfo.sec", "SEC_Duration=" + (long) (item.getMediaInfo().getDurationInSeconds() * 1000)); } @@ -578,7 +578,7 @@ private static void sendMediaResponse(HttpServletRequest req, HttpServletRespons resp.setHeader("ContentFeatures.DLNA.ORG", DlnaHelper.getDlnaContentFeatures(item)); } - if (samsungMediaInfo != null && item.getMediaInfo().getDurationInSeconds() > 0) { + if (samsungMediaInfo != null && item.getMediaInfo() != null && item.getMediaInfo().getDurationInSeconds() > 0) { resp.setHeader("MediaInfo.sec", "SEC_Duration=" + (long) (item.getMediaInfo().getDurationInSeconds() * 1000)); } @@ -591,7 +591,7 @@ private static void sendMediaResponse(HttpServletRequest req, HttpServletRespons } } - if (timeseekrange.isStartOffsetAvailable()) { + if (timeseekrange.isStartOffsetAvailable() && item.getMediaInfo() != null) { // Add timeseek information headers. String timeseekValue = StringUtil.formatDLNADuration(timeseekrange.getStartOrZero()); String timetotalValue = item.getMediaInfo().getDurationString(); diff --git a/src/main/java/net/pms/parsers/FFmpegParser.java b/src/main/java/net/pms/parsers/FFmpegParser.java index 4abb84b69b8..de8ec0a8f18 100644 --- a/src/main/java/net/pms/parsers/FFmpegParser.java +++ b/src/main/java/net/pms/parsers/FFmpegParser.java @@ -78,18 +78,18 @@ public static void parse(MediaInfo media, InputFile inputFile, Format ext, int t boolean ffmpegParsing = true; - if (type == Format.AUDIO || ext instanceof AudioAsVideo) { + if (file != null && type == Format.AUDIO || ext instanceof AudioAsVideo) { ffmpegParsing = false; JaudiotaggerParser.parse(media, file, ext); } - if (type == Format.IMAGE && file != null) { + if (type == Format.IMAGE) { try { ffmpegParsing = false; - MetadataExtractorParser.parse(file, media); + MetadataExtractorParser.parse(inputFile, media); media.setImageCount(media.getImageCount() + 1); } catch (IOException e) { - LOGGER.debug("Error parsing image \"{}\", switching to FFmpeg: {}", file.getAbsolutePath(), e.getMessage()); + LOGGER.debug("Error parsing image \"{}\", switching to FFmpeg: {}", inputFile.getFilename(), e.getMessage()); LOGGER.trace("", e); ffmpegParsing = true; } @@ -305,7 +305,7 @@ public static void parseFFmpegInfo(MediaInfo media, List lines, String i if (line.startsWith("Output")) { matches = false; } else if (line.startsWith("Input")) { - if (line.contains(input)) { + if (line.contains(input) || line.contains("from 'fd:':")) { matches = true; media.setContainer(line.substring(10, line.indexOf(',', 11)).trim()); diff --git a/src/main/java/net/pms/parsers/MetadataExtractorParser.java b/src/main/java/net/pms/parsers/MetadataExtractorParser.java index a1984470bca..c8ae8bb1a92 100644 --- a/src/main/java/net/pms/parsers/MetadataExtractorParser.java +++ b/src/main/java/net/pms/parsers/MetadataExtractorParser.java @@ -54,6 +54,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collection; @@ -74,6 +76,7 @@ import net.pms.image.ImageIOTools; import net.pms.image.ImageInfo; import net.pms.media.MediaInfo; +import net.pms.util.InputFile; import net.pms.util.ParseException; import net.pms.util.ResettableInputStream; import net.pms.util.UnknownFormatException; @@ -91,6 +94,22 @@ public class MetadataExtractorParser { private MetadataExtractorParser() { } + public static void parse(InputFile inputFile, MediaInfo mediaInfo) throws IOException { + if (inputFile.getFile() != null) { + parse(inputFile.getFile(), mediaInfo); + } else if (inputFile.getPush() != null) { + try (PipedOutputStream out = new PipedOutputStream(); InputStream fis = new PipedInputStream(out)) { + inputFile.getPush().push(out); + parseStream(fis, inputFile.getSize(), inputFile.getFilename(), mediaInfo); + } catch (IOException e) { + LOGGER.debug("Error reading \"{}\": {}", inputFile.getFilename(), e.getMessage()); + LOGGER.trace("", e); + } + } else { + throw new IllegalArgumentException("parseImage: inputFile cannot be null"); + } + } + /** * Parses an image file and stores the results in the given * {@link MediaInfo}. Parsing is performed using both @@ -110,9 +129,36 @@ private MetadataExtractorParser() { * */ public static void parse(File file, MediaInfo mediaInfo) throws IOException { + long size = file.length(); + InputStream fis = Files.newInputStream(file.toPath()); + String filename = file.getAbsolutePath(); + parseStream(fis, size, filename, mediaInfo); + } + + /** + * Parses an image stream and stores the results in the given + * {@link MediaInfo}. Parsing is performed using both + * Metadata Extractor + * and {@link ImageIO}. While Metadata Extractor offers more detailed + * information, {@link ImageIO} offers information that is convenient for + * image transformation with {@link ImageIO}. Parsing will be performed if + * just one of the two methods produces results, but some details will be + * missing if either one failed. + *

+ * This method consumes and closes {@code inputStream}. + * + * @param stream the {@link InputStream} to parse. + * @param size the size. + * @param filename the filename for debug. + * @param mediaInfo the {@link MediaInfo} instance to store the parsing + * results to. + * @throws IOException if an IO error occurs or no information can be parsed. + * + */ + private static void parseStream(InputStream stream, long size, String filename, MediaInfo mediaInfo) throws IOException { final int maxBuffer = 1048576; // 1 MB - if (file == null) { - throw new IllegalArgumentException("parseImage: file cannot be null"); + if (stream == null) { + throw new IllegalArgumentException("parseImage: stream cannot be null"); } if (mediaInfo == null) { throw new IllegalArgumentException("parseImage: media cannot be null"); @@ -120,10 +166,9 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { boolean trace = LOGGER.isTraceEnabled(); if (trace) { - LOGGER.trace("Parsing image file \"{}\"", file.getAbsolutePath()); + LOGGER.trace("Parsing image file \"{}\"", filename); } - long size = file.length(); - ResettableInputStream inputStream = new ResettableInputStream(Files.newInputStream(file.toPath()), maxBuffer); + ResettableInputStream inputStream = new ResettableInputStream(stream, maxBuffer); try { Metadata metadata; FileType fileType = null; @@ -132,14 +177,14 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { metadata = getMetadata(inputStream, fileType); } catch (IOException e) { metadata = new Metadata(); - LOGGER.debug("Error reading \"{}\": {}", file.getAbsolutePath(), e.getMessage()); + LOGGER.debug("Error reading \"{}\": {}", filename, e.getMessage()); LOGGER.trace("", e); } catch (ImageProcessingException e) { metadata = new Metadata(); LOGGER.debug( "Error parsing {} metadata for \"{}\": {}", fileType != null ? fileType.toString().toUpperCase(Locale.ROOT) : "null", - file.getAbsolutePath(), + filename, e.getMessage() ); LOGGER.trace("", e); @@ -157,7 +202,7 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { } else { // If we can't reset it, close it and create a new inputStream.close(); - inputStream = new ResettableInputStream(Files.newInputStream(file.toPath()), maxBuffer); + inputStream = new ResettableInputStream(stream, maxBuffer); } ImageInfo imageInfo = null; try { @@ -165,13 +210,13 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { } catch (UnknownFormatException | IIOException | ParseException e) { if (format == null) { throw new UnknownFormatException( - "Unable to recognize image format for \"" + file.getAbsolutePath() + "\" - parsing failed", + "Unable to recognize image format for \"" + filename + "\" - parsing failed", e ); } LOGGER.debug( "Unable to parse \"{}\" with ImageIO because the format is unsupported, image information will be limited", - file.getAbsolutePath() + filename ); LOGGER.trace("ImageIO parse failure reason: {}", e.getMessage()); @@ -180,14 +225,14 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { try { imageInfo = ImageInfo.create(metadata, format, size, true, true); } catch (ParseException pe) { - LOGGER.debug("Unable to parse metadata for \"{}\": {}", file.getAbsolutePath(), pe.getMessage()); + LOGGER.debug("Unable to parse metadata for \"{}\": {}", filename, pe.getMessage()); LOGGER.trace("", pe); } } } if (imageInfo == null && format == null) { - throw new ParseException("Parsing of \"" + file.getAbsolutePath() + "\" failed"); + throw new ParseException("Parsing of \"" + filename + "\" failed"); } if (format == null && imageInfo != null) { @@ -204,7 +249,7 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { LOGGER.trace( "Correcting misidentified image format ARW to {} for \"{}\"", format, - file.getAbsolutePath() + filename ); } else { /* @@ -228,14 +273,14 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { LOGGER.trace( "Correcting misidentified image format TIFF to {} for \"{}\"", format.toString(), - file.getAbsolutePath() + filename ); } } else { LOGGER.debug( "Image parsing for \"{}\" was inconclusive, metadata parsing " + "detected {} format while ImageIO detected {}. Choosing {}.", - file.getAbsolutePath(), + filename, format, imageInfo.getFormat(), imageInfo.getFormat() @@ -248,11 +293,11 @@ public static void parse(File file, MediaInfo mediaInfo) throws IOException { if (format != null) { mediaInfo.setContainer(format.toFormatConfiguration()); } - mediaInfo.setSize(file.length()); + mediaInfo.setSize(size); mediaInfo.setMediaParser(PARSER_NAME); Parser.postParse(mediaInfo, Format.IMAGE); if (trace) { - LOGGER.trace("Parsing of image \"{}\" completed", file.getName()); + LOGGER.trace("Parsing of image \"{}\" completed", filename); } } finally { inputStream.close(); diff --git a/src/main/java/net/pms/store/container/RarredFile.java b/src/main/java/net/pms/store/container/RarredFile.java index cf288dd451a..27b6eab97ea 100644 --- a/src/main/java/net/pms/store/container/RarredFile.java +++ b/src/main/java/net/pms/store/container/RarredFile.java @@ -16,64 +16,35 @@ */ package net.pms.store.container; -import com.github.junrar.Archive; -import com.github.junrar.exception.RarException; -import com.github.junrar.rarfile.FileHeader; -import com.github.junrar.volume.FileVolumeManager; import java.io.File; -import java.io.IOException; -import java.util.List; import net.pms.renderers.Renderer; -import net.pms.store.StoreContainer; -import net.pms.store.item.RarredEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import net.pms.store.SystemFileResource; -public class RarredFile extends StoreContainer { - private static final Logger LOGGER = LoggerFactory.getLogger(RarredFile.class); - private File f; - private Archive rarFile; +public class RarredFile extends RarredFolder implements SystemFileResource { - public RarredFile(Renderer renderer, File f) { - super(renderer, f.getName(), null); - this.f = f; - setLastModified(f.lastModified()); - - try { - rarFile = new Archive(new FileVolumeManager(f), null, null); - List headers = rarFile.getFileHeaders(); - - for (FileHeader fh : headers) { - // if (fh.getFullUnpackSize() < MAX_ARCHIVE_ENTRY_SIZE && fh.getFullPackSize() < MAX_ARCHIVE_ENTRY_SIZE) - addChild(new RarredEntry(renderer, fh.getFileName(), f, fh.getFileName(), fh.getFullUnpackSize())); - } + public RarredFile(Renderer renderer, File file) { + super(renderer, file, ""); + setLastModified(file.lastModified()); + } - rarFile.close(); - } catch (RarException | IOException e) { - LOGGER.error(null, e); - } + @Override + public String getName() { + return file.getName(); } @Override public long length() { - return f.length(); + return file.length(); } @Override public String getSystemName() { - return f.getAbsolutePath(); + return file.getAbsolutePath(); } @Override - public boolean isValid() { - boolean t = false; - - try { - t = f.exists() && !rarFile.isEncrypted(); - } catch (RarException th) { - LOGGER.debug("Caught exception", th); - } - - return t; + public File getSystemFile() { + return file; } + } diff --git a/src/main/java/net/pms/store/container/RarredFolder.java b/src/main/java/net/pms/store/container/RarredFolder.java new file mode 100644 index 00000000000..2aeb8f9c8a3 --- /dev/null +++ b/src/main/java/net/pms/store/container/RarredFolder.java @@ -0,0 +1,99 @@ +/* + * This file is part of Universal Media Server, based on PS3 Media Server. + * + * This program is a free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License only. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package net.pms.store.container; + +import com.github.junrar.Archive; +import com.github.junrar.exception.RarException; +import com.github.junrar.rarfile.FileHeader; +import com.github.junrar.volume.FileVolumeManager; +import java.io.File; +import java.io.IOException; +import java.util.List; +import net.pms.renderers.Renderer; +import net.pms.store.StoreContainer; +import net.pms.store.item.RarredEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RarredFolder extends StoreContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RarredFolder.class); + + protected final File file; + + private final String entryName; + + public RarredFolder(Renderer renderer, File file, String entryName) { + super(renderer, null, null); + this.file = file; + if (entryName == null || "".equals(entryName)) { + this.entryName = ""; + } else { + this.entryName = entryName; + this.name = entryName; + if (this.name.endsWith("/")) { + this.name = this.name.substring(0, this.name.length() - 1); + } + if (this.name.contains("/")) { + this.name = this.name.substring(this.name.lastIndexOf("/")); + } + } + } + + @Override + public String getSystemName() { + return file.getAbsolutePath() + "#" + entryName; + } + + @Override + public boolean isValid() { + return file.exists(); + } + + @Override + public void discoverChildren() { + getChildren().clear(); + try (Archive rarFile = new Archive(new FileVolumeManager(file), null, null)) { + List headers = rarFile.getFileHeaders(); + for (FileHeader fh : headers) { + if (fh.getFileName().equals(entryName) && fh.getCreationTime() != null) { + setLastModified(fh.getCreationTime().toMillis()); + } else if (isDirectChild(fh)) { + if (fh.isDirectory()) { + addChild(new SevenZipFolder(renderer, file, fh.getFileName())); + } else { + RarredEntry child = new RarredEntry(renderer, file, fh.getFileName(), fh.getFullUnpackSize()); + if (child.isValid()) { + addChild(child); + } + } + } + } + } catch (RarException | IOException e) { + LOGGER.error("Error reading archive file", e); + } + } + + private boolean isDirectChild(FileHeader fh) { + if (fh.getFileName().startsWith(entryName)) { + String childName = fh.getFileName().substring(entryName.length() + 1); + return !childName.contains("/") && !childName.contains("\\"); + } + return false; + } + +} diff --git a/src/main/java/net/pms/store/container/SevenZipFile.java b/src/main/java/net/pms/store/container/SevenZipFile.java index 95d4ef8fea4..d9daea63571 100644 --- a/src/main/java/net/pms/store/container/SevenZipFile.java +++ b/src/main/java/net/pms/store/container/SevenZipFile.java @@ -17,47 +17,14 @@ package net.pms.store.container; import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; import net.pms.renderers.Renderer; -import net.pms.store.StoreContainer; -import net.pms.store.item.SevenZipEntry; -import net.sf.sevenzipjbinding.IInArchive; -import net.sf.sevenzipjbinding.SevenZip; -import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; -import net.sf.sevenzipjbinding.simple.ISimpleInArchive; -import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import net.pms.store.SystemFileResource; -public class SevenZipFile extends StoreContainer { - private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipFile.class); - private File file; - private IInArchive arc; +public class SevenZipFile extends SevenZipFolder implements SystemFileResource { - public SevenZipFile(Renderer renderer, File f) { - super(renderer, f.getName(), null); - file = f; + public SevenZipFile(Renderer renderer, File file) { + super(renderer, file, ""); setLastModified(file.lastModified()); - try { - RandomAccessFile rf = new RandomAccessFile(f, "r"); - arc = SevenZip.openInArchive(null, new RandomAccessFileInStream(rf)); - ISimpleInArchive simpleInArchive = arc.getSimpleInterface(); - - for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { - LOGGER.debug("found " + item.getPath() + " in arc " + file.getAbsolutePath()); - - // Skip folders for now - if (item.isFolder()) { - continue; - } - addChild(new SevenZipEntry(renderer, f, item.getPath(), item.getSize())); - } - } catch (IOException e) { - LOGGER.error("Error reading archive file", e); - } catch (NullPointerException e) { - LOGGER.error("Caught 7-Zip Null-Pointer Exception", e); - } } @Override @@ -65,13 +32,19 @@ public String getName() { return file.getName(); } + @Override + public long length() { + return file.length(); + } + @Override public String getSystemName() { return file.getAbsolutePath(); } @Override - public boolean isValid() { - return file.exists(); + public File getSystemFile() { + return file; } + } diff --git a/src/main/java/net/pms/store/container/SevenZipFolder.java b/src/main/java/net/pms/store/container/SevenZipFolder.java new file mode 100644 index 00000000000..485a7864a99 --- /dev/null +++ b/src/main/java/net/pms/store/container/SevenZipFolder.java @@ -0,0 +1,101 @@ +/* + * This file is part of Universal Media Server, based on PS3 Media Server. + * + * This program is a free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License only. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package net.pms.store.container; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import net.pms.renderers.Renderer; +import net.pms.store.StoreContainer; +import net.pms.store.item.SevenZipEntry; +import net.sf.sevenzipjbinding.IInArchive; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.SevenZipException; +import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; +import net.sf.sevenzipjbinding.simple.ISimpleInArchive; +import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SevenZipFolder extends StoreContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipFolder.class); + + protected final File file; + + private final String entryName; + + public SevenZipFolder(Renderer renderer, File file, String entryName) { + super(renderer, null, null); + this.file = file; + if (entryName == null || "".equals(entryName)) { + this.entryName = ""; + } else { + this.entryName = entryName; + this.name = entryName; + if (this.name.endsWith("/")) { + this.name = this.name.substring(0, this.name.length() - 1); + } + if (this.name.contains("/")) { + this.name = this.name.substring(this.name.lastIndexOf("/")); + } + } + } + + @Override + public String getSystemName() { + return file.getAbsolutePath() + "#" + entryName; + } + + @Override + public boolean isValid() { + return file.exists(); + } + + @Override + public void discoverChildren() { + getChildren().clear(); + try (RandomAccessFile rf = new RandomAccessFile(file, "r"); IInArchive arc = SevenZip.openInArchive(null, new RandomAccessFileInStream(rf))) { + ISimpleInArchive simpleInArchive = arc.getSimpleInterface(); + for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { + if (item.getPath().equals(entryName) && item.getCreationTime() != null) { + setLastModified(item.getCreationTime().getTime()); + } else if (isDirectChild(item)) { + if (item.isFolder()) { + addChild(new SevenZipFolder(renderer, file, item.getPath())); + } else { + SevenZipEntry child = new SevenZipEntry(renderer, file, item.getPath(), item.getSize()); + if (child.isValid()) { + addChild(child); + } + } + } + } + } catch (IOException e) { + LOGGER.error("Error reading archive file", e); + } + } + + private boolean isDirectChild(ISimpleInArchiveItem item) throws SevenZipException { + if (item.getPath().startsWith(entryName)) { + String childName = item.getPath().substring(entryName.length() + 1); + return !childName.contains("/") && !childName.contains("\\"); + } + return false; + } + +} diff --git a/src/main/java/net/pms/store/container/ZippedFile.java b/src/main/java/net/pms/store/container/ZippedFile.java index 6152bd1fc1a..57c77cfe109 100644 --- a/src/main/java/net/pms/store/container/ZippedFile.java +++ b/src/main/java/net/pms/store/container/ZippedFile.java @@ -17,44 +17,14 @@ package net.pms.store.container; import java.io.File; -import java.io.IOException; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; import net.pms.renderers.Renderer; -import net.pms.store.StoreContainer; import net.pms.store.SystemFileResource; -import net.pms.store.item.ZippedEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class ZippedFile extends StoreContainer implements SystemFileResource { - - private static final Logger LOGGER = LoggerFactory.getLogger(ZippedFile.class); - private final File file; - private ZipFile zip; +public class ZippedFile extends ZippedFolder implements SystemFileResource { public ZippedFile(Renderer renderer, File file) { - super(renderer, null, null); - this.file = file; + super(renderer, file, ""); setLastModified(file.lastModified()); - - try { - zip = new ZipFile(file); - Enumeration enm = zip.entries(); - - while (enm.hasMoreElements()) { - ZipEntry ze = enm.nextElement(); - addChild(new ZippedEntry(renderer, file, ze.getName(), ze.getSize())); - } - - zip.close(); - } catch (ZipException e) { - LOGGER.error("Error reading zip file", e); - } catch (IOException e) { - LOGGER.error("Error reading zip file", e); - } } @Override @@ -72,11 +42,6 @@ public String getSystemName() { return file.getAbsolutePath(); } - @Override - public boolean isValid() { - return file.exists(); - } - @Override public File getSystemFile() { return file; diff --git a/src/main/java/net/pms/store/container/ZippedFolder.java b/src/main/java/net/pms/store/container/ZippedFolder.java new file mode 100644 index 00000000000..d01cf35b455 --- /dev/null +++ b/src/main/java/net/pms/store/container/ZippedFolder.java @@ -0,0 +1,103 @@ +/* + * This file is part of Universal Media Server, based on PS3 Media Server. + * + * This program is a free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License only. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package net.pms.store.container; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import net.pms.renderers.Renderer; +import net.pms.store.StoreContainer; +import net.pms.store.item.ZippedEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZippedFolder extends StoreContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZippedFolder.class); + + private final String entryName; + protected final File file; + + public ZippedFolder(Renderer renderer, File file, String entryName) { + super(renderer, null, null); + this.file = file; + if (entryName == null || "".equals(entryName)) { + this.entryName = ""; + } else { + this.entryName = entryName; + this.name = entryName; + if (this.name.endsWith("/")) { + this.name = this.name.substring(0, this.name.length() - 1); + } + if (this.name.contains("/")) { + this.name = this.name.substring(this.name.lastIndexOf("/")); + } + } + } + + @Override + public String getSystemName() { + return file.getAbsolutePath() + "#" + entryName; + } + + @Override + public boolean isValid() { + return file.exists(); + } + + @Override + public void discoverChildren() { + getChildren().clear(); + try (ZipFile zip = new ZipFile(file)) { + Enumeration enm = zip.entries(); + while (enm.hasMoreElements()) { + ZipEntry entry = enm.nextElement(); + if (entry.getName().equals(entryName)) { + setLastModified(entry.getTime()); + } else if (isDirectChild(entry)) { + if (entry.isDirectory()) { + addChild(new ZippedFolder(renderer, file, entry.getName())); + } else { + ZippedEntry child = new ZippedEntry(renderer, file, entry.getName(), entry.getSize()); + if (child.isValid()) { + addChild(child); + } + } + } + } + } catch (ZipException e) { + LOGGER.error("Error reading zip file", e); + } catch (IOException e) { + LOGGER.error("Error reading zip file", e); + } + } + + private boolean isDirectChild(ZipEntry entry) { + if (entry.getName().startsWith(entryName)) { + String childName = entry.getName().substring(entryName.length()); + if (entry.isDirectory()) { + childName = childName.substring(0, childName.length() - 1); + } + return !childName.contains("/"); + } + return false; + } + +} diff --git a/src/main/java/net/pms/store/item/ArchiveEntry.java b/src/main/java/net/pms/store/item/ArchiveEntry.java new file mode 100644 index 00000000000..8cb08ae27e5 --- /dev/null +++ b/src/main/java/net/pms/store/item/ArchiveEntry.java @@ -0,0 +1,120 @@ +/* + * This file is part of Universal Media Server, based on PS3 Media Server. + * + * This program is a free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License only. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package net.pms.store.item; + +import java.io.File; +import java.io.IOException; +import net.pms.dlna.DLNAThumbnailInputStream; +import net.pms.formats.Format; +import net.pms.media.MediaInfo; +import net.pms.parsers.Parser; +import net.pms.renderers.Renderer; +import net.pms.store.StoreItem; +import net.pms.util.IPushOutput; +import net.pms.util.InputFile; + +abstract class ArchiveEntry extends StoreItem implements IPushOutput { + + protected final File file; + protected final String entryName; + protected final long length; + protected final String name; + + protected ArchiveEntry(Renderer renderer, File file, String entryName, long length) { + super(renderer); + this.file = file; + this.entryName = entryName; + this.length = length; + this.name = getName(entryName); + } + + @Override + public String getName() { + return name; + } + + @Override + public long length() { + if (isTranscoded() && getTranscodingSettings().getEngine().type() != Format.IMAGE) { + return TRANS_SIZE; + } + + return length; + } + + @Override + public String getSystemName() { + return file.getAbsolutePath() + "#" + entryName; + } + + @Override + public boolean isValid() { + resolveFormat(); + return getFormat() != null; + } + + @Override + public boolean isUnderlyingSeekSupported() { + return length() < MAX_ARCHIVE_SIZE_SEEK; + } + + @Override + protected void resolveOnce() { + if (getMediaInfo() == null) { + setMediaInfo(new MediaInfo()); + } + + if (getFormat() != null) { + InputFile input = new InputFile(); + input.setPush(this); + input.setSize(length()); + Parser.parse(getMediaInfo(), input, getFormat(), getType()); + if (getMediaInfo() != null && getMediaInfo().isSLS()) { + setFormat(getMediaInfo().getAudioVariantFormat()); + } + } + } + + @Override + public DLNAThumbnailInputStream getThumbnailInputStream() throws IOException { + if (getMediaInfo() != null && getMediaInfo().getThumbnail() != null) { + return getMediaInfo().getThumbnailInputStream(); + } else { + return super.getThumbnailInputStream(); + } + } + + @Override + public String write() { + return entryName + ">" + file.getAbsolutePath() + ">" + length; + } + + private static String getName(String entryName) { + String name = entryName; + while (name.endsWith("/") || name.endsWith("\\")) { + name = name.substring(0, name.length() - 1); + } + if (name.contains("/")) { + name = name.substring(name.lastIndexOf("/") + 1); + } + if (name.contains("\\")) { + name = name.substring(name.lastIndexOf("\\") + 1); + } + return name; + } + +} diff --git a/src/main/java/net/pms/store/item/RarredEntry.java b/src/main/java/net/pms/store/item/RarredEntry.java index 2516cafe1e5..c801408ba2d 100644 --- a/src/main/java/net/pms/store/item/RarredEntry.java +++ b/src/main/java/net/pms/store/item/RarredEntry.java @@ -24,87 +24,32 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import net.pms.dlna.DLNAImageProfile; -import net.pms.dlna.DLNAThumbnailInputStream; -import net.pms.formats.Format; -import net.pms.media.MediaInfo; -import net.pms.parsers.Parser; import net.pms.renderers.Renderer; -import net.pms.store.StoreItem; -import net.pms.util.FileUtil; +import net.pms.util.ArchiveFileInputStream; import net.pms.util.IPushOutput; -import net.pms.util.InputFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RarredEntry extends StoreItem implements IPushOutput { - private static final Logger LOGGER = LoggerFactory.getLogger(RarredEntry.class); - private final String name; - private final File file; - private final String fileHeaderName; - private final long length; - - public RarredEntry(Renderer renderer, String name, File file, String fileHeaderName, long length) { - super(renderer); - this.fileHeaderName = fileHeaderName; - this.name = name; - this.file = file; - this.length = length; - } +public class RarredEntry extends ArchiveEntry implements IPushOutput { - @Override - public String getThumbnailURL(DLNAImageProfile profile) { - if (getType() == Format.IMAGE || getType() == Format.AUDIO) { // no thumbnail support for now for rarred videos - return null; - } + private static final Logger LOGGER = LoggerFactory.getLogger(RarredEntry.class); - return super.getThumbnailURL(profile); + public RarredEntry(Renderer renderer, File file, String entryName, long length) { + super(renderer, file, entryName, length); } @Override public InputStream getInputStream() throws IOException { - return null; - } - - @Override - public String getName() { - return name; - } - - @Override - public long length() { - if (isTranscoded() && getTranscodingSettings().getEngine().type() != Format.IMAGE) { - return TRANS_SIZE; - } - - return length; - } - - @Override - public String getSystemName() { - return FileUtil.getFileNameWithoutExtension(file.getAbsolutePath()) + "." + FileUtil.getExtension(name); - } - - @Override - public boolean isValid() { - resolveFormat(); - return getFormat() != null; - } - - @Override - public boolean isUnderlyingSeekSupported() { - return length() < MAX_ARCHIVE_SIZE_SEEK; + return ArchiveFileInputStream.getRarEntryInputStream(file, entryName); } @Override public void push(final OutputStream out) throws IOException { Runnable r = () -> { - Archive rarFile = null; - try { - rarFile = new Archive(new FileVolumeManager(file), null, null); + try (Archive rarFile = new Archive(new FileVolumeManager(file), null, null)) { FileHeader header = null; for (FileHeader fh : rarFile.getFileHeaders()) { - if (fh.getFileName().equals(fileHeaderName)) { + if (fh.getFileName().equals(entryName)) { header = fh; break; } @@ -117,9 +62,6 @@ public void push(final OutputStream out) throws IOException { LOGGER.debug("Unpack error, maybe it's normal, as backend can be terminated: " + e.getMessage()); } finally { try { - if (rarFile != null) { - rarFile.close(); - } out.close(); } catch (IOException e) { LOGGER.debug("Caught exception", e); @@ -130,33 +72,4 @@ public void push(final OutputStream out) throws IOException { new Thread(r, "Rar Extractor").start(); } - @Override - protected void resolveOnce() { - if (getFormat() == null || !getFormat().isVideo()) { - return; - } - - if (getMediaInfo() == null) { - setMediaInfo(new MediaInfo()); - } - - if (getFormat() != null) { - InputFile input = new InputFile(); - input.setPush(this); - input.setSize(length()); - Parser.parse(getMediaInfo(), input, getFormat(), getType()); - if (getMediaInfo() != null && getMediaInfo().isSLS()) { - setFormat(getMediaInfo().getAudioVariantFormat()); - } - } - } - - @Override - public DLNAThumbnailInputStream getThumbnailInputStream() throws IOException { - if (getMediaInfo() != null && getMediaInfo().getThumbnail() != null) { - return getMediaInfo().getThumbnailInputStream(); - } else { - return super.getThumbnailInputStream(); - } - } } diff --git a/src/main/java/net/pms/store/item/SevenZipEntry.java b/src/main/java/net/pms/store/item/SevenZipEntry.java index 18fda159b5c..5a0470b5dbe 100644 --- a/src/main/java/net/pms/store/item/SevenZipEntry.java +++ b/src/main/java/net/pms/store/item/SevenZipEntry.java @@ -22,16 +22,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; -import net.pms.dlna.DLNAImageProfile; -import net.pms.dlna.DLNAThumbnailInputStream; -import net.pms.formats.Format; -import net.pms.media.MediaInfo; -import net.pms.parsers.Parser; import net.pms.renderers.Renderer; -import net.pms.store.StoreItem; -import net.pms.util.FileUtil; +import net.pms.util.ArchiveFileInputStream; import net.pms.util.IPushOutput; -import net.pms.util.InputFile; import net.sf.sevenzipjbinding.IInArchive; import net.sf.sevenzipjbinding.SevenZip; import net.sf.sevenzipjbinding.SevenZipException; @@ -41,84 +34,36 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SevenZipEntry extends StoreItem implements IPushOutput { - private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipEntry.class); - private final File file; - private final String zeName; - private final long length; - private IInArchive arc; - - public SevenZipEntry(Renderer renderer, File file, String zeName, long length) { - super(renderer); - this.zeName = zeName; - this.file = file; - this.length = length; - } +public class SevenZipEntry extends ArchiveEntry implements IPushOutput { - @Override - public String getThumbnailURL(DLNAImageProfile profile) { - if (getType() == Format.IMAGE || getType() == Format.AUDIO) { - // no thumbnail support for now for zipped videos - return null; - } + private static final Logger LOGGER = LoggerFactory.getLogger(SevenZipEntry.class); - return super.getThumbnailURL(profile); + public SevenZipEntry(Renderer renderer, File file, String entryName, long length) { + super(renderer, file, entryName, length); } @Override public InputStream getInputStream() throws IOException { - return null; - } - - @Override - public String getName() { - return zeName; - } - - @Override - public long length() { - if (isTranscoded() && getTranscodingSettings().getEngine().type() != Format.IMAGE) { - return TRANS_SIZE; - } - - return length; - } - - @Override - public String getSystemName() { - return FileUtil.getFileNameWithoutExtension(file.getAbsolutePath()) + "." + FileUtil.getExtension(zeName); - } - - @Override - public boolean isValid() { - resolveFormat(); - return getFormat() != null; - } - - @Override - public boolean isUnderlyingSeekSupported() { - return length() < MAX_ARCHIVE_SIZE_SEEK; + return ArchiveFileInputStream.getSevenZipEntryInputStream(file, entryName); } @Override public void push(final OutputStream out) throws IOException { Runnable r = () -> { - try { - RandomAccessFile rf = new RandomAccessFile(file, "r"); - - arc = SevenZip.openInArchive(null, new RandomAccessFileInStream(rf)); + try (RandomAccessFile rf = new RandomAccessFile(file, "r"); + IInArchive arc = SevenZip.openInArchive(null, new RandomAccessFileInStream(rf))) { ISimpleInArchive simpleInArchive = arc.getSimpleInterface(); ISimpleInArchiveItem realItem = null; for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { - if (item.getPath().equals(zeName)) { + if (item.getPath().equals(entryName)) { realItem = item; break; } } if (realItem == null) { - LOGGER.trace("No such item " + zeName + " found in archive"); + LOGGER.trace("No such item " + entryName + " found in archive"); return; } @@ -133,9 +78,10 @@ public void push(final OutputStream out) throws IOException { }); } catch (FileNotFoundException | SevenZipException e) { LOGGER.debug("Unpack error. Possibly harmless.", e.getMessage()); + } catch (IOException e) { + LOGGER.error("Unpack error. Possibly harmless.", e.getMessage()); } finally { try { - arc.close(); out.close(); } catch (IOException e) { LOGGER.debug("Caught exception", e); @@ -146,40 +92,4 @@ public void push(final OutputStream out) throws IOException { new Thread(r, "7Zip Extractor").start(); } - @Override - public synchronized void resolve() { - if (getFormat() == null || !getFormat().isVideo()) { - return; - } - - if (getMediaInfo() == null) { - setMediaInfo(new MediaInfo()); - } - - if (getFormat() != null) { - InputFile input = new InputFile(); - input.setPush(this); - input.setSize(length()); - Parser.parse(getMediaInfo(), input, getFormat(), getType()); - if (getMediaInfo() != null && getMediaInfo().isSLS()) { - setFormat(getMediaInfo().getAudioVariantFormat()); - } - } - - super.resolve(); - } - - @Override - public DLNAThumbnailInputStream getThumbnailInputStream() throws IOException { - if (getMediaInfo() != null && getMediaInfo().getThumbnail() != null) { - return getMediaInfo().getThumbnailInputStream(); - } else { - return super.getThumbnailInputStream(); - } - } - - @Override - public String write() { - return getName() + ">" + file.getAbsolutePath() + ">" + length; - } } diff --git a/src/main/java/net/pms/store/item/ZippedEntry.java b/src/main/java/net/pms/store/item/ZippedEntry.java index f1f2c9248c8..bebac268148 100644 --- a/src/main/java/net/pms/store/item/ZippedEntry.java +++ b/src/main/java/net/pms/store/item/ZippedEntry.java @@ -20,140 +20,42 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import net.pms.dlna.DLNAImageProfile; -import net.pms.dlna.DLNAThumbnailInputStream; -import net.pms.formats.Format; -import net.pms.media.MediaInfo; -import net.pms.parsers.Parser; import net.pms.renderers.Renderer; -import net.pms.store.StoreItem; -import net.pms.util.FileUtil; -import net.pms.util.IPushOutput; -import net.pms.util.InputFile; +import net.pms.util.ArchiveFileInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ZippedEntry extends StoreItem implements IPushOutput { - private static final Logger LOGGER = LoggerFactory.getLogger(ZippedEntry.class); - private final File file; - private final String zeName; - private final long length; - private ZipFile zipFile; - - public ZippedEntry(Renderer renderer, File file, String zeName, long length) { - super(renderer); - this.zeName = zeName; - this.file = file; - this.length = length; - } +public class ZippedEntry extends ArchiveEntry { - @Override - public String getThumbnailURL(DLNAImageProfile profile) { - if (getType() == Format.IMAGE || getType() == Format.AUDIO) { - // no thumbnail support for now for zipped videos - return null; - } + private static final Logger LOGGER = LoggerFactory.getLogger(ZippedEntry.class); - return super.getThumbnailURL(profile); + public ZippedEntry(Renderer renderer, File file, String entryName, long length) { + super(renderer, file, entryName, length); } @Override public InputStream getInputStream() { - return null; - } - - @Override - public String getName() { - return zeName; - } - - @Override - public long length() { - if (isTranscoded() && getTranscodingSettings().getEngine().type() != Format.IMAGE) { - return TRANS_SIZE; - } - - return length; - } - - @Override - public String getSystemName() { - return FileUtil.getFileNameWithoutExtension(file.getAbsolutePath()) + "." + FileUtil.getExtension(zeName); - } - - @Override - public boolean isValid() { - resolveFormat(); - return getFormat() != null; - } - - @Override - public boolean isUnderlyingSeekSupported() { - return length() < MAX_ARCHIVE_SIZE_SEEK; + return ArchiveFileInputStream.getZipEntryInputStream(file, entryName); } @Override public void push(final OutputStream out) throws IOException { Runnable r = () -> { - try { - int n; - byte[] data = new byte[65536]; - zipFile = new ZipFile(file); - ZipEntry ze = zipFile.getEntry(zeName); - try (InputStream in = zipFile.getInputStream(ze)) { - while ((n = in.read(data)) > -1) { - out.write(data, 0, n); - } - } + try (InputStream in = ArchiveFileInputStream.getZipEntryInputStream(file, entryName)) { + in.transferTo(out); } catch (IOException e) { - LOGGER.error("Unpack error. Possibly harmless.", e); + if (!"Pipe closed".equals(e.getMessage())) { + LOGGER.error("UnZip error. Possibly harmless.", e); + } } finally { try { - zipFile.close(); out.close(); } catch (IOException e) { LOGGER.debug("Caught exception", e); } } }; - new Thread(r, "Zip Extractor").start(); } - @Override - protected void resolveOnce() { - if (getFormat() == null || !getFormat().isVideo()) { - return; - } - - if (getMediaInfo() == null) { - setMediaInfo(new MediaInfo()); - } - - if (getFormat() != null) { - InputFile input = new InputFile(); - input.setPush(this); - input.setSize(length()); - Parser.parse(getMediaInfo(), input, getFormat(), getType()); - if (getMediaInfo() != null && getMediaInfo().isSLS()) { - setFormat(getMediaInfo().getAudioVariantFormat()); - } - } - } - - @Override - public DLNAThumbnailInputStream getThumbnailInputStream() throws IOException { - if (getMediaInfo() != null && getMediaInfo().getThumbnail() != null) { - return getMediaInfo().getThumbnailInputStream(); - } else { - return super.getThumbnailInputStream(); - } - } - - @Override - public String write() { - return getName() + ">" + file.getAbsolutePath() + ">" + length; - } } diff --git a/src/main/java/net/pms/util/ArchiveFileInputStream.java b/src/main/java/net/pms/util/ArchiveFileInputStream.java new file mode 100644 index 00000000000..d573ef8c7e9 --- /dev/null +++ b/src/main/java/net/pms/util/ArchiveFileInputStream.java @@ -0,0 +1,243 @@ +/* + * This file is part of Universal Media Server, based on PS3 Media Server. + * + * This program is a free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License only. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package net.pms.util; + +import com.github.junrar.Archive; +import com.github.junrar.exception.RarException; +import com.github.junrar.rarfile.FileHeader; +import com.github.junrar.volume.FileVolumeManager; +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.RandomAccessFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import net.sf.sevenzipjbinding.IInArchive; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.SevenZipException; +import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; +import net.sf.sevenzipjbinding.simple.ISimpleInArchive; +import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class close the archive file on InputStream close. + * + * @author SurfaceS + */ +public class ArchiveFileInputStream extends InputStream { + + private static final Logger LOGGER = LoggerFactory.getLogger(ArchiveFileInputStream.class); + + protected final File file; + protected final String name; + protected final InputStream inputStream; + protected final Closeable closeable; + + protected ArchiveFileInputStream(File file, String name, InputStream inputStream, Closeable closeable) { + this.file = file; + this.name = name; + this.inputStream = inputStream; + this.closeable = closeable; + } + + @Override + public void close() throws IOException { + if (inputStream != null) { + inputStream.close(); + } + if (closeable != null) { + closeable.close(); + } + } + + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return inputStream.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return inputStream.read(b, off, len); + } + + @Override + public byte[] readAllBytes() throws IOException { + return inputStream.readAllBytes(); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + return inputStream.readNBytes(len); + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return inputStream.readNBytes(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return inputStream.skip(n); + } + + @Override + public void skipNBytes(long n) throws IOException { + inputStream.skipNBytes(n); + } + + @Override + public int available() throws IOException { + return inputStream.available(); + } + + @Override + public synchronized void mark(int readlimit) { + inputStream.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + inputStream.reset(); + } + + @Override + public boolean markSupported() { + return inputStream.markSupported(); + } + + @Override + public long transferTo(OutputStream out) throws IOException { + return inputStream.transferTo(out); + } + + public static ArchiveFileInputStream getZipEntryInputStream(File file, String name) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + ZipEntry ze = zipFile.getEntry(name); + if (ze != null) { + return new ArchiveFileInputStream(file, name, zipFile.getInputStream(ze), zipFile); + } else { + LOGGER.error("Zip entry '{}' not found.", name); + } + } catch (IOException e) { + LOGGER.error("ZipEntryInputStream error.", e); + } + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException e) { + //ignore + } + } + return null; + } + + public static ArchiveFileInputStream getSevenZipEntryInputStream(File file, String name) { + IInArchive arc = null; + try { + RandomAccessFile rf = new RandomAccessFile(file, "r"); + arc = SevenZip.openInArchive(null, new RandomAccessFileInStream(rf)); + ISimpleInArchive simpleInArchive = arc.getSimpleInterface(); + ISimpleInArchiveItem realItem = null; + + for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { + if (item.getPath().equals(name)) { + realItem = item; + break; + } + } + + if (realItem != null) { + final ISimpleInArchiveItem archiveItem = realItem; + final int bufferSize = (int) Math.max(Math.min(realItem.getSize(), 65536), 1); + final PipedInputStream in = new PipedInputStream(bufferSize); + Runnable r = () -> { + try (PipedOutputStream out = new PipedOutputStream(in)) { + archiveItem.extractSlow((byte[] data) -> { + try { + out.write(data); + } catch (IOException e) { + LOGGER.debug("Caught exception", e); + throw new SevenZipException(); + } + return data.length; + }); + } catch (IOException e) { + //ignore + } + }; + new Thread(r).start(); + return new ArchiveFileInputStream(file, name, in, arc); + } else { + LOGGER.error("SevenZip entry '{}' not found.", name); + } + } catch (FileNotFoundException | SevenZipException e) { + LOGGER.debug("Unpack error. Possibly harmless.", e.getMessage()); + } + if (arc != null) { + try { + arc.close(); + } catch (IOException e) { + //ignore + } + } + return null; + } + + public static ArchiveFileInputStream getRarEntryInputStream(File file, String name) { + Archive rarFile = null; + try { + rarFile = new Archive(new FileVolumeManager(file), null, null); + FileHeader header = null; + for (FileHeader fh : rarFile.getFileHeaders()) { + if (fh.getFileName().equals(name)) { + header = fh; + break; + } + } + if (header != null) { + return new ArchiveFileInputStream(file, name, rarFile.getInputStream(header), rarFile); + } else { + LOGGER.error("Rar entry '{}' not found.", name); + } + } catch (RarException | IOException e) { + LOGGER.error("RarEntryInputStream error: " + e.getMessage()); + } + if (rarFile != null) { + try { + rarFile.close(); + } catch (IOException e) { + //ignore + } + } + return null; + } + +}