Skip to content

Commit 71ef6d9

Browse files
committed
Add a DownloadService to manage remote downloads
1 parent 63cf9dc commit 71ef6d9

File tree

5 files changed

+336
-0
lines changed

5 files changed

+336
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*-
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.download;
34+
35+
import java.io.IOException;
36+
37+
import org.scijava.io.handle.DataHandle;
38+
import org.scijava.io.handle.DataHandleService;
39+
import org.scijava.io.location.Location;
40+
import org.scijava.plugin.Parameter;
41+
import org.scijava.plugin.Plugin;
42+
import org.scijava.service.AbstractService;
43+
import org.scijava.service.Service;
44+
import org.scijava.task.Task;
45+
import org.scijava.task.TaskService;
46+
47+
/**
48+
* Default implementation of {@link DownloadService}.
49+
*
50+
* @author Curtis Rueden
51+
*/
52+
@Plugin(type = Service.class)
53+
public class DefaultDownloadService extends AbstractService implements
54+
DownloadService
55+
{
56+
57+
@Parameter
58+
private DataHandleService dataHandleService;
59+
60+
@Parameter
61+
private TaskService taskService;
62+
63+
@Override
64+
public Download download(final Location source, final Location destination) {
65+
final Task task = taskService.createTask("Download");
66+
final Download download = new Download() {
67+
68+
@Override
69+
public Location source() {
70+
return source;
71+
}
72+
73+
@Override
74+
public Location destination() {
75+
return destination;
76+
}
77+
78+
@Override
79+
public Task task() {
80+
return task;
81+
}
82+
};
83+
task.run(() -> {
84+
try (final DataHandle<Location> in = dataHandleService.create(source);
85+
final DataHandle<Location> out = dataHandleService.create(
86+
destination))
87+
{
88+
task.setStatusMessage("Downloading " + source.getURI());
89+
copy(task, in, out);
90+
}
91+
catch (final IOException exc) {
92+
// TODO: Improve error handling:
93+
// 1. Consider a better exception handling design here.
94+
// 2. Retry at least a few times if something goes wrong.
95+
throw new RuntimeException(exc);
96+
}
97+
});
98+
return download;
99+
}
100+
101+
// -- Helper methods --
102+
103+
private void copy(final Task task, final DataHandle<Location> in,
104+
final DataHandle<Location> out) throws IOException
105+
{
106+
long length;
107+
try {
108+
length = in.length();
109+
}
110+
catch (final IOException exc) {
111+
// Assume unknown length.
112+
length = 0;
113+
}
114+
if (length > 0) task.setProgressMaximum(length);
115+
116+
final int chunkSize = 64 * 1024; // TODO: Make size configurable.
117+
final byte[] buf = new byte[chunkSize];
118+
while (true) {
119+
if (task.isCanceled()) return;
120+
final int r = in.read(buf);
121+
if (r <= 0) break; // EOF
122+
if (task.isCanceled()) return;
123+
out.write(buf, 0, r);
124+
if (length > 0) task.setProgressValue(task.getProgressValue() + r);
125+
}
126+
}
127+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*-
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
package org.scijava.download;
33+
34+
import org.scijava.io.location.Location;
35+
import org.scijava.task.Task;
36+
37+
/**
38+
* Object representing an asynchronous download task.
39+
*
40+
* @author Curtis Rueden
41+
* @see Task
42+
*/
43+
public interface Download {
44+
45+
Location source();
46+
Location destination();
47+
Task task();
48+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*-
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.download;
34+
35+
import org.scijava.io.location.Location;
36+
import org.scijava.service.SciJavaService;
37+
38+
/**
39+
* Service for managing retrieval of remote resources.
40+
*
41+
* @author Curtis Rueden
42+
*/
43+
public interface DownloadService extends SciJavaService {
44+
45+
/**
46+
* Downloads data from the given source, storing it into the given
47+
* destination.
48+
*
49+
* @param source The location of the needed data.
50+
* @param destination The location where the needed data should be stored.
51+
*/
52+
Download download(Location source, Location destination);
53+
}

src/test/java/org/scijava/ContextCreationTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public void testFull() {
9494
org.scijava.console.DefaultConsoleService.class,
9595
org.scijava.convert.DefaultConvertService.class,
9696
org.scijava.display.DefaultDisplayService.class,
97+
org.scijava.download.DefaultDownloadService.class,
9798
org.scijava.event.DefaultEventHistory.class,
9899
org.scijava.input.DefaultInputService.class,
99100
org.scijava.io.DefaultIOService.class,
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*-
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
package org.scijava.download;
33+
34+
import static org.junit.Assert.assertArrayEquals;
35+
36+
import java.io.File;
37+
import java.io.IOException;
38+
import java.util.concurrent.ExecutionException;
39+
40+
import org.junit.After;
41+
import org.junit.Before;
42+
import org.junit.Test;
43+
import org.scijava.Context;
44+
import org.scijava.io.location.FileLocation;
45+
import org.scijava.io.location.Location;
46+
import org.scijava.util.FileUtils;
47+
import org.scijava.util.MersenneTwisterFast;
48+
49+
/**
50+
* Tests {@link DownloadService}.
51+
*
52+
* @author Curtis Rueden
53+
*/
54+
public class DownloadServiceTest {
55+
56+
private DownloadService downloadService;
57+
58+
@Before
59+
public void setUp() {
60+
final Context ctx = new Context(DownloadService.class);
61+
downloadService = ctx.service(DownloadService.class);
62+
}
63+
64+
@After
65+
public void tearDown() {
66+
downloadService.context().dispose();
67+
}
68+
69+
@Test
70+
public void testDownload() throws IOException, InterruptedException,
71+
ExecutionException
72+
{
73+
final byte[] data = randomBytes(0xbabebabe);
74+
75+
final String prefix = getClass().getName();
76+
final File inFile = File.createTempFile(prefix, "testDownloadIn");
77+
final File outFile = File.createTempFile(prefix, "testDownloadOut");
78+
79+
try {
80+
FileUtils.writeFile(inFile, data);
81+
82+
final Location src = new FileLocation(inFile);
83+
final Location dest = new FileLocation(outFile);
84+
85+
final Download download = downloadService.download(src, dest);
86+
download.task().waitFor();
87+
88+
final byte[] result = FileUtils.readFile(outFile);
89+
assertArrayEquals(data, result);
90+
}
91+
finally {
92+
inFile.delete();
93+
outFile.delete();
94+
}
95+
}
96+
97+
// -- Helper methods --
98+
99+
private byte[] randomBytes(final long seed) {
100+
final MersenneTwisterFast r = new MersenneTwisterFast(seed);
101+
final byte[] data = new byte[2938740];
102+
for (int i = 0; i < data.length; i++) {
103+
data[i] = r.nextByte();
104+
}
105+
return data;
106+
}
107+
}

0 commit comments

Comments
 (0)