convert EmojiPreference and EmojiCompatFont to Kotlin (#1922)

* convert EmojiPreference and EmojiCompatFont to Kotlin

* move preference related to to dedicated preference package

* update proguard-rules.pro

* reformat & add comment

* maintain disposable information in EmojiPreference instead of EmojiCompatFont
This commit is contained in:
Konrad Pozniak 2020-09-02 12:27:51 +02:00 committed by GitHub
commit 1d309850b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 709 additions and 906 deletions

View file

@ -1,564 +0,0 @@
package com.keylesspalace.tusky.util;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.keylesspalace.tusky.R;
import java.io.EOFException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.c1710.filemojicompat.FileEmojiCompatConfig;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
/**
* This class bundles information about an emoji font as well as many convenient actions.
*/
public class EmojiCompatFont {
private static final String TAG = "EmojiCompatFont";
/**
* This String represents the sub-directory the fonts are stored in.
*/
private static final String DIRECTORY = "emoji";
// These are the items which are also present in the JSON files
private final String name, display, url;
// The thumbnail image and the caption are provided as resource ids
private final int img, caption;
// The version is stored as a String in the x.xx.xx format (to be able to compare versions)
private final String version;
private final int[] versionCode;
private AsyncTask fontDownloader;
// The system font gets some special behavior...
private static final EmojiCompatFont SYSTEM_DEFAULT =
new EmojiCompatFont("system-default",
"System Default",
R.string.caption_systememoji,
R.drawable.ic_emoji_34dp,
"",
"0");
private static final EmojiCompatFont BLOBMOJI =
new EmojiCompatFont("Blobmoji",
"Blobmoji",
R.string.caption_blobmoji,
R.drawable.ic_blobmoji,
"https://tusky.app/hosted/emoji/BlobmojiCompat.ttf",
"12.0.0"
);
private static final EmojiCompatFont TWEMOJI =
new EmojiCompatFont("Twemoji",
"Twemoji",
R.string.caption_twemoji,
R.drawable.ic_twemoji,
"https://tusky.app/hosted/emoji/TwemojiCompat.ttf",
"12.0.0"
);
private static final EmojiCompatFont NOTOEMOJI =
new EmojiCompatFont("NotoEmoji",
"Noto Emoji",
R.string.caption_notoemoji,
R.drawable.ic_notoemoji,
"https://tusky.app/hosted/emoji/NotoEmojiCompat.ttf",
"11.0.0"
);
/**
* This array stores all available EmojiCompat fonts.
* References to them can simply be saved by saving their indices
*/
public static final EmojiCompatFont[] FONTS = {SYSTEM_DEFAULT, BLOBMOJI, TWEMOJI, NOTOEMOJI};
// A list of all available font files and whether they are older than the current version or not
// They are ordered by there version codes in ascending order
private ArrayList<Pair<File, int[]>> existingFontFiles;
private EmojiCompatFont(String name,
String display,
int caption,
int img,
String url,
String version) {
this.name = name;
this.display = display;
this.caption = caption;
this.img = img;
this.url = url;
this.version = version;
this.versionCode = getVersionCode(version);
}
/**
* Returns the Emoji font associated with this ID
*
* @param id the ID of this font
* @return the corresponding font. Will default to SYSTEM_DEFAULT if not in range.
*/
public static EmojiCompatFont byId(int id) {
if (id >= 0 && id < FONTS.length) {
return FONTS[id];
} else {
return SYSTEM_DEFAULT;
}
}
public int getId() {
return Arrays.asList(FONTS).indexOf(this);
}
public String getName() {
return name;
}
public String getDisplay(Context context) {
return this != SYSTEM_DEFAULT ? display : context.getString(R.string.system_default);
}
public String getCaption(Context context) {
return context.getResources().getString(caption);
}
public String getUrl() {
return url;
}
public Drawable getThumb(Context context) {
return ContextCompat.getDrawable(context, img);
}
public String getVersion() {
return version;
}
public int[] getVersionCode() {
return versionCode;
}
/**
* This method will return the actual font file (regardless of its existence) for
* the current version (not necessarily the latest!).
*
* @return The font (TTF) file or null if called on SYSTEM_FONT
*/
@Nullable
private File getFont(Context context) {
if (this != SYSTEM_DEFAULT) {
File directory = new File(context.getExternalFilesDir(null), DIRECTORY);
return new File(directory, this.getName() + this.getVersion() + ".ttf");
} else {
return null;
}
}
public FileEmojiCompatConfig getConfig(Context context) {
return new FileEmojiCompatConfig(context, getLatestFontFile(context));
}
public boolean isDownloaded(Context context) {
// The existence of the current version is actually checked twice, although the first method should
// be much faster and more common.
return this == SYSTEM_DEFAULT || getFont(context) != null
&& (getFont(context).exists() || newerFileExists(context));
}
/**
* Checks whether there is already a font version that satisfies the current version, i.e. it
* has a higher or equal version code.
*
* @param context The Context
* @return Whether there is a font file with a higher or equal version code to the current
*/
private boolean newerFileExists(Context context) {
loadExistingFontFiles(context);
if (!existingFontFiles.isEmpty())
// The last file is already the newest one...
return compareVersions(existingFontFiles.get(existingFontFiles.size() - 1).second,
getVersionCode()) >= 0;
return false;
}
/**
* Downloads the TTF file for this font
*
* @param listeners The listeners which will be notified when the download has been finished
*/
public void downloadFont(Context context, Downloader.EmojiDownloadListener... listeners) {
if (this != SYSTEM_DEFAULT) {
// Additionally run a cleanup process after the download has been successful.
Downloader.EmojiDownloadListener cleanup = font -> deleteOldVersions(context);
List<Downloader.EmojiDownloadListener> allListeners
= new ArrayList<>(Arrays.asList(listeners));
allListeners.add(cleanup);
Downloader.EmojiDownloadListener[] allListenersA =
new Downloader.EmojiDownloadListener[allListeners.size()];
fontDownloader = new Downloader(
this,
allListeners.toArray(allListenersA))
.execute(getFont(context));
} else {
for (Downloader.EmojiDownloadListener listener : listeners) {
// The system emoji font is always downloaded...
listener.onDownloaded(this);
}
}
}
/**
* Deletes any older version of a font
*
* @param context The current Context
*/
private void deleteOldVersions(Context context) {
loadExistingFontFiles(context);
Log.d(TAG, "deleting old versions...");
Log.d(TAG, String.format("deleteOldVersions: Found %d other font files", existingFontFiles.size()));
for (Pair<File, int[]> fileExists : existingFontFiles) {
if (compareVersions(fileExists.second, getVersionCode()) < 0) {
File file = fileExists.first;
// Uses side effects!
Log.d(TAG, String.format("Deleted %s successfully: %s", file.getAbsolutePath(),
file.delete()));
}
}
}
private static final Comparator<Pair<File, int[]>> pairComparator = (o1, o2) -> compareVersions(o1.second, o2.second);
/**
* Loads all font files that are inside the files directory into an ArrayList with the information
* on whether they are older than the currently available version or not.
*
* @param context The Context
*/
private void loadExistingFontFiles(Context context) {
// Only load it once
if (this.existingFontFiles == null) {
// If we call this on the system default font, just return nothing...
if (this == SYSTEM_DEFAULT) {
existingFontFiles = new ArrayList<>(0);
}
File directory = new File(context.getExternalFilesDir(null), DIRECTORY);
// It will search for old versions using a regex that matches the font's name plus
// (if present) a version code. No version code will be regarded as version 0.
Pattern fontRegex = Pattern.compile(getName() + "(\\d+(\\.\\d+)*)?" + "\\.ttf");
FilenameFilter ttfFilter = (dir, name) -> name.endsWith(".ttf");
File[] existingFontFiles = directory.isDirectory() ? directory.listFiles(ttfFilter) : new File[0];
Log.d(TAG, String.format("loadExistingFontFiles: %d other font files found",
existingFontFiles.length));
// This is actually the upper bound
this.existingFontFiles = new ArrayList<>(existingFontFiles.length);
for (File file : existingFontFiles) {
Matcher matcher = fontRegex.matcher(file.getName());
if (matcher.matches()) {
String version = matcher.group(1);
int[] versionCode = getVersionCode(version);
Pair<File, int[]> entry = new Pair<>(file, versionCode);
// https://stackoverflow.com/a/51893026
// Insert it in a sorted way
int index = Collections.binarySearch(this.existingFontFiles, entry, pairComparator);
if (index < 0) {
index = -index - 1;
}
this.existingFontFiles.add(index, entry);
}
}
}
}
/**
* Returns the current or latest version of this font file (if there is any)
*
* @param context The Context
* @return The file for this font with the current or (if not existent) highest version code or null if there is no file for this font.
*/
private File getLatestFontFile(@NonNull Context context) {
File current = getFont(context);
if (current != null && current.exists())
return current;
loadExistingFontFiles(context);
try {
return existingFontFiles.get(existingFontFiles.size() - 1).first;
} catch (IndexOutOfBoundsException e) {
return getFont(context);
}
}
private @Nullable
int[] getVersionCode(@Nullable String version) {
if (version == null)
return null;
String[] versions = version.split("\\.");
int[] versionCodes = new int[versions.length];
for (int i = 0; i < versions.length; i++)
versionCodes[i] = parseInt(versions[i], 0);
return versionCodes;
}
/**
* A small helper method to convert a String to an int with a default value
*
* @param value The String to be parsed
* @param def The default value
* @return Either the String parsed to an int or - if this is not possible - the default value
*/
private int parseInt(@Nullable String value, int def) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException | NullPointerException e) {
e.printStackTrace();
return def;
}
}
/**
* Compares two version codes to each other
*
* @param versionA The first version
* @param versionB The second version
* @return -1 if versionA < versionB, 1 if versionA > versionB and 0 otherwise
*/
private static int compareVersions(int[] versionA, int[] versionB) {
// This saves us much headache about handling a null version
if (versionA == null)
versionA = new int[]{0};
int len = Math.max(versionB.length, versionA.length);
int vA, vB;
// Compare the versions
for (int i = 0; i < len; i++) {
// Just to make sure there is something specified here
if (versionA.length > i) {
vA = versionA[i];
} else {
vA = 0;
}
if (versionB.length > i) {
vB = versionB[i];
} else {
vB = 0;
}
// It needs to be decided on the next level
if (vB == vA)
continue;
// Okay, is version B newer or version A?
return Integer.compare(vA, vB);
}
// The versions are equal
return 0;
}
/**
* Stops downloading the font. If no one started a font download, nothing happens.
*/
public void cancelDownload() {
if (fontDownloader != null) {
fontDownloader.cancel(false);
fontDownloader = null;
}
}
/**
* This class is used to easily manage the download of a font
*/
public static class Downloader extends AsyncTask<File, Float, File> {
// All interested objects/methods
private final EmojiDownloadListener[] listeners;
// The MIME-Type which might be unnecessary
private static final String MIME = "application/woff";
// The font belonging to this download
private final EmojiCompatFont font;
private static final String TAG = "Emoji-Font Downloader";
private static long CHUNK_SIZE = 4096;
private boolean failed = false;
Downloader(EmojiCompatFont font, EmojiDownloadListener... listeners) {
super();
this.listeners = listeners;
this.font = font;
}
@Override
protected File doInBackground(File... files) {
// Only download to one file...
File downloadFile = files[0];
try {
// It is possible (and very likely) that the file does not exist yet
if (!downloadFile.exists()) {
downloadFile.getParentFile().mkdirs();
downloadFile.createNewFile();
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(font.getUrl())
.addHeader("Content-Type", MIME)
.build();
Response response = client.newCall(request).execute();
BufferedSink sink = Okio.buffer(Okio.sink(downloadFile));
Source source = null;
try {
long size;
// Download!
if (response.body() != null
&& response.isSuccessful()
&& (size = networkResponseLength(response)) > 0) {
float progress = 0;
source = response.body().source();
try {
while (!isCancelled()) {
sink.write(response.body().source(), CHUNK_SIZE);
progress += CHUNK_SIZE;
publishProgress(progress / size);
}
} catch (EOFException ex) {
/*
This means we've finished downloading the file since sink.write
will throw an EOFException when the file to be read is empty.
*/
}
} else {
Log.e(TAG, "downloading " + font.getUrl() + " failed. No content to download.");
Log.e(TAG, "Status code: " + response.code());
failed = true;
}
} finally {
if (source != null) {
source.close();
}
sink.close();
// This 'if' uses side effects to delete the File.
if (isCancelled() && !downloadFile.delete()) {
Log.e(TAG, "Could not delete file " + downloadFile);
}
}
} catch (IOException ex) {
ex.printStackTrace();
failed = true;
}
return downloadFile;
}
@Override
public void onProgressUpdate(Float... progress) {
for (EmojiDownloadListener listener : listeners) {
listener.onProgress(progress[0]);
}
}
@Override
public void onPostExecute(File downloadedFile) {
if (!failed && downloadedFile.exists()) {
for (EmojiDownloadListener listener : listeners) {
listener.onDownloaded(font);
}
} else {
fail(downloadedFile);
}
}
private void fail(File failedFile) {
if (failedFile.exists() && !failedFile.delete()) {
Log.e(TAG, "Could not delete file " + failedFile);
}
for (EmojiDownloadListener listener : listeners) {
listener.onFailed();
}
}
/**
* This interfaced is used to get notified when a download has been finished
*/
public interface EmojiDownloadListener {
/**
* Called after successfully finishing a download.
*
* @param font The font related to this download. This will help identifying the download
*/
void onDownloaded(EmojiCompatFont font);
// TODO: Add functionality
/**
* Called when something went wrong with the download.
* This one won't be called when the download has been cancelled though.
*/
default void onFailed() {
// Oh no! D:
}
/**
* Called whenever the progress changed
*
* @param Progress A value between 0 and 1 representing the current progress
*/
default void onProgress(float Progress) {
// ARE WE THERE YET?
}
}
/**
* This method is needed because when transparent compression is used OkHttp reports
* {@link ResponseBody#contentLength()} as -1. We try to get the header which server sent
* us manually here.
*
* @see <a href="https://github.com/square/okhttp/issues/259">OkHttp issue 259</a>
*/
private long networkResponseLength(Response response) {
Response networkResponse = response.networkResponse();
if (networkResponse == null) {
// In case it's a fully cached response
ResponseBody body = response.body();
return body == null ? -1 : body.contentLength();
}
String header = networkResponse.header("Content-Length");
if (header == null) {
return -1;
}
try {
return Integer.parseInt(header);
} catch (NumberFormatException e) {
return -1;
}
}
}
@Override
@NonNull
public String toString() {
return display;
}
}

