From 11d80786d4d1c81ffef29ac6a45e2bb7d24b480a Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 10 Sep 2018 13:31:58 +0300 Subject: [PATCH] image-loader Fix data scheme handler --- .../markwon/il/AsyncDrawableLoader.java | 12 +++ .../markwon/il/DataUriSchemeHandler.java | 8 +- .../ru/noties/markwon/il/SchemeHandler.java | 4 + .../markwon/il/DataUriSchemeHandlerTest.java | 85 +++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java index 0009c855..6c31280b 100644 --- a/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/AsyncDrawableLoader.java @@ -79,6 +79,14 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { // todo: should we cancel pending request for the same destination? // we _could_ but there is possibility that one resource is request in multiple places + // todo: error handing (simply applying errorDrawable is not a good solution + // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc) + + // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal + // for big images for sure. We _could_ introduce internal Drawable that will check for + // image bounds (but we will need to cache inputStream in order to inspect and optimize + // input image...) + // todo, if not a link -> show placeholder return executorService.submit(new Runnable() { @@ -176,6 +184,10 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader { return out; } + // todo: as now we have different layers of abstraction (for scheme handling and media decoding) + // we no longer should add dependencies implicitly, it would be way better to allow adding + // multiple artifacts (file, data, network, svg, gif)... at least, maybe we can extract API + // for this module (without implementations), but keep _all-in_ (fat) artifact with all of these. public static class Builder { private OkHttpClient client; diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java index e93aa256..73f415af 100644 --- a/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/DataUriSchemeHandler.java @@ -19,6 +19,8 @@ public class DataUriSchemeHandler extends SchemeHandler { return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create()); } + private static final String START = "data://"; + private final DataUriParser uriParser; private final DataUriDecoder uriDecoder; @@ -32,12 +34,12 @@ public class DataUriSchemeHandler extends SchemeHandler { @Override public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { - final String part = uri.getSchemeSpecificPart(); - - if (TextUtils.isEmpty(part)) { + if (!raw.startsWith(START)) { return null; } + final String part = raw.substring(START.length()); + final DataUri dataUri = uriParser.parse(part); if (dataUri == null) { return null; diff --git a/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java index 3a7f9fc7..6d8a44d1 100644 --- a/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java +++ b/markwon-image-loader/src/main/java/ru/noties/markwon/il/SchemeHandler.java @@ -16,6 +16,10 @@ public abstract class SchemeHandler { public abstract void cancel(@NonNull String raw); + /** + * Will be called only once during initialization, should return schemes that are + * handled by this handler + */ @NonNull public abstract Collection schemes(); } diff --git a/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java new file mode 100644 index 00000000..5274c5fb --- /dev/null +++ b/markwon-image-loader/src/test/java/ru/noties/markwon/il/DataUriSchemeHandlerTest.java @@ -0,0 +1,85 @@ +package ru.noties.markwon.il; + +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DataUriSchemeHandlerTest { + + private DataUriSchemeHandler handler; + + @Before + public void before() { + handler = DataUriSchemeHandler.create(); + } + + @Test + public void scheme_specific_part_is_empty() { + assertNull(handler.handle("data:", Uri.parse("data:"))); + } + + @Test + public void data_uri_is_empty() { + assertNull(handler.handle("data://whatever", Uri.parse("data://whatever"))); + } + + @Test + public void no_data() { + assertNull(handler.handle("data://,", Uri.parse("data://,"))); + } + + @Test + public void correct() { + + final class Item { + + final String contentType; + final String data; + + Item(String contentType, String data) { + this.contentType = contentType; + this.data = data; + } + } + + final Map expected = new HashMap() {{ + put("data://text/plain;,123", new Item("text/plain", "123")); + put("data://image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123")); + }}; + + for (Map.Entry entry : expected.entrySet()) { + final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey())); + assertNotNull(entry.getKey(), item); + assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType()); + assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream())); + } + } + + @NonNull + private static String readStream(@NonNull InputStream stream) { + try { + final Scanner scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A"); + return scanner.hasNext() + ? scanner.next() + : ""; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } +} \ No newline at end of file