Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 5 additions & 29 deletions src/main/java/org/scijava/download/DefaultDownloadService.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import org.scijava.io.handle.DataHandle;
import org.scijava.io.handle.DataHandleService;
import org.scijava.io.handle.DataHandles;
import org.scijava.io.location.Location;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
Expand Down Expand Up @@ -70,7 +71,7 @@ public Download download(final Location source, final Location destination) {
destination))
{
task.setStatusMessage("Downloading " + source.getURI());
copy(task, in, out);
DataHandles.copy(in, out, task);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍

}
catch (final IOException exc) {
// TODO: Improve error handling:
Expand Down Expand Up @@ -102,13 +103,13 @@ public Download download(final Location source, final Location destination,
if (isCachedHandleValid(source, cache, sourceHandle, cachedHandle)) {
// The data is cached; download from the cached source instead.
task.setStatusMessage("Retrieving " + source.getURI());
copy(task, cachedHandle, destHandle);
DataHandles.copy(cachedHandle, destHandle, task);
}
else {
// Data is not yet cached; write to the destination _and_ the cache.
task.setStatusMessage("Downloading + caching " + source.getURI());
copy(task, sourceHandle, //
new MultiWriteHandle(cachedHandle, destHandle));
DataHandles.copy(sourceHandle, //
new MultiWriteHandle(cachedHandle, destHandle), task);
}
}
catch (final IOException exc) {
Expand All @@ -122,31 +123,6 @@ public Download download(final Location source, final Location destination,

// -- Helper methods --

private void copy(final Task task, final DataHandle<Location> in,
final DataHandle<Location> out) throws IOException
{
long length;
try {
length = in.length();
}
catch (final IOException exc) {
// Assume unknown length.
length = 0;
}
if (length > 0) task.setProgressMaximum(length);

final int chunkSize = 64 * 1024; // TODO: Make size configurable.
final byte[] buf = new byte[chunkSize];
while (true) {
if (task.isCanceled()) return;
final int r = in.read(buf);
if (r <= 0) break; // EOF
if (task.isCanceled()) return;
out.write(buf, 0, r);
if (length > 0) task.setProgressValue(task.getProgressValue() + r);
}
}

private boolean isCachedHandleValid(final Location source,
final LocationCache cache, final DataHandle<Location> sourceHandle,
final DataHandle<Location> cachedHandle) throws IOException
Expand Down
147 changes: 144 additions & 3 deletions src/main/java/org/scijava/io/handle/DataHandles.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
Expand All @@ -38,10 +38,14 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.scijava.io.location.Location;
import org.scijava.task.Task;

/**
* Utility methods for working with {@link DataHandle}s.
*
*
* @author Curtis Rueden
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add yourself as another author.

* @author Gabriel Einsdorf
*/
public final class DataHandles {

Expand Down Expand Up @@ -108,4 +112,141 @@ private static synchronized void initUTFMethod() {
"No usable DataOutputStream.writeUTF(String, DataOutput)", exc);
}
}

/**
* Copies all bytes from the input to the output handle. Reading and writing
* start at the current positions of the handles.
*
* @param in the input handle
* @param out the output handle
* @return the number of bytes copied
* @throws IOException if an I/O error occurs.
*/
public static long copy(final DataHandle<Location> in,
final DataHandle<Location> out) throws IOException
{
return copy(in, out, 0l, null);
}

/**
* Copies all bytes from the input to the output handle, reporting the
* progress to the provided task. Reading and writing start at the current
* positions of the handles.
*
* @param in the input handle
* @param out the output handle
* @param task task to report progress to
* @return the number of bytes copied
* @throws IOException if an I/O error occurs.
*/
public static long copy(final DataHandle<Location> in,
final DataHandle<Location> out, final Task task) throws IOException
{
return copy(in, out, 0l, task);
}

/**
* Copies up to <code>length</code> bytes from the input to the output handle.
* Reading and writing start at the current positions of the handles. Stops
* early if there are no more bytes available from the input handle.
*
* @param in the input handle
* @param out the output handle
* @param length maximum number of bytes to copy; will copy all bytes if set
* to <code>0</code>
* @return the number of bytes copied
* @throws IOException if an I/O error occurs.
*/
public static long copy(final DataHandle<Location> in,
final DataHandle<Location> out, final int length) throws IOException
{
return copy(in, out, length, null);
}

/**
* Copies up to <code>length</code> bytes from the input to the output handle,
* reporting the progress to the provided task. Reading and writing start at
* the current positions of the handles. Stops early if there are no more
* bytes available from the input handle.
*
* @param in input handle
* @param out the output handle
* @param length maximum number of bytes to copy; will copy all bytes if set
* to <code>0</code>
* @param task a task object to use for reporting the status of the copy
* operation. Can be <code>null</code> if no reporting is needed.
* @return the number of bytes copied
* @throws IOException if an I/O error occurs.
*/
public static long copy(final DataHandle<Location> in,
final DataHandle<Location> out, final long length, final Task task)
throws IOException
{
return copy(in, out, length, task, 64 * 1024);
}

/**
* Copies up to <code>length</code> bytes from the input to the output handle,
* reporting the progress to the provided task. Reading and writing start at
* the current positions of the handles. Stops early if there are no more
* bytes available from the input handle. Uses a buffer of the provided size,
* instead of using the default size.
*
* @param in input handle
* @param out the output handle
* @param length maximum number of bytes to copy, will copy all bytes if set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More grammatically correct would be ; instead of ,. Otherwise it's a comma splice.

* to <code>0</code>
* @param task a task object to use for reporting the status of the copy
* operation. Can be <code>null</code> if no reporting is needed.
* @return the number of bytes copied
* @throws IOException if an I/O error occurs.
*/
public static long copy(final DataHandle<Location> in,
final DataHandle<Location> out, final long length, final Task task,
final int bufferSize) throws IOException
{

// get length of input
final long inputlength;
{
long i = 0;
try {
i = in.length();
}
catch (final IOException exc) {
// Assume unknown length.
i = 0;
}
inputlength = i;
}

if (task != null) {
if (length > 0) task.setProgressMaximum(length);
else if (inputlength > 0) task.setProgressMaximum(inputlength);
}

final byte[] buffer = new byte[bufferSize];
long totalRead = 0;

while (true) {
if (task != null && task.isCanceled()) break;
final int r;
// ensure we do not read more than required into the buffer
if (length > 0 && totalRead + bufferSize > length) {
int remaining = (int) (length - totalRead);
r = in.read(buffer, 0, remaining);
}
else {
r = in.read(buffer);
}
if (r <= 0) break; // EOF
if (task != null && task.isCanceled()) break;
out.write(buffer, 0, r);
totalRead += r;
if (task != null) {
task.setProgressValue(task.getProgressValue() + r);
}
}
return totalRead;
}
}
152 changes: 152 additions & 0 deletions src/test/java/org/scijava/io/handle/DataHandlesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*-
* #%L
* SciJava Common shared library for SciJava software.
* %%
* Copyright (C) 2009 - 2017 Board of Regents of the University of
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
* Institute of Molecular Cell Biology and Genetics, University of
* Konstanz, and KNIME GmbH.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header.

package org.scijava.io.handle;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.scijava.Context;
import org.scijava.event.EventService;
import org.scijava.io.location.BytesLocation;
import org.scijava.io.location.Location;
import org.scijava.task.DefaultTask;
import org.scijava.task.Task;
import org.scijava.thread.ThreadService;
import org.scijava.util.MersenneTwisterFast;

/**
* Tests for utility methods in the {@link DataHandles} class.
*
* @author Gabriel Einsdorf
*/
public class DataHandlesTest {

private static final int TEST_SIZE = 2_938_740;
private DataHandleService handles;
private Location inFile;
private BytesLocation outFile;
private byte[] data;
private ThreadService threadService;
private EventService eventService;

@Before
public void classSetup() {
final Context ctx = new Context(DataHandleService.class,
ThreadService.class, EventService.class);
handles = ctx.service(DataHandleService.class);
threadService = ctx.service(ThreadService.class);
eventService = ctx.service(EventService.class);

data = randomBytes(0xbabebabe);
inFile = new BytesLocation(data);
outFile = new BytesLocation(TEST_SIZE);
}

@After
public void cleanup() {
handles.context().dispose();
}

@Test
public void testDefaultCopy() throws IOException {
try (DataHandle<Location> src = handles.create(inFile);
final DataHandle<Location> dest = handles.create(outFile))
{
DataHandles.copy(src, dest);
assertHandleEquals(data, dest);
}
}

@Test
public void testCopyTask() throws IOException {
try (DataHandle<Location> src = handles.create(inFile);
final DataHandle<Location> dest = handles.create(outFile))
{
final Task t = new DefaultTask(threadService, eventService);
DataHandles.copy(src, dest, t);
assertEquals(t.getProgressValue(), src.length());
assertHandleEquals(data, dest);
}
}

@Test
public void testCopyLength() throws IOException {
final int sliceSize = 50_000;
try (DataHandle<Location> src = handles.create(inFile);
final DataHandle<Location> dest = handles.create(outFile))
{
DataHandles.copy(src, dest, sliceSize);
final byte[] expected = new byte[sliceSize];
System.arraycopy(data, 0, expected, 0, sliceSize);
assertHandleEquals(expected, dest);
}
}

@Test
public void testCopyLengthTask() throws IOException {
final int sliceSize = 50_000;
try (DataHandle<Location> src = handles.create(inFile);
final DataHandle<Location> dest = handles.create(outFile))
{
final Task t = new DefaultTask(threadService, eventService);
DataHandles.copy(src, dest, sliceSize, t);
assertEquals(t.getProgressValue(), sliceSize);

final byte[] expected = new byte[sliceSize];
System.arraycopy(data, 0, expected, 0, sliceSize);
assertHandleEquals(expected, dest);
}
}

private void assertHandleEquals(final byte[] expected,
final DataHandle<Location> handle) throws IOException
{
handle.seek(0);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], handle.readByte());
}
}

private byte[] randomBytes(final long seed) {
final MersenneTwisterFast r = new MersenneTwisterFast(seed);
final byte[] ldata = new byte[TEST_SIZE];
for (int i = 0; i < ldata.length; i++) {
ldata[i] = r.nextByte();
}
return ldata;
}
}