View file

@ -0,0 +1,351 @@
package com.keylesspalace.tusky.util
import android.content.Context
import android.util.Log
import android.util.Pair
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.keylesspalace.tusky.R
import de.c1710.filemojicompat.FileEmojiCompatConfig
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.internal.toLongOrDefault
import okio.Source
import okio.buffer
import okio.sink
import java.io.EOFException
import java.io.File
import java.io.FilenameFilter
import java.io.IOException
import kotlin.math.max
/**
* This class bundles information about an emoji font as well as many convenient actions.
*/
class EmojiCompatFont(
val name: String,
private val display: String,
@StringRes val caption: Int,
@DrawableRes val img: Int,
val url: String,
// The version is stored as a String in the x.xx.xx format (to be able to compare versions)
val version: String) {
private val versionCode = getVersionCode(version)
// A list of all available font files and whether they are older than the current version or not
// They are ordered by their version codes in ascending order
private var existingFontFileCache: List<Pair<File, List<Int>>>? = null
val id: Int
get() = FONTS.indexOf(this)
fun getDisplay(context: Context): String {
return if (this !== SYSTEM_DEFAULT) display else context.getString(R.string.system_default)
}
/**
* This method will return the actual font file (regardless of its existence) for
* the current version (not necessarily the latest!).
*
* @return The font (TTF) file or null if called on SYSTEM_FONT
*/
private fun getFontFile(context: Context): File? {
return if (this !== SYSTEM_DEFAULT) {
val directory = File(context.getExternalFilesDir(null), DIRECTORY)
File(directory, "$name$version.ttf")
} else {
null
}
}
fun getConfig(context: Context): FileEmojiCompatConfig {
return FileEmojiCompatConfig(context, getLatestFontFile(context))
}
fun isDownloaded(context: Context): Boolean {
return this === SYSTEM_DEFAULT || getFontFile(context)?.exists() == true || fontFileExists(context)
}
/**
* Checks whether there is already a font version that satisfies the current version, i.e. it
* has a higher or equal version code.
*
* @param context The Context
* @return Whether there is a font file with a higher or equal version code to the current
*/
private fun fontFileExists(context: Context): Boolean {
val existingFontFiles = getExistingFontFiles(context)
return if (existingFontFiles.isNotEmpty()) {
compareVersions(existingFontFiles.last().second, versionCode) >= 0
} else {
false
}
}
/**
* Deletes any older version of a font
*
* @param context The current Context
*/
private fun deleteOldVersions(context: Context) {
val existingFontFiles = getExistingFontFiles(context)
Log.d(TAG, "deleting old versions...")
Log.d(TAG, String.format("deleteOldVersions: Found %d other font files", existingFontFiles.size))
for (fileExists in existingFontFiles) {
if (compareVersions(fileExists.second, versionCode) < 0) {
val file = fileExists.first
// Uses side effects!
Log.d(TAG, String.format("Deleted %s successfully: %s", file.absolutePath,
file.delete()))
}
}
}
/**
* Loads all font files that are inside the files directory into an ArrayList with the information
* on whether they are older than the currently available version or not.
*
* @param context The Context
*/
private fun getExistingFontFiles(context: Context): List<Pair<File, List<Int>>> {
// Only load it once
existingFontFileCache?.let {
return it
}
// If we call this on the system default font, just return nothing...
if (this === SYSTEM_DEFAULT) {
existingFontFileCache = emptyList()
return emptyList()
}
val directory = File(context.getExternalFilesDir(null), DIRECTORY)
// It will search for old versions using a regex that matches the font's name plus
// (if present) a version code. No version code will be regarded as version 0.
val fontRegex = "$name(\\d+(\\.\\d+)*)?\\.ttf".toPattern()
val ttfFilter = FilenameFilter { _, name: String -> name.endsWith(".ttf") }
val foundFontFiles = directory.listFiles(ttfFilter).orEmpty()
Log.d(TAG, String.format("loadExistingFontFiles: %d other font files found",
foundFontFiles.size))
return foundFontFiles.map { file ->
val matcher = fontRegex.matcher(file.name)
val versionCode = if (matcher.matches()) {
val version = matcher.group(1)
getVersionCode(version)
} else {
listOf(0)
}
Pair(file, versionCode)
}.sortedWith(
Comparator<Pair<File, List<Int>>> { a, b -> compareVersions(a.second, b.second) }
).also {
existingFontFileCache = it
}
}
/**
* Returns the current or latest version of this font file (if there is any)
*
* @param context The Context
* @return The file for this font with the current or (if not existent) highest version code or null if there is no file for this font.
*/
private fun getLatestFontFile(context: Context): File? {
val current = getFontFile(context)
if (current != null && current.exists()) return current
val existingFontFiles = getExistingFontFiles(context)
return existingFontFiles.firstOrNull()?.first
}
private fun getVersionCode(version: String?): List<Int> {
if (version == null) return listOf(0)
return version.split(".").map {
it.toIntOrNull() ?: 0
}
}
fun downloadFontFile(context: Context,
okHttpClient: OkHttpClient): Observable<Float> {
return Observable.create { emitter: ObservableEmitter<Float> ->
// It is possible (and very likely) that the file does not exist yet
val downloadFile = getFontFile(context)!!
if (!downloadFile.exists()) {
downloadFile.parentFile?.mkdirs()
downloadFile.createNewFile()
}
val request = Request.Builder().url(url)
.build()
val sink = downloadFile.sink().buffer()
var source: Source? = null
try {
// Download!
val response = okHttpClient.newCall(request).execute()
val responseBody = response.body
if (response.isSuccessful && responseBody != null) {
val size = response.length()
var progress = 0f
source = responseBody.source()
try {
while (!emitter.isDisposed) {
sink.write(source, CHUNK_SIZE)
progress += CHUNK_SIZE.toFloat()
if(size > 0) {
emitter.onNext(progress / size)
} else {
emitter.onNext(-1f)
}
}
} catch (ex: EOFException) {
/*
This means we've finished downloading the file since sink.write
will throw an EOFException when the file to be read is empty.
*/
}
} else {
Log.e(TAG, "Downloading $url failed. Status code: ${response.code}")
emitter.tryOnError(Exception())
}
} catch (ex: IOException) {
Log.e(TAG, "Downloading $url failed.", ex)
downloadFile.deleteIfExists()
emitter.tryOnError(ex)
} finally {
source?.close()
sink.close()
if (emitter.isDisposed) {
downloadFile.deleteIfExists()
} else {
deleteOldVersions(context)
emitter.onComplete()
}
}
}
.subscribeOn(Schedulers.io())
}
/**
* Deletes the downloaded file, if it exists. Should be called when a download gets cancelled.
*/
fun deleteDownloadedFile(context: Context) {
getFontFile(context)?.deleteIfExists()
}
override fun toString(): String {
return display
}
companion object {
private const val TAG = "EmojiCompatFont"
/**
* This String represents the sub-directory the fonts are stored in.
*/
private const val DIRECTORY = "emoji"
private const val CHUNK_SIZE = 4096L
// The system font gets some special behavior...
private val SYSTEM_DEFAULT = EmojiCompatFont("system-default",
"System Default",
R.string.caption_systememoji,
R.drawable.ic_emoji_34dp,
"",
"0")
private val BLOBMOJI = EmojiCompatFont("Blobmoji",
"Blobmoji",
R.string.caption_blobmoji,
R.drawable.ic_blobmoji,
"https://tusky.app/hosted/emoji/BlobmojiCompat.ttf",
"12.0.0"
)
private val TWEMOJI = EmojiCompatFont("Twemoji",
"Twemoji",
R.string.caption_twemoji,
R.drawable.ic_twemoji,
"https://tusky.app/hosted/emoji/TwemojiCompat.ttf",
"12.0.0"
)
private val NOTOEMOJI = EmojiCompatFont("NotoEmoji",
"Noto Emoji",
R.string.caption_notoemoji,
R.drawable.ic_notoemoji,
"https://tusky.app/hosted/emoji/NotoEmojiCompat.ttf",
"11.0.0"
)
/**
* This array stores all available EmojiCompat fonts.
* References to them can simply be saved by saving their indices
*/
val FONTS = listOf(SYSTEM_DEFAULT, BLOBMOJI, TWEMOJI, NOTOEMOJI)
/**
* Returns the Emoji font associated with this ID
*
* @param id the ID of this font
* @return the corresponding font. Will default to SYSTEM_DEFAULT if not in range.
*/
fun byId(id: Int): EmojiCompatFont = FONTS.getOrElse(id) { SYSTEM_DEFAULT }
/**
* Compares two version codes to each other
*
* @param versionA The first version
* @param versionB The second version
* @return -1 if versionA < versionB, 1 if versionA > versionB and 0 otherwise
*/
@VisibleForTesting
fun compareVersions(versionA: List<Int>, versionB: List<Int>): Int {
val len = max(versionB.size, versionA.size)
for (i in 0 until len) {
val vA = versionA.getOrElse(i) { 0 }
val vB = versionB.getOrElse(i) { 0 }
// It needs to be decided on the next level
if (vA == vB) continue
// Okay, is version B newer or version A?
return vA.compareTo(vB)
}
// The versions are equal
return 0
}
/**
* This method is needed because when transparent compression is used OkHttp reports
* [ResponseBody.contentLength] as -1. We try to get the header which server sent
* us manually here.
*
* @see [OkHttp issue 259](https://github.com/square/okhttp/issues/259)
*/
private fun Response.length(): Long {
networkResponse?.let {
val header = it.header("Content-Length") ?: return -1
return header.toLongOrDefault(-1)
}
// In case it's a fully cached response
return body?.contentLength() ?: -1
}
private fun File.deleteIfExists() {
if(exists() && !delete()) {
Log.e(TAG, "Could not delete file $this")
}
}
}
}