|
33 | 33 | import org.apache.commons.lang3.function.TriFunction; |
34 | 34 | import org.janelia.saalfeldlab.n5.N5Exception.N5IOException; |
35 | 35 | import org.janelia.saalfeldlab.n5.http.ListResponseParser; |
| 36 | +import org.janelia.saalfeldlab.n5.readdata.ReadData; |
36 | 37 |
|
37 | 38 | import java.io.Closeable; |
| 39 | +import java.io.FileNotFoundException; |
38 | 40 | import java.io.IOException; |
39 | 41 | import java.io.InputStream; |
40 | 42 | import java.io.InputStreamReader; |
@@ -135,6 +137,12 @@ public boolean exists(final String normalPath) { |
135 | 137 | } |
136 | 138 | } |
137 | 139 |
|
| 140 | + @Override public long size(String normalPath) { |
| 141 | + |
| 142 | + final HttpURLConnection head = requireValidHttpResponse(normalPath, "HEAD", "Error checking existence: " + normalPath, true); |
| 143 | + return head.getContentLengthLong(); |
| 144 | + } |
| 145 | + |
138 | 146 | /** |
139 | 147 | * Test whether the path is a directory. |
140 | 148 | * <p> |
@@ -224,12 +232,16 @@ private HttpURLConnection httpRequest(String normalPath, String method) throws I |
224 | 232 | } |
225 | 233 |
|
226 | 234 | @Override |
| 235 | + public ReadData createReadData(final String normalPath) { |
| 236 | + return new KeyValueAccessReadData(new HttpLazyRead(normalPath)); |
| 237 | + } |
| 238 | + |
227 | 239 | public LockedChannel lockForReading(final String normalPath) throws N5IOException { |
228 | 240 | //TODO Caleb: Maybe check exists lazily when attempting to read |
229 | 241 | try { |
230 | 242 | if (!exists(normalPath)) |
231 | 243 | throw new N5Exception.N5NoSuchKeyException("Key does not exist: " + normalPath); |
232 | | - return new HttpObjectChannel(uri(normalPath)); |
| 244 | + return new HttpObjectChannel(uri(normalPath), 0, -1); |
233 | 245 | } catch (URISyntaxException e) { |
234 | 246 | throw new N5Exception("Invalid URI Syntax", e); |
235 | 247 | } |
@@ -341,23 +353,49 @@ public void delete(final String normalPath) { |
341 | 353 | private class HttpObjectChannel implements LockedChannel { |
342 | 354 |
|
343 | 355 | protected final URI uri; |
| 356 | + private final long startByte; |
| 357 | + private final long size; |
344 | 358 | private final ArrayList<Closeable> resources = new ArrayList<>(); |
345 | 359 |
|
346 | | - protected HttpObjectChannel(final URI uri) { |
| 360 | + protected HttpObjectChannel(final URI uri, long startByte, long size) { |
347 | 361 |
|
348 | 362 | this.uri = uri; |
| 363 | + this.startByte = startByte; |
| 364 | + this.size = size; |
| 365 | + } |
| 366 | + |
| 367 | + private boolean isPartialRead() { |
| 368 | + return startByte > 0 || (size >= 0 && size != Long.MAX_VALUE); |
349 | 369 | } |
350 | 370 |
|
351 | 371 | @Override |
352 | 372 | public InputStream newInputStream() throws N5IOException { |
353 | 373 |
|
354 | 374 | try { |
355 | | - return uri.toURL().openStream(); |
| 375 | + HttpURLConnection conn = (HttpURLConnection)uri.toURL().openConnection(); |
| 376 | + if (isPartialRead()) { |
| 377 | + conn.setRequestProperty(RANGE, rangeString()); |
| 378 | + final String acceptRanges = conn.getHeaderField(ACCEPT_RANGE); |
| 379 | + if (acceptRanges == null || !acceptRanges.equals(BYTES)) { |
| 380 | + conn.disconnect(); |
| 381 | + conn = (HttpURLConnection)uri.toURL().openConnection(); |
| 382 | + return ReadData.from(conn.getInputStream()).materialize().slice(startByte, size).inputStream(); |
| 383 | + } |
| 384 | + } |
| 385 | + return conn.getInputStream(); |
| 386 | + } catch (FileNotFoundException e) { |
| 387 | + throw new N5Exception.N5NoSuchKeyException("Could not open stream for " + uri, e); |
356 | 388 | } catch (IOException e) { |
357 | 389 | throw new N5IOException("Could not open stream for " + uri, e); |
358 | 390 | } |
359 | 391 | } |
360 | 392 |
|
| 393 | + private String rangeString() { |
| 394 | + |
| 395 | + final String lastByte = (size > 0) ? Long.toString(startByte + size - 1) : ""; |
| 396 | + return String.format("%s=%d-%s", BYTES, startByte, lastByte); |
| 397 | + } |
| 398 | + |
361 | 399 | @Override |
362 | 400 | public Reader newReader() throws N5IOException { |
363 | 401 |
|
@@ -392,4 +430,29 @@ public void close() throws IOException { |
392 | 430 | } |
393 | 431 | } |
394 | 432 |
|
| 433 | + private class HttpLazyRead implements LazyRead { |
| 434 | + |
| 435 | + private final String normalKey; |
| 436 | + |
| 437 | + HttpLazyRead(String normalKey) { |
| 438 | + this.normalKey = normalKey; |
| 439 | + } |
| 440 | + |
| 441 | + @Override |
| 442 | + public long size() { |
| 443 | + return HttpKeyValueAccess.this.size(normalKey); |
| 444 | + } |
| 445 | + |
| 446 | + @Override |
| 447 | + public ReadData materialize(long offset, long length) { |
| 448 | + try (final HttpObjectChannel ch = new HttpObjectChannel(uri(normalKey), offset, length)) { |
| 449 | + return ReadData.from(ch.newInputStream()).materialize(); |
| 450 | + } catch (IOException e) { |
| 451 | + throw new N5IOException(e); |
| 452 | + } catch (URISyntaxException e) { |
| 453 | + throw new N5Exception(e); |
| 454 | + } |
| 455 | + } |
| 456 | + } |
| 457 | + |
395 | 458 | } |
0 commit comments