Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Handles case where we overflow void elements. #4

Merged
merged 8 commits into from
Dec 18, 2024
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
Loading