Skip to content

Commit

Permalink
feat: Handles case where we overflow void elements. (#4)
Browse files Browse the repository at this point in the history
* feat: Handles case where we overflow void elements.

Handles more tags and tracks.

* squash: Account for isSeekable when creating void block.

* squash: Adds logs for exception we swallow.

* squash: Recreates the file after the move, open it again.

* squash: Moves the logic into tracks and tag and fix it by seeking the correct src position when coping the end of the file.

* squash: Fix seeking the original file.

* squash: Fix setting end position.

It was skipping the void data.

* squash: Fix comment.
  • Loading branch information
damencho authored Dec 18, 2024
1 parent 5cec011 commit b310849
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 16 deletions.
15 changes: 12 additions & 3 deletions src/main/java/org/ebml/io/FileDataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <[email protected]>
* Based on Javatroska (C) 2002 John Cannon <[email protected]>
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
*
* This library 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
* Lesser General Public License for more details.
*
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Expand Down Expand Up @@ -42,6 +42,15 @@ public FileDataSource(final String filename, final String mode) throws FileNotFo
fc = file.getChannel();
}

/**
* Converts a writer to a reader.
*/
public FileDataSource(FileDataWriter writer) throws FileNotFoundException, IOException
{
file = writer.file;
fc = writer.fc;
}

@Override
public byte readByte()
{
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/org/ebml/io/FileDataWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,43 @@
*/
package org.ebml.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class FileDataWriter implements DataWriter, Closeable
{
private static final Logger LOG = LoggerFactory.getLogger(FileDataWriter.class);

RandomAccessFile file = null;
FileChannel fc = null;

/**
* We need this in order to be able to replace the file with another one.
*/
String filename = null;

public FileDataWriter(final String filename) throws FileNotFoundException, IOException
{
file = new RandomAccessFile(filename, "rw");
fc = file.getChannel();
this.filename = filename;
}

public FileDataWriter(final String filename, final String mode) throws FileNotFoundException, IOException
{
file = new RandomAccessFile(filename, mode);
fc = file.getChannel();
this.filename = filename;
}

@Override
Expand All @@ -53,6 +68,7 @@ public int write(final byte b)
}
catch (final IOException ex)
{
LOG.error("Failed to write byte", ex);
return 0;
}
}
Expand All @@ -66,6 +82,7 @@ public int write(final ByteBuffer buff)
}
catch (final IOException ex)
{
LOG.error("Failed to write buffer", ex);
return 0;
}
}
Expand All @@ -79,6 +96,7 @@ public long length()
}
catch (final IOException ex)
{
LOG.error("Failed to get length", ex);
return -1;
}
}
Expand All @@ -92,6 +110,7 @@ public long getFilePointer()
}
catch (final IOException ex)
{
LOG.error("Failed to get pointer", ex);
return -1;
}
}
Expand All @@ -112,6 +131,7 @@ public long seek(final long pos)
}
catch (final IOException ex)
{
LOG.error("Failed to seek", ex);
return -1;
}
}
Expand All @@ -121,4 +141,56 @@ public void close() throws IOException
{
file.close();
}

/**
* Copy data from source to source position into current file starting from the beginning.
*/
public void copyToPosition(FileDataWriter src) throws IOException
{
src.fc.transferTo(0, src.fc.position(), fc);
}

/**
* Copy from current position of src to its end to the current file on current position.
*/
public void copyFromPosition(FileDataWriter src) throws IOException
{
src.fc.transferTo(src.fc.position(), src.fc.size() - src.fc.position(), fc);
}

/**
* Copies the beginning of the file to a temporary file.
* @return the FileDataWriter for the temporary file.
* @throws IOException
*/
public FileDataWriter copyBeginningOfFile()
throws IOException
{
Path f = Files.createTempFile("mka", ".tmp");

FileDataWriter dw = new FileDataWriter(f.toFile().getPath());
dw.copyToPosition(this);

return dw;
}

/**
* Copies the end of the current file(from current position) into the supplied FileDataWriter and replaces
* the current file with the temp one.
* @param dw The FileDataWriter to use as a new file.
* @throws IOException
*/
public void copyEndOfFile(FileDataWriter dw)
throws IOException
{
dw.copyFromPosition(this);

this.close();

Files.move(Path.of(dw.filename), Path.of(this.filename), StandardCopyOption.REPLACE_EXISTING);

// recreate after we move the new file
file = new RandomAccessFile(filename, "rw");
fc = file.getChannel();
}
}
60 changes: 56 additions & 4 deletions src/main/java/org/ebml/matroska/MatroskaFileTags.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package org.ebml.matroska;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;

