From 75e46e075a15d4e15b01f92400f462846d64fe78 Mon Sep 17 00:00:00 2001 From: keyboardr Date: Mon, 27 Apr 2020 01:44:10 -0700 Subject: [PATCH] Add constructors for FileDescriptors --- .../java/com/mpatric/mp3agic/FileWrapper.java | 62 ++++++++++--- .../java/com/mpatric/mp3agic/Mp3File.java | 24 ++++- .../com/mpatric/mp3agic/FileWrapperTest.java | 16 ++++ .../java/com/mpatric/mp3agic/Mp3FileTest.java | 93 ++++++++++++++++++- .../java/com/mpatric/mp3agic/TestHelper.java | 8 +- 5 files changed, 180 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/mpatric/mp3agic/FileWrapper.java b/src/main/java/com/mpatric/mp3agic/FileWrapper.java index 11d15236..2fba016b 100644 --- a/src/main/java/com/mpatric/mp3agic/FileWrapper.java +++ b/src/main/java/com/mpatric/mp3agic/FileWrapper.java @@ -1,48 +1,69 @@ package com.mpatric.mp3agic; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.io.*; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.concurrent.TimeUnit; public class FileWrapper { - protected Path path; - protected long length; - protected long lastModified; + private interface SeekableByteChannelFactory { + SeekableByteChannel createByteChannel() throws IOException; + } + + private SeekableByteChannelFactory byteChannelFactory; + private String filename; + private Path absolutePath; + private long length; + private long lastModified; protected FileWrapper() { } + public FileWrapper(FileDescriptor fileDescriptor) throws IOException { + if (fileDescriptor == null) throw new NullPointerException(); + if (!fileDescriptor.valid()) throw new IOException("FileDescriptor not valid"); + length = -1; + lastModified = -1; + byteChannelFactory = () -> { + FileChannel channel = new FileInputStream(fileDescriptor).getChannel(); + length = channel.size(); + return channel; + }; + } + public FileWrapper(String filename) throws IOException { - this.path = Paths.get(filename); - init(); + this(Paths.get(filename)); } public FileWrapper(File file) throws IOException { - if (file == null) throw new NullPointerException(); - this.path = Paths.get(file.getPath()); - init(); + this(Paths.get(file.getPath())); } public FileWrapper(Path path) throws IOException { if (path == null) throw new NullPointerException(); - this.path = path; - init(); + init(path); } - private void init() throws IOException { + private void init(Path path) throws IOException { if (!Files.exists(path)) throw new FileNotFoundException("File not found " + path); if (!Files.isReadable(path)) throw new IOException("File not readable"); + absolutePath = path.toAbsolutePath(); + byteChannelFactory = () -> Files.newByteChannel(path, StandardOpenOption.READ); + filename = path.toString(); length = Files.size(path); lastModified = Files.getLastModifiedTime(path).to(TimeUnit.MILLISECONDS); } public String getFilename() { - return path.toString(); + if (filename == null) { + throw new IllegalArgumentException("Cannot get filename from a FileDescriptor"); + } + return filename; } public long getLength() { @@ -50,6 +71,17 @@ public long getLength() { } public long getLastModified() { + if (lastModified < 0) { + throw new IllegalArgumentException("Cannot get last modified from a FileDescriptor"); + } return lastModified; } + + protected SeekableByteChannel getByteChannel() throws IOException { + return byteChannelFactory.createByteChannel(); + } + + protected Path getAbsolutePath() { + return absolutePath; + } } diff --git a/src/main/java/com/mpatric/mp3agic/Mp3File.java b/src/main/java/com/mpatric/mp3agic/Mp3File.java index d8d976da..cb9379ea 100644 --- a/src/main/java/com/mpatric/mp3agic/Mp3File.java +++ b/src/main/java/com/mpatric/mp3agic/Mp3File.java @@ -1,6 +1,7 @@ package com.mpatric.mp3agic; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; @@ -8,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import java.util.EnumSet; +import java.util.Objects; public class Mp3File extends FileWrapper { @@ -71,6 +73,19 @@ public Mp3File(File file, int bufferLength, boolean scanFile) throws IOException init(bufferLength, scanFile); } + public Mp3File(FileDescriptor fileDescriptor) throws IOException, UnsupportedTagException, InvalidDataException { + this(fileDescriptor, DEFAULT_BUFFER_LENGTH, true); + } + + public Mp3File(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + this(fileDescriptor, bufferLength, true); + } + + public Mp3File(FileDescriptor fileDescriptor, int bufferLength, boolean scanFile) throws IOException, UnsupportedTagException, InvalidDataException { + super(fileDescriptor); + init(bufferLength, scanFile); + } + public Mp3File(Path path) throws IOException, UnsupportedTagException, InvalidDataException { this(path, DEFAULT_BUFFER_LENGTH, true); } @@ -90,7 +105,7 @@ private void init(int bufferLength, boolean scanFile) throws IOException, Unsupp this.bufferLength = bufferLength; this.scanFile = scanFile; - try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path, StandardOpenOption.READ)) { + try (SeekableByteChannel seekableByteChannel = getByteChannel()) { initId3v1Tag(seekableByteChannel); scanFile(seekableByteChannel); if (startOffset < 0) { @@ -440,7 +455,10 @@ public void removeCustomTag() { } public void save(String newFilename) throws IOException, NotSupportedException { - if (path.toAbsolutePath().compareTo(Paths.get(newFilename).toAbsolutePath()) == 0) { + if (getAbsolutePath() == null) { + throw new IllegalArgumentException("Unable to save files based on FileDescriptors"); + } + if (Objects.equals(getAbsolutePath(), Paths.get(newFilename).toAbsolutePath())) { throw new IllegalArgumentException("Save filename same as source filename"); } try (SeekableByteChannel saveFile = Files.newByteChannel(Paths.get(newFilename), EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE))) { @@ -470,7 +488,7 @@ private void saveMpegFrames(SeekableByteChannel saveFile) throws IOException { if (filePos < 0) return; if (endOffset < filePos) return; ByteBuffer byteBuffer = ByteBuffer.allocate(bufferLength); - try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path, StandardOpenOption.READ)) { + try (SeekableByteChannel seekableByteChannel = getByteChannel()) { seekableByteChannel.position(filePos); while (true) { byteBuffer.clear(); diff --git a/src/test/java/com/mpatric/mp3agic/FileWrapperTest.java b/src/test/java/com/mpatric/mp3agic/FileWrapperTest.java index 15f66994..74cc5e8f 100644 --- a/src/test/java/com/mpatric/mp3agic/FileWrapperTest.java +++ b/src/test/java/com/mpatric/mp3agic/FileWrapperTest.java @@ -5,9 +5,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.channels.SeekableByteChannel; import java.nio.file.InvalidPathException; import java.nio.file.Paths; +import static com.mpatric.mp3agic.TestHelper.toFileDescriptor; import static org.junit.Assert.*; public class FileWrapperTest { @@ -17,6 +19,15 @@ public class FileWrapperTest { private static final String NON_EXISTENT_FILENAME = "just-not.there"; private static final String MALFORMED_FILENAME = "malformed.\0"; + @Test + public void shouldReadValidFileDescriptor() throws IOException { + FileWrapper fileWrapper = new FileWrapper(toFileDescriptor(new File(VALID_FILENAME))); + assertEquals(fileWrapper.getLength(), -1); // length not valid until channel is opened + try (SeekableByteChannel channel = fileWrapper.getByteChannel()) { + assertEquals(fileWrapper.getLength(), VALID_FILE_LENGTH); + } + } + @Test public void shouldReadValidFilename() throws IOException { FileWrapper fileWrapper = new FileWrapper(VALID_FILENAME); @@ -67,6 +78,11 @@ public void shouldFailForNullFilenameFile() throws IOException { new FileWrapper((java.io.File) null); } + @Test(expected = NullPointerException.class) + public void shouldFailForNullFileDescriptor() throws IOException { + new FileWrapper((java.io.FileDescriptor) null); + } + @Test(expected = NullPointerException.class) public void shouldFailForNullPath() throws IOException { new FileWrapper((java.nio.file.Path) null); diff --git a/src/test/java/com/mpatric/mp3agic/Mp3FileTest.java b/src/test/java/com/mpatric/mp3agic/Mp3FileTest.java index f8657b8d..ffe63855 100644 --- a/src/test/java/com/mpatric/mp3agic/Mp3FileTest.java +++ b/src/test/java/com/mpatric/mp3agic/Mp3FileTest.java @@ -2,13 +2,13 @@ import org.junit.Test; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import static com.mpatric.mp3agic.TestHelper.toFileDescriptor; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.*; @@ -33,6 +33,10 @@ public void shouldLoadMp3WithNoTags() throws IOException, UnsupportedTagExceptio loadAndCheckTestMp3WithNoTags(new File(MP3_WITH_NO_TAGS), 256); loadAndCheckTestMp3WithNoTags(new File(MP3_WITH_NO_TAGS), 1024); loadAndCheckTestMp3WithNoTags(new File(MP3_WITH_NO_TAGS), 5000); + loadAndCheckTestMp3WithNoTags(toFileDescriptor(new File(MP3_WITH_NO_TAGS)), 41); + loadAndCheckTestMp3WithNoTags(toFileDescriptor(new File(MP3_WITH_NO_TAGS)), 256); + loadAndCheckTestMp3WithNoTags(toFileDescriptor(new File(MP3_WITH_NO_TAGS)), 1024); + loadAndCheckTestMp3WithNoTags(toFileDescriptor(new File(MP3_WITH_NO_TAGS)), 5000); } @Test @@ -45,6 +49,10 @@ public void shouldLoadMp3WithId3Tags() throws IOException, UnsupportedTagExcepti loadAndCheckTestMp3WithTags(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS), 256); loadAndCheckTestMp3WithTags(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS), 1024); loadAndCheckTestMp3WithTags(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS), 5000); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS)), 41); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS)), 256); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS)), 1024); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS)), 5000); } @Test @@ -57,6 +65,10 @@ public void shouldLoadMp3WithFakeStartAndEndFrames() throws IOException, Unsuppo loadAndCheckTestMp3WithTags(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES), 256); loadAndCheckTestMp3WithTags(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES), 1024); loadAndCheckTestMp3WithTags(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES), 5000); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES)), 41); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES)), 256); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES)), 1024); + loadAndCheckTestMp3WithTags(toFileDescriptor(new File(MP3_WITH_DUMMY_START_AND_END_FRAMES)), 5000); } @Test @@ -69,6 +81,10 @@ public void shouldLoadMp3WithCustomTag() throws IOException, UnsupportedTagExcep loadAndCheckTestMp3WithCustomTag(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS), 256); loadAndCheckTestMp3WithCustomTag(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS), 1024); loadAndCheckTestMp3WithCustomTag(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS), 5000); + loadAndCheckTestMp3WithCustomTag(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS)), 41); + loadAndCheckTestMp3WithCustomTag(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS)), 256); + loadAndCheckTestMp3WithCustomTag(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS)), 1024); + loadAndCheckTestMp3WithCustomTag(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS)), 5000); } @Test @@ -91,6 +107,16 @@ public void shouldThrowExceptionForFileThatIsNotAnMp3ForFileConstructor() throws } } + @Test + public void shouldThrowExceptionForFileThatIsNotAnMp3ForFileDescriptorConstructor() throws Exception { + try { + new Mp3File(toFileDescriptor(new File(NOT_AN_MP3))); + fail("InvalidDataException expected but not thrown"); + } catch (InvalidDataException e) { + assertEquals("No mpegs frames found", e.getMessage()); + } + } + @Test public void shouldFindProbableStartOfMpegFramesWithPrescan() throws IOException { Mp3FileForTesting mp3File = new Mp3FileForTesting(MP3_WITH_ID3V1_AND_ID3V23_TAGS); @@ -103,6 +129,12 @@ public void shouldFindProbableStartOfMpegFramesWithPrescanForFileConstructor() t testShouldFindProbableStartOfMpegFramesWithPrescan(mp3File); } + @Test + public void shouldFindProbableStartOfMpegFramesWithPrescanForFileDescriptorConstructor() throws IOException { + Mp3FileForTesting mp3File = new Mp3FileForTesting(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_TAGS))); + testShouldFindProbableStartOfMpegFramesWithPrescan(mp3File); + } + private void testShouldFindProbableStartOfMpegFramesWithPrescan(Mp3FileForTesting mp3File) { assertEquals(0x44B, mp3File.preScanResult); } @@ -130,6 +162,17 @@ private void testShouldThrowExceptionIfSavingMp3WithSameNameAsSourceFile(Mp3File } } + @Test + public void shouldThrowExceptionIfSavingForFileDescriptorConstructor() throws Exception { + Mp3File mp3File = new Mp3File(toFileDescriptor(new File(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS))); + try { + mp3File.save(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS + ".copy"); + fail("IllegalArgumentException expected but not thrown"); + } catch (IllegalArgumentException e) { + assertEquals("Unable to save files based on FileDescriptors", e.getMessage()); + } + } + @Test public void shouldSaveLoadedMp3WhichIsEquivalentToOriginal() throws Exception { copyAndCheckTestMp3WithCustomTag(MP3_WITH_ID3V1_AND_ID3V23_AND_CUSTOM_TAGS, 41); @@ -152,6 +195,10 @@ public void shouldLoadAndCheckMp3ContainingUnicodeFields() throws Exception { loadAndCheckTestMp3WithUnicodeFields(new File(MP3_WITH_ID3V23_UNICODE_TAGS), 256); loadAndCheckTestMp3WithUnicodeFields(new File(MP3_WITH_ID3V23_UNICODE_TAGS), 1024); loadAndCheckTestMp3WithUnicodeFields(new File(MP3_WITH_ID3V23_UNICODE_TAGS), 5000); + loadAndCheckTestMp3WithUnicodeFields(toFileDescriptor(new File(MP3_WITH_ID3V23_UNICODE_TAGS)), 41); + loadAndCheckTestMp3WithUnicodeFields(toFileDescriptor(new File(MP3_WITH_ID3V23_UNICODE_TAGS)), 256); + loadAndCheckTestMp3WithUnicodeFields(toFileDescriptor(new File(MP3_WITH_ID3V23_UNICODE_TAGS)), 1024); + loadAndCheckTestMp3WithUnicodeFields(toFileDescriptor(new File(MP3_WITH_ID3V23_UNICODE_TAGS)), 5000); } @Test @@ -178,6 +225,12 @@ public void shouldIgnoreIncompleteMpegFrameForFileConstructor() throws Exception testShouldIgnoreIncompleteMpegFrame(mp3File); } + @Test + public void shouldIgnoreIncompleteMpegFrameForFileDescriptorConstructor() throws Exception { + Mp3File mp3File = new Mp3File(toFileDescriptor(new File(MP3_WITH_INCOMPLETE_MPEG_FRAME)), 256); + testShouldIgnoreIncompleteMpegFrame(mp3File); + } + private void testShouldIgnoreIncompleteMpegFrame(Mp3File mp3File) throws Exception { assertEquals(0x44B, mp3File.getXingOffset()); assertEquals(0x5EC, mp3File.getStartOffset()); @@ -199,6 +252,12 @@ public void shouldInitialiseProperlyWhenNotScanningFileForFileConstructor() thro testShouldInitialiseProperlyWhenNotScanningFile(mp3File); } + @Test + public void shouldInitialiseProperlyWhenNotScanningFileForFileDescriptorConstructor() throws Exception { + Mp3File mp3File = new Mp3File(toFileDescriptor(new File(MP3_WITH_INCOMPLETE_MPEG_FRAME)), 256, false); + testShouldInitialiseProperlyWhenNotScanningFile(mp3File); + } + private void testShouldInitialiseProperlyWhenNotScanningFile(Mp3File mp3File) throws Exception { assertTrue(mp3File.hasId3v1Tag()); assertTrue(mp3File.hasId3v2Tag()); @@ -366,6 +425,11 @@ private Mp3File loadAndCheckTestMp3WithNoTags(File filename, int bufferLength) t return loadAndCheckTestMp3WithNoTags(mp3File); } + private Mp3File loadAndCheckTestMp3WithNoTags(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + Mp3File mp3File = loadAndCheckTestMp3(fileDescriptor, bufferLength); + return loadAndCheckTestMp3WithNoTags(mp3File); + } + private Mp3File loadAndCheckTestMp3WithNoTags(Mp3File mp3File) { assertEquals(0x000, mp3File.getXingOffset()); assertEquals(0x1A1, mp3File.getStartOffset()); @@ -386,6 +450,11 @@ private Mp3File loadAndCheckTestMp3WithTags(File filename, int bufferLength) thr return loadAndCheckTestMp3WithTags(mp3File); } + private Mp3File loadAndCheckTestMp3WithTags(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + Mp3File mp3File = loadAndCheckTestMp3(fileDescriptor, bufferLength); + return loadAndCheckTestMp3WithTags(mp3File); + } + private Mp3File loadAndCheckTestMp3WithTags(Mp3File mp3File) { assertEquals(0x44B, mp3File.getXingOffset()); assertEquals(0x5EC, mp3File.getStartOffset()); @@ -406,6 +475,11 @@ private Mp3File loadAndCheckTestMp3WithUnicodeFields(File filename, int bufferLe return loadAndCheckTestMp3WithUnicodeFields(mp3File); } + private Mp3File loadAndCheckTestMp3WithUnicodeFields(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + Mp3File mp3File = loadAndCheckTestMp3(fileDescriptor, bufferLength); + return loadAndCheckTestMp3WithUnicodeFields(mp3File); + } + private Mp3File loadAndCheckTestMp3WithUnicodeFields(Mp3File mp3File) { assertEquals(0x0CA, mp3File.getXingOffset()); assertEquals(0x26B, mp3File.getStartOffset()); @@ -426,6 +500,11 @@ private Mp3File loadAndCheckTestMp3WithCustomTag(File filename, int bufferLength return loadAndCheckTestMp3WithCustomTag(mp3File); } + private Mp3File loadAndCheckTestMp3WithCustomTag(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + Mp3File mp3File = loadAndCheckTestMp3(fileDescriptor, bufferLength); + return loadAndCheckTestMp3WithCustomTag(mp3File); + } + private Mp3File loadAndCheckTestMp3WithCustomTag(Mp3File mp3File) { assertEquals(0x44B, mp3File.getXingOffset()); assertEquals(0x5EC, mp3File.getStartOffset()); @@ -446,6 +525,11 @@ private Mp3File loadAndCheckTestMp3(File filename, int bufferLength) throws IOEx return loadAndCheckTestMp3(mp3File); } + private Mp3File loadAndCheckTestMp3(FileDescriptor fileDescriptor, int bufferLength) throws IOException, UnsupportedTagException, InvalidDataException { + Mp3File mp3File = new Mp3File(fileDescriptor, bufferLength); + return loadAndCheckTestMp3(mp3File); + } + private Mp3File loadAndCheckTestMp3(Mp3File mp3File) { assertTrue(mp3File.hasXingFrame()); assertEquals(6, mp3File.getFrameCount()); @@ -480,5 +564,10 @@ public Mp3FileForTesting(File filename) throws IOException { SeekableByteChannel file = Files.newByteChannel(filename.toPath(), StandardOpenOption.READ); preScanResult = preScanFile(file); } + + public Mp3FileForTesting(FileDescriptor fileDescriptor) throws IOException { + SeekableByteChannel file = new FileInputStream(fileDescriptor).getChannel(); + preScanResult = preScanFile(file); + } } } diff --git a/src/test/java/com/mpatric/mp3agic/TestHelper.java b/src/test/java/com/mpatric/mp3agic/TestHelper.java index 15f8c4d9..209427bb 100644 --- a/src/test/java/com/mpatric/mp3agic/TestHelper.java +++ b/src/test/java/com/mpatric/mp3agic/TestHelper.java @@ -1,8 +1,6 @@ package com.mpatric.mp3agic; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; +import java.io.*; import org.junit.Test; @@ -59,6 +57,10 @@ public static void replaceNumbersWithBytes(byte[] bytes, int offset) { } } + public static FileDescriptor toFileDescriptor(File file) throws IOException { + return new FileInputStream(file).getFD(); + } + // self tests @Test public void shouldConvertBytesToHexAndBack() throws Exception {