From 16360e62e6f8c0b48b5d43d2252841a22e90bfd4 Mon Sep 17 00:00:00 2001 From: kudanai Date: Mon, 26 Oct 2020 20:33:41 +0500 Subject: [PATCH 1/8] preliminary dotlottie support --- .../lottie/LottieCompositionFactory.java | 41 ++++++++++++++++++- .../airbnb/lottie/network/NetworkFetcher.java | 2 +- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 0c686f7202..c55b59f660 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -49,6 +49,13 @@ public class LottieCompositionFactory { */ private static final Map> taskCache = new HashMap<>(); + /** + * reference magic bytes for zip compressed files. + * useful to determine if an InputStream is a zip file or not + */ + private static final int[] MAGIC = new int[] { 0x50, 0x4b, 0x03, 0x04 }; + + private LottieCompositionFactory() { } @@ -181,7 +188,13 @@ public static LottieResult fromAssetSync(Context context, Str @WorkerThread public static LottieResult fromAssetSync(Context context, String fileName, @Nullable String cacheKey) { try { - if (fileName.endsWith(".zip")) { + if(fileName.endsWith(".lottie")) { + // in case the .lottie is just a renamed .json + boolean isZip = isZipCompressed(context.getAssets().open(fileName)); + if(isZip) { + return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey); + } + } else if (fileName.endsWith(".zip")) { return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey); } return fromJsonInputStreamSync(context.getAssets().open(fileName), cacheKey); @@ -253,6 +266,13 @@ public static LottieResult fromRawResSync(Context context, @R @WorkerThread public static LottieResult fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) { try { + // performance note: raw res opened twice isn't exactly idea, + // but it's the only way we can tell if the res is a .zip or not + boolean isZip = isZipCompressed(context.getResources().openRawResource(rawRes)); + if(isZip) { + return fromZipStreamSync(new ZipInputStream(context.getResources().openRawResource(rawRes)), cacheKey); + } + return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), cacheKey); } catch (Resources.NotFoundException e) { return new LottieResult<>(e); @@ -423,6 +443,8 @@ private static LottieResult fromZipStreamSyncInternal(ZipInpu final String entryName = entry.getName(); if (entryName.contains("__MACOSX")) { inputStream.closeEntry(); + } else if (entry.getName().equalsIgnoreCase("manifest.json")) { //ignore .lottie manifest + inputStream.closeEntry(); } else if (entry.getName().contains(".json")) { com.airbnb.lottie.parser.moshi.JsonReader reader = of(buffer(source(inputStream))); composition = LottieCompositionFactory.fromJsonReaderSyncInternal(reader, null, false).getValue(); @@ -465,6 +487,23 @@ private static LottieResult fromZipStreamSyncInternal(ZipInpu return new LottieResult<>(composition); } + /** + * Check if a given InputStream points to a .zip compressed file + */ + private static Boolean isZipCompressed(InputStream inputStream) { + try { + for (int value : MAGIC) { + if (inputStream.read() != value) { + return false; + } + } + inputStream.close(); + return true; + } catch (IOException e) { + return false; + } + } + @Nullable private static LottieImageAsset findImageAssetForFileName(LottieComposition composition, String fileName) { for (LottieImageAsset asset : composition.getImages().values()) { diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java index 21fbc3400e..5d26431b03 100644 --- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java +++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java @@ -108,7 +108,7 @@ private LottieResult fromInputStream(@NonNull String url, @No // in the result which is more useful than failing here. contentType = "application/json"; } - if (contentType.contains("application/zip")) { + if (contentType.contains("application/zip") || url.contains(".lottie")) { Logger.debug("Handling zip response."); extension = FileExtension.ZIP; result = fromZipStream(url, inputStream, cacheKey); From 7fbbc42238ca983395cde4f1c24772a0fe78807d Mon Sep 17 00:00:00 2001 From: kudanai Date: Tue, 27 Oct 2020 12:13:51 +0500 Subject: [PATCH 2/8] fix NetworkFetcher logic check make opinionated decision all .lottie MUST be .zip --- .../com/airbnb/lottie/LottieCompositionFactory.java | 10 ++-------- .../java/com/airbnb/lottie/network/NetworkFetcher.java | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index c55b59f660..48f9ef8f26 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -188,13 +188,7 @@ public static LottieResult fromAssetSync(Context context, Str @WorkerThread public static LottieResult fromAssetSync(Context context, String fileName, @Nullable String cacheKey) { try { - if(fileName.endsWith(".lottie")) { - // in case the .lottie is just a renamed .json - boolean isZip = isZipCompressed(context.getAssets().open(fileName)); - if(isZip) { - return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey); - } - } else if (fileName.endsWith(".zip")) { + if (fileName.endsWith(".zip") || fileName.endsWith(".lottie")) { return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey); } return fromJsonInputStreamSync(context.getAssets().open(fileName), cacheKey); @@ -266,7 +260,7 @@ public static LottieResult fromRawResSync(Context context, @R @WorkerThread public static LottieResult fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) { try { - // performance note: raw res opened twice isn't exactly idea, + // performance note: raw res opened twice isn't exactly ideal, // but it's the only way we can tell if the res is a .zip or not boolean isZip = isZipCompressed(context.getResources().openRawResource(rawRes)); if(isZip) { diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java index 5d26431b03..75a8ab69d7 100644 --- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java +++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java @@ -108,7 +108,7 @@ private LottieResult fromInputStream(@NonNull String url, @No // in the result which is more useful than failing here. contentType = "application/json"; } - if (contentType.contains("application/zip") || url.contains(".lottie")) { + if (contentType.contains("application/zip") || url.split("\\?")[0].endsWith(".lottie")) { Logger.debug("Handling zip response."); extension = FileExtension.ZIP; result = fromZipStream(url, inputStream, cacheKey); From c477bcf26f19dfb8074f4c0904b36e84ac231e38 Mon Sep 17 00:00:00 2001 From: kudanai Date: Tue, 27 Oct 2020 13:22:44 +0500 Subject: [PATCH 3/8] use Okio to read rawRes to use peek() --- .../lottie/LottieCompositionFactory.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 48f9ef8f26..c3e6e6f89f 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -9,7 +9,6 @@ import com.airbnb.lottie.model.LottieCompositionCache; import com.airbnb.lottie.parser.LottieCompositionMoshiParser; import com.airbnb.lottie.parser.moshi.JsonReader; - import com.airbnb.lottie.utils.Utils; import org.json.JSONObject; @@ -27,8 +26,10 @@ import androidx.annotation.Nullable; import androidx.annotation.RawRes; import androidx.annotation.WorkerThread; +import okio.BufferedSource; +import okio.Okio; -import static com.airbnb.lottie.parser.moshi.JsonReader.*; +import static com.airbnb.lottie.parser.moshi.JsonReader.of; import static com.airbnb.lottie.utils.Utils.closeQuietly; import static okio.Okio.buffer; import static okio.Okio.source; @@ -53,7 +54,7 @@ public class LottieCompositionFactory { * reference magic bytes for zip compressed files. * useful to determine if an InputStream is a zip file or not */ - private static final int[] MAGIC = new int[] { 0x50, 0x4b, 0x03, 0x04 }; + private static final byte[] MAGIC = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; private LottieCompositionFactory() { @@ -260,14 +261,11 @@ public static LottieResult fromRawResSync(Context context, @R @WorkerThread public static LottieResult fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) { try { - // performance note: raw res opened twice isn't exactly ideal, - // but it's the only way we can tell if the res is a .zip or not - boolean isZip = isZipCompressed(context.getResources().openRawResource(rawRes)); - if(isZip) { - return fromZipStreamSync(new ZipInputStream(context.getResources().openRawResource(rawRes)), cacheKey); + BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes))); + if(isZipCompressed(source)) { + return fromZipStreamSync(new ZipInputStream(source.inputStream()), cacheKey); } - - return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), cacheKey); + return fromJsonInputStreamSync(source.inputStream(), cacheKey); } catch (Resources.NotFoundException e) { return new LottieResult<>(e); } @@ -484,18 +482,21 @@ private static LottieResult fromZipStreamSyncInternal(ZipInpu /** * Check if a given InputStream points to a .zip compressed file */ - private static Boolean isZipCompressed(InputStream inputStream) { + private static Boolean isZipCompressed(BufferedSource inputSource) { + try { - for (int value : MAGIC) { - if (inputStream.read() != value) { + BufferedSource peek = inputSource.peek(); + for(byte b: MAGIC) { + if(peek.readByte()!=b) return false; - } } - inputStream.close(); + peek.close(); return true; - } catch (IOException e) { + } catch (Exception e) { + e.printStackTrace(); return false; } + } @Nullable From 5e2885bfb2fd9818ab8064ca074c8845686c843d Mon Sep 17 00:00:00 2001 From: Naail Abdul Rahman Date: Tue, 27 Oct 2020 20:57:47 +0500 Subject: [PATCH 4/8] Update lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java Co-authored-by: Gabriel Peal --- .../main/java/com/airbnb/lottie/LottieCompositionFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index c3e6e6f89f..f26eea1afe 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -486,7 +486,7 @@ private static Boolean isZipCompressed(BufferedSource inputSource) { try { BufferedSource peek = inputSource.peek(); - for(byte b: MAGIC) { + for (byte b: MAGIC) { if(peek.readByte()!=b) return false; } From e21324de8f7c598a4b9f2b6e39dba53c73531d9f Mon Sep 17 00:00:00 2001 From: Naail Abdul Rahman Date: Tue, 27 Oct 2020 20:58:00 +0500 Subject: [PATCH 5/8] Update lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java Co-authored-by: Gabriel Peal --- .../main/java/com/airbnb/lottie/LottieCompositionFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index f26eea1afe..96bc729d93 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -487,7 +487,7 @@ private static Boolean isZipCompressed(BufferedSource inputSource) { try { BufferedSource peek = inputSource.peek(); for (byte b: MAGIC) { - if(peek.readByte()!=b) + if(peek.readByte() != b) return false; } peek.close(); From 00fd159e9faef7c6f34c4d86425b3936a646c4f8 Mon Sep 17 00:00:00 2001 From: Naail Abdul Rahman Date: Tue, 27 Oct 2020 20:58:45 +0500 Subject: [PATCH 6/8] Update lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java Co-authored-by: Gabriel Peal --- .../main/java/com/airbnb/lottie/LottieCompositionFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 96bc729d93..344853dd86 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -493,7 +493,7 @@ private static Boolean isZipCompressed(BufferedSource inputSource) { peek.close(); return true; } catch (Exception e) { - e.printStackTrace(); + L.error(e, "Failed to check zip file header") return false; } From 130f46b84f863f53d1860d35f0e594137f17922b Mon Sep 17 00:00:00 2001 From: Naail Abdul Rahman Date: Tue, 27 Oct 2020 20:59:49 +0500 Subject: [PATCH 7/8] Update lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java Co-authored-by: Gabriel Peal --- .../main/java/com/airbnb/lottie/LottieCompositionFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 344853dd86..3edead6c54 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -262,7 +262,7 @@ public static LottieResult fromRawResSync(Context context, @R public static LottieResult fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) { try { BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes))); - if(isZipCompressed(source)) { + if (isZipCompressed(source)) { return fromZipStreamSync(new ZipInputStream(source.inputStream()), cacheKey); } return fromJsonInputStreamSync(source.inputStream(), cacheKey); From 713b5d4310e206aa6c222819230e35a435b03c78 Mon Sep 17 00:00:00 2001 From: kudanai Date: Tue, 27 Oct 2020 21:04:16 +0500 Subject: [PATCH 8/8] [fix] oops on the logger --- .../main/java/com/airbnb/lottie/LottieCompositionFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 3edead6c54..344bf988db 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -9,6 +9,7 @@ import com.airbnb.lottie.model.LottieCompositionCache; import com.airbnb.lottie.parser.LottieCompositionMoshiParser; import com.airbnb.lottie.parser.moshi.JsonReader; +import com.airbnb.lottie.utils.Logger; import com.airbnb.lottie.utils.Utils; import org.json.JSONObject; @@ -493,7 +494,7 @@ private static Boolean isZipCompressed(BufferedSource inputSource) { peek.close(); return true; } catch (Exception e) { - L.error(e, "Failed to check zip file header") + Logger.error("Failed to check zip file header", e); return false; }