import org.ebml.MasterElement;
import org.ebml.io.DataWriter;
import org.ebml.io.FileDataWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MatroskaFileTags
implements PropertyChangeListener
{
private static final int BLOCK_SIZE = 4096;
private static final Logger LOG = LoggerFactory.getLogger(MatroskaFileTags.class);

private final ArrayList<MatroskaFileTagEntry> tags = new ArrayList<>();

private long myPosition;
private long myStartPosition;
private long myEndPosition;

public void addTag(final MatroskaFileTagEntry tag)
{
Expand All @@ -23,29 +29,75 @@ public void addTag(final MatroskaFileTagEntry tag)

public long writeTags(final DataWriter ioDW)
{
myPosition = ioDW.getFilePointer();
myStartPosition = ioDW.getFilePointer();
final MasterElement tagsElem = MatroskaDocTypes.Tags.getInstance();

for (final MatroskaFileTagEntry tag : tags)
{
tagsElem.addChildElement(tag.toElement());
}

if (BLOCK_SIZE < tagsElem.getTotalSize() && ioDW.isSeekable()
// do the shuffling the data only if the file is big enough to contain the data
// if it is not it means we are writing the file for the first time and we don't need to shuffle the data
&& ioDW.length() > myStartPosition + tagsElem.getTotalSize())
{
long len;

// we need to write beyond the void space we have reserved
// copy beginning of file into a temporary file
try (FileDataWriter tmp = ((FileDataWriter)ioDW).copyBeginningOfFile())
{
// write the tags
len = tagsElem.writeElement(tmp);

// now let's copy the rest of the original file by first setting the position after the tags
ioDW.seek(myEndPosition);

// copy the rest of the original file
((FileDataWriter)ioDW).copyEndOfFile(tmp);
myEndPosition = myStartPosition + len;

ioDW.seek(myEndPosition);
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}

return len;
}

long len = tagsElem.writeElement(ioDW);
if (ioDW.isSeekable())
myEndPosition = ioDW.getFilePointer();
if (BLOCK_SIZE > tagsElem.getTotalSize() && ioDW.isSeekable())
{
new VoidElement(BLOCK_SIZE - tagsElem.getTotalSize()).writeElement(ioDW);
myEndPosition = ioDW.getFilePointer();
return BLOCK_SIZE;
}

return len;
}

public long update(final DataWriter ioDW)
{
LOG.info("Updating tags list!");
final long start = ioDW.getFilePointer();
ioDW.seek(myPosition);
ioDW.seek(myStartPosition);
long len = writeTags(ioDW);
ioDW.seek(start);
return len;
}

@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getPropertyName().equals(MatroskaFileTracks.RESIZED))
{
long increase = (long) evt.getNewValue() - (long) evt.getOldValue();
myStartPosition += increase;
myEndPosition += increase;
}
}
}
74 changes: 66 additions & 8 deletions src/main/java/org/ebml/matroska/MatroskaFileTracks.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package org.ebml.matroska;

import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;

import org.ebml.MasterElement;
import org.ebml.io.DataWriter;
import org.ebml.io.FileDataWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MatroskaFileTracks
{
private static final int BLOCK_SIZE = 4096;
final public static String RESIZED = "resized";

private final PropertyChangeSupport listeners = new PropertyChangeSupport(this);

private static final long BLOCK_SIZE = 4096;
private static final Logger LOG = LoggerFactory.getLogger(MatroskaFileTracks.class);

private final ArrayList<MatroskaFileTrack> tracks = new ArrayList<>();

private long myPosition;
private long myStartPosition;
private long myEndPosition;

public void addTrack(final MatroskaFileTrack track)
{
Expand All @@ -23,26 +32,75 @@ public void addTrack(final MatroskaFileTrack track)

public long writeTracks(final DataWriter ioDW)
{
myPosition = ioDW.getFilePointer();
myStartPosition = ioDW.getFilePointer();
final MasterElement tracksElem = MatroskaDocTypes.Tracks.getInstance();

for (final MatroskaFileTrack track : tracks)
{
tracksElem.addChildElement(track.toElement());
}
tracksElem.writeElement(ioDW);
assert BLOCK_SIZE > tracksElem.getTotalSize();
new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW);
return BLOCK_SIZE;

if (BLOCK_SIZE < tracksElem.getTotalSize() && ioDW.isSeekable()
// do the shuffling the data only if the file is big enough to contain the data
// if it is not it means we are writing the file for the first time and we don't need to shuffle the data
&& ioDW.length() > myStartPosition + tracksElem.getTotalSize())
{
long len;

// we need to write beyond the void space we have reserved
// copy beginning of file into a temporary file
try (FileDataWriter tmp = ((FileDataWriter)ioDW).copyBeginningOfFile())
{
// write the tracks
len = tracksElem.writeElement(tmp);

// now let's copy the rest of the original file by first setting the position after the tracks
ioDW.seek(myEndPosition);

// copy the rest of the original file
((FileDataWriter)ioDW).copyEndOfFile(tmp);

myEndPosition = myStartPosition + len;

ioDW.seek(myEndPosition);
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}

// we need to update tags element that its current position changed as we moved the data and inserted
// some data before tags
this.listeners.firePropertyChange(RESIZED, BLOCK_SIZE, len);

return len;
}

long size = tracksElem.writeElement(ioDW);

myEndPosition = ioDW.getFilePointer();
if (BLOCK_SIZE > tracksElem.getTotalSize() && ioDW.isSeekable())
{
new VoidElement(BLOCK_SIZE - tracksElem.getTotalSize()).writeElement(ioDW);
myEndPosition = ioDW.getFilePointer();
return BLOCK_SIZE;
}

return size;
}

public long update(final DataWriter ioDW)
{
LOG.info("Updating tracks list!");
final long start = ioDW.getFilePointer();
ioDW.seek(myPosition);
ioDW.seek(myStartPosition);
long len = writeTracks(ioDW);
ioDW.seek(start);
return len;
}

public void addPropertyChangeListener(PropertyChangeListener listener)
{
this.listeners.addPropertyChangeListener(listener);
}
}
Loading

0 comments on commit b310849

Please sign in to comment.