summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorByteHamster <ByteHamster@users.noreply.github.com>2022-10-23 12:10:07 +0200
committerGitHub <noreply@github.com>2022-10-23 12:10:07 +0200
commitcac231a461c11f64eae3284553d6b69f6f65a052 (patch)
tree41f489afc026da647c13860193c788031e5b9dfc
parente6613807c08e74433424883ff0e6f30c50f66fc6 (diff)
parentc7e41c31b61e830eabf4df6eeb06d86d36939d97 (diff)
downloadAntennaPod-cac231a461c11f64eae3284553d6b69f6f65a052.zip
Merge pull request #6153 from ByteHamster/fast-document-file
Speed up local folder refresh
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java79
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FastDocumentFile.java77
-rw-r--r--core/src/test/java/androidx/documentfile/provider/AssetsDocumentFile.java138
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java98
-rw-r--r--parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ID3Reader.java13
-rw-r--r--parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/model/FrameHeader.java11
7 files changed, 175 insertions, 250 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
index d37bc230d..8ee924243 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
@@ -8,8 +8,8 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.documentfile.provider.DocumentFile;
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
@@ -24,7 +24,10 @@ import java.util.Locale;
import java.util.Set;
import java.util.UUID;
+import androidx.annotation.VisibleForTesting;
+import androidx.documentfile.provider.DocumentFile;
import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.util.FastDocumentFile;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
@@ -46,12 +49,22 @@ import org.apache.commons.io.input.CountingInputStream;
public class LocalFeedUpdater {
private static final String TAG = "LocalFeedUpdater";
- static final String[] PREFERRED_FEED_IMAGE_FILENAMES = { "folder.jpg", "Folder.jpg", "folder.png", "Folder.png" };
+ static final String[] PREFERRED_FEED_IMAGE_FILENAMES = {"folder.jpg", "Folder.jpg", "folder.png", "Folder.png"};
public static void updateFeed(Feed feed, Context context,
@Nullable UpdaterProgressListener updaterProgressListener) {
try {
- tryUpdateFeed(feed, context, updaterProgressListener);
+ String uriString = feed.getDownload_url().replace(Feed.PREFIX_LOCAL_FOLDER, "");
+ DocumentFile documentFolder = DocumentFile.fromTreeUri(context, Uri.parse(uriString));
+ if (documentFolder == null) {
+ throw new IOException("Unable to retrieve document tree. "
+ + "Try re-connecting the folder on the podcast info page.");
+ }
+ if (!documentFolder.exists() || !documentFolder.canRead()) {
+ throw new IOException("Cannot read local directory. "
+ + "Try re-connecting the folder on the podcast info page.");
+ }
+ tryUpdateFeed(feed, context, documentFolder.getUri(), updaterProgressListener);
if (mustReportDownloadSuccessful(feed)) {
reportSuccess(feed);
@@ -62,19 +75,9 @@ public class LocalFeedUpdater {
}
}
- private static void tryUpdateFeed(Feed feed, Context context, UpdaterProgressListener updaterProgressListener)
- throws IOException {
- String uriString = feed.getDownload_url().replace(Feed.PREFIX_LOCAL_FOLDER, "");
- DocumentFile documentFolder = DocumentFile.fromTreeUri(context, Uri.parse(uriString));
- if (documentFolder == null) {
- throw new IOException("Unable to retrieve document tree. "
- + "Try re-connecting the folder on the podcast info page.");
- }
- if (!documentFolder.exists() || !documentFolder.canRead()) {
- throw new IOException("Cannot read local directory. "
- + "Try re-connecting the folder on the podcast info page.");
- }
-
+ @VisibleForTesting
+ static void tryUpdateFeed(Feed feed, Context context, Uri folderUri,
+ UpdaterProgressListener updaterProgressListener) {
if (feed.getItems() == null) {
feed.setItems(new ArrayList<>());
}
@@ -82,9 +85,10 @@ public class LocalFeedUpdater {
feed = DBTasks.updateFeed(context, feed, false);
// list files in feed folder
- List<DocumentFile> mediaFiles = new ArrayList<>();
+ List<FastDocumentFile> allFiles = FastDocumentFile.list(context, folderUri);
+ List<FastDocumentFile> mediaFiles = new ArrayList<>();
Set<String> mediaFileNames = new HashSet<>();
- for (DocumentFile file : documentFolder.listFiles()) {
+ for (FastDocumentFile file : allFiles) {
String mimeType = MimeTypeUtils.getMimeType(file.getType(), file.getUri().toString());
MediaType mediaType = MediaType.fromMimeType(mimeType);
if (mediaType == MediaType.AUDIO || mediaType == MediaType.VIDEO) {
@@ -117,7 +121,7 @@ public class LocalFeedUpdater {
}
}
- feed.setImageUrl(getImageUrl(documentFolder));
+ feed.setImageUrl(getImageUrl(allFiles, folderUri));
feed.getPreferences().setAutoDownload(false);
feed.getPreferences().setAutoDeleteAction(FeedPreferences.AutoDeleteAction.NO);
@@ -135,17 +139,18 @@ public class LocalFeedUpdater {
* Returns the image URL for the local feed.
*/
@NonNull
- static String getImageUrl(@NonNull DocumentFile documentFolder) {
+ static String getImageUrl(List<FastDocumentFile> files, Uri folderUri) {
// look for special file names
for (String iconLocation : PREFERRED_FEED_IMAGE_FILENAMES) {
- DocumentFile image = documentFolder.findFile(iconLocation);
- if (image != null) {
- return image.getUri().toString();
+ for (FastDocumentFile file : files) {
+ if (iconLocation.equals(file.getName())) {
+ return file.getUri().toString();
+ }
}
}
// use the first image in the folder if existing
- for (DocumentFile file : documentFolder.listFiles()) {
+ for (FastDocumentFile file : files) {
String mime = file.getType();
if (mime != null && (mime.startsWith("image/jpeg") || mime.startsWith("image/png"))) {
return file.getUri().toString();
@@ -153,7 +158,7 @@ public class LocalFeedUpdater {
}
// use default icon as fallback
- return Feed.PREFIX_GENERATIVE_COVER + documentFolder.getUri();
+ return Feed.PREFIX_GENERATIVE_COVER + folderUri;
}
private static FeedItem feedContainsFile(Feed feed, String filename) {
@@ -166,26 +171,36 @@ public class LocalFeedUpdater {
return null;
}
- private static FeedItem createFeedItem(Feed feed, DocumentFile file, Context context) {
+ private static FeedItem createFeedItem(Feed feed, FastDocumentFile file, Context context) {
FeedItem item = new FeedItem(0, file.getName(), UUID.randomUUID().toString(),
- file.getName(), new Date(file.lastModified()), FeedItem.UNPLAYED, feed);
+ file.getName(), new Date(file.getLastModified()), FeedItem.UNPLAYED, feed);
item.disableAutoDownload();
- long size = file.length();
+ long size = file.getLength();
FeedMedia media = new FeedMedia(0, item, 0, 0, size, file.getType(),
file.getUri().toString(), file.getUri().toString(), false, null, 0, 0);
item.setMedia(media);
+ for (FeedItem existingItem : feed.getItems()) {
+ if (existingItem.getMedia() != null
+ && existingItem.getMedia().getDownload_url().equals(file.getUri().toString())
+ && file.getLength() == existingItem.getMedia().getSize()) {
+ // We found an old file that we already scanned. Re-use metadata.
+ item.updateFromOther(existingItem);
+ return item;
+ }
+ }
+
+ // Did not find existing item. Scan metadata.
try {
loadMetadata(item, file, context);
} catch (Exception e) {
item.setDescriptionIfLonger(e.getMessage());
}
-
return item;
}
- private static void loadMetadata(FeedItem item, DocumentFile file, Context context) {
+ private static void loadMetadata(FeedItem item, FastDocumentFile file, Context context) {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(context, file.getUri());
@@ -211,9 +226,11 @@ public class LocalFeedUpdater {
item.getMedia().setDuration((int) Long.parseLong(durationStr));
item.getMedia().setHasEmbeddedPicture(mediaMetadataRetriever.getEmbeddedPicture() != null);
+ mediaMetadataRetriever.close();
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
- Id3MetadataReader reader = new Id3MetadataReader(new CountingInputStream(inputStream));
+ Id3MetadataReader reader = new Id3MetadataReader(
+ new CountingInputStream(new BufferedInputStream(inputStream)));
reader.readInputStream();
item.setDescriptionIfLonger(reader.getComment());
} catch (IOException | ID3ReaderException e) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
index 723ea1d47..cc63bf2b0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java
@@ -23,6 +23,7 @@ import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.input.CountingInputStream;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -120,17 +121,17 @@ public class ChapterUtils {
if (!source.exists()) {
throw new IOException("Local file does not exist");
}
- return new CountingInputStream(new FileInputStream(source));
+ return new CountingInputStream(new BufferedInputStream(new FileInputStream(source)));
} else if (playable.getStreamUrl().startsWith(ContentResolver.SCHEME_CONTENT)) {
Uri uri = Uri.parse(playable.getStreamUrl());
- return new CountingInputStream(context.getContentResolver().openInputStream(uri));
+ return new CountingInputStream(new BufferedInputStream(context.getContentResolver().openInputStream(uri)));
} else {
Request request = new Request.Builder().url(playable.getStreamUrl()).build();
Response response = AntennapodHttpClient.getHttpClient().newCall(request).execute();
if (response.body() == null) {
throw new IOException("Body is null");
}
- return new CountingInputStream(response.body().byteStream());
+ return new CountingInputStream(new BufferedInputStream(response.body().byteStream()));
}
}
@@ -171,7 +172,7 @@ public class ChapterUtils {
@NonNull
private static List<Chapter> readOggChaptersFromInputStream(InputStream input) throws VorbisCommentReaderException {
- VorbisCommentChapterReader reader = new VorbisCommentChapterReader(input);
+ VorbisCommentChapterReader reader = new VorbisCommentChapterReader(new BufferedInputStream(input));
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
if (chapters == null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FastDocumentFile.java b/core/src/main/java/de/danoeh/antennapod/core/util/FastDocumentFile.java
new file mode 100644
index 000000000..885d66687
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FastDocumentFile.java
@@ -0,0 +1,77 @@
+package de.danoeh.antennapod.core.util;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Android's DocumentFile is slow because every single method call queries the ContentResolver.
+ * This queries the ContentResolver a single time with all the information.
+ */
+public class FastDocumentFile {
+ private final String name;
+ private final String type;
+ private final Uri uri;
+ private final long length;
+ private final long lastModified;
+
+ public static List<FastDocumentFile> list(Context context, Uri folderUri) {
+ if (android.os.Build.VERSION.SDK_INT < 21) {
+ return Collections.emptyList();
+ }
+
+ Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri,
+ DocumentsContract.getDocumentId(folderUri));
+ Cursor cursor = context.getContentResolver().query(childrenUri, new String[] {
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_SIZE,
+ DocumentsContract.Document.COLUMN_LAST_MODIFIED,
+ DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null);
+ ArrayList<FastDocumentFile> list = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ String id = cursor.getString(0);
+ Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, id);
+ String name = cursor.getString(1);
+ long size = cursor.getLong(2);
+ long lastModified = cursor.getLong(3);
+ String mimeType = cursor.getString(4);
+ list.add(new FastDocumentFile(name, mimeType, uri, size, lastModified));
+ }
+ cursor.close();
+ return list;
+ }
+
+ public FastDocumentFile(String name, String type, Uri uri, long length, long lastModified) {
+ this.name = name;
+ this.type = type;
+ this.uri = uri;
+ this.length = length;
+ this.lastModified = lastModified;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+}
diff --git a/core/src/test/java/androidx/documentfile/provider/AssetsDocumentFile.java b/core/src/test/java/androidx/documentfile/provider/AssetsDocumentFile.java
deleted file mode 100644
index 8a8205c10..000000000
--- a/core/src/test/java/androidx/documentfile/provider/AssetsDocumentFile.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package androidx.documentfile.provider;
-
-import android.content.res.AssetManager;
-import android.net.Uri;
-import android.webkit.MimeTypeMap;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.IOException;
-
-/**
- * <p>Wraps an Android assets file or folder as a DocumentFile object.</p>
- *
- * <p>This is used to emulate access to the external storage.</p>
- */
-public class AssetsDocumentFile extends DocumentFile {
-
- /**
- * Absolute file path in the assets folder.
- */
- @NonNull
- private final String fileName;
-
- @NonNull
- private final AssetManager assetManager;
-
- public AssetsDocumentFile(@NonNull String fileName, @NonNull AssetManager assetManager) {
- super(null);
- this.fileName = fileName;
- this.assetManager = assetManager;
- }
-
- @Nullable
- @Override
- public DocumentFile createFile(@NonNull String mimeType, @NonNull String displayName) {
- return null;
- }
-
- @Nullable
- @Override
- public DocumentFile createDirectory(@NonNull String displayName) {
- return null;
- }
-
- @NonNull
- @Override
- public Uri getUri() {
- return Uri.parse(fileName);
- }
-
- @Nullable
- @Override
- public String getName() {
- int pos = fileName.indexOf('/');
- if (pos >= 0) {
- return fileName.substring(pos + 1);
- } else {
- return fileName;
- }
- }
-
- @Nullable
- @Override
- public String getType() {
- String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
- return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
- }
-
- @Override
- public boolean isDirectory() {
- return false;
- }
-
- @Override
- public boolean isFile() {
- return true;
- }
-
- @Override
- public boolean isVirtual() {
- return false;
- }
-
- @Override
- public long lastModified() {
- return 0;
- }
-
- @Override
- public long length() {
- return 0;
- }
-
- @Override
- public boolean canRead() {
- return true;
- }
-
- @Override
- public boolean canWrite() {
- return false;
- }
-
- @Override
- public boolean delete() {
- return false;
- }
-
- @Override
- public boolean exists() {
- return true;
- }
-
- @NonNull
- @Override
- public DocumentFile[] listFiles() {
- try {
- String[] files = assetManager.list(fileName);
- if (files == null) {
- return new DocumentFile[0];
- }
- DocumentFile[] result = new DocumentFile[files.length];
- for (int i = 0; i < files.length; i++) {
- String subFileName = fileName + '/' + files[i];
- result[i] = new AssetsDocumentFile(subFileName, assetManager);
- }
- return result;
- } catch (IOException e) {
- return new DocumentFile[0];
- }
- }
-
- @Override
- public boolean renameTo(@NonNull String displayName) {
- return false;
- }
-}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
index 05b0584ed..4cf746ba4 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
@@ -7,11 +7,10 @@ import android.net.Uri;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
-import androidx.documentfile.provider.AssetsDocumentFile;
-import androidx.documentfile.provider.DocumentFile;
import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.util.FastDocumentFile;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
@@ -24,11 +23,12 @@ import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowMediaMetadataRetriever;
-import java.io.IOException;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import de.danoeh.antennapod.core.ApplicationCallbacks;
import de.danoeh.antennapod.core.ClientConfig;
@@ -60,8 +60,8 @@ public class LocalFeedUpdaterTest {
*/
private static final String FEED_URL =
"content://com.android.externalstorage.documents/tree/primary%3ADownload%2Flocal-feed";
- private static final String LOCAL_FEED_DIR1 = "local-feed1";
- private static final String LOCAL_FEED_DIR2 = "local-feed2";
+ private static final String LOCAL_FEED_DIR1 = "src/test/assets/local-feed1";
+ private static final String LOCAL_FEED_DIR2 = "src/test/assets/local-feed2";
private Context context;
@@ -172,74 +172,61 @@ public class LocalFeedUpdaterTest {
Feed feed = verifySingleFeedInDatabase();
List<FeedItem> feedItems = DBReader.getFeedItemList(feed);
- FeedItem feedItem = feedItems.get(0);
-
- assertEquals("track1.mp3", feedItem.getTitle());
-
- Date pubDate = feedItem.getPubDate();
- Calendar calendar = GregorianCalendar.getInstance();
- calendar.setTime(pubDate);
- assertEquals(2020, calendar.get(Calendar.YEAR));
- assertEquals(6 - 1, calendar.get(Calendar.MONTH));
- assertEquals(1, calendar.get(Calendar.DAY_OF_MONTH));
- assertEquals(22, calendar.get(Calendar.HOUR_OF_DAY));
- assertEquals(23, calendar.get(Calendar.MINUTE));
- assertEquals(24, calendar.get(Calendar.SECOND));
+ assertEquals("track1.mp3", feedItems.get(0).getTitle());
}
@Test
public void testGetImageUrl_EmptyFolder() {
- DocumentFile documentFolder = mockDocumentFolder();
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(Collections.emptyList(), Uri.EMPTY);
assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
@Test
public void testGetImageUrl_NoImageButAudioFiles() {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"));
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ List<FastDocumentFile> folder = Collections.singletonList(mockDocumentFile("audio.mp3", "audio/mp3"));
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
@Test
public void testGetImageUrl_PreferredImagesFilenames() {
for (String filename : LocalFeedUpdater.PREFERRED_FEED_IMAGE_FILENAMES) {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
+ List<FastDocumentFile> folder = Arrays.asList(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile(filename, "image/jpeg")); // image MIME type doesn't matter
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, endsWith(filename));
}
}
@Test
public void testGetImageUrl_OtherImageFilenameJpg() {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
+ List<FastDocumentFile> folder = Arrays.asList(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.jpg", "image/jpeg"));
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, endsWith("my-image.jpg"));
}
@Test
public void testGetImageUrl_OtherImageFilenameJpeg() {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
+ List<FastDocumentFile> folder = Arrays.asList(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.jpeg", "image/jpeg"));
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, endsWith("my-image.jpeg"));
}
@Test
public void testGetImageUrl_OtherImageFilenamePng() {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
+ List<FastDocumentFile> folder = Arrays.asList(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.png", "image/png"));
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, endsWith("my-image.png"));
}
@Test
public void testGetImageUrl_OtherImageFilenameUnsupportedMimeType() {
- DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
+ List<FastDocumentFile> folder = Arrays.asList(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.svg", "image/svg+xml"));
- String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(folder, Uri.EMPTY);
assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
@@ -248,9 +235,8 @@ public class LocalFeedUpdaterTest {
*
* @param localFeedDir assets local feed folder with media files
*/
- private void mapDummyMetadata(@NonNull String localFeedDir) throws IOException {
- String[] fileNames = context.getAssets().list(localFeedDir);
- for (String fileName : fileNames) {
+ private void mapDummyMetadata(@NonNull String localFeedDir) {
+ for (String fileName : Objects.requireNonNull(new File(localFeedDir).list())) {
String path = localFeedDir + '/' + fileName;
ShadowMediaMetadataRetriever.addMetadata(path,
MediaMetadataRetriever.METADATA_KEY_DURATION, "10");
@@ -259,24 +245,21 @@ public class LocalFeedUpdaterTest {
ShadowMediaMetadataRetriever.addMetadata(path,
MediaMetadataRetriever.METADATA_KEY_DATE, "20200601T222324");
}
-
}
/**
- * Calls the method {@link LocalFeedUpdater#updateFeed(Feed, Context)} with
- * the given local feed folder.
+ * Calls the method LocalFeedUpdater#tryUpdateFeed with the given local feed folder.
*
* @param localFeedDir assets local feed folder with media files
*/
private void callUpdateFeed(@NonNull String localFeedDir) {
- DocumentFile documentFile = new AssetsDocumentFile(localFeedDir, context.getAssets());
- try (MockedStatic<DocumentFile> dfMock = Mockito.mockStatic(DocumentFile.class)) {
+ try (MockedStatic<FastDocumentFile> dfMock = Mockito.mockStatic(FastDocumentFile.class)) {
// mock external storage
- dfMock.when(() -> DocumentFile.fromTreeUri(any(), any())).thenReturn(documentFile);
+ dfMock.when(() -> FastDocumentFile.list(any(), any())).thenReturn(mockLocalFolder(localFeedDir));
// call method to test
Feed feed = new Feed(FEED_URL, null);
- LocalFeedUpdater.updateFeed(feed, context, null);
+ LocalFeedUpdater.tryUpdateFeed(feed, context, null, null);
}
}
@@ -306,21 +289,18 @@ public class LocalFeedUpdaterTest {
* Create a DocumentFile mock object.
*/
@NonNull
- private static DocumentFile mockDocumentFile(@NonNull String fileName, @NonNull String mimeType) {
- DocumentFile file = mock(DocumentFile.class);
- when(file.getName()).thenReturn(fileName);
- when(file.getUri()).thenReturn(Uri.parse("file:///path/" + fileName));
- when(file.getType()).thenReturn(mimeType);
- return file;
+ private static FastDocumentFile mockDocumentFile(@NonNull String fileName, @NonNull String mimeType) {
+ return new FastDocumentFile(fileName, mimeType, Uri.parse("file:///path/" + fileName), 0, 0);
}
- /**
- * Create a DocumentFile folder mock object with a list of files.
- */
- @NonNull
- private static DocumentFile mockDocumentFolder(DocumentFile... files) {
- DocumentFile documentFolder = mock(DocumentFile.class);
- when(documentFolder.listFiles()).thenReturn(files);
- return documentFolder;
+ private static List<FastDocumentFile> mockLocalFolder(String folderName) {
+ List<FastDocumentFile> files = new ArrayList<>();
+ for (File f : Objects.requireNonNull(new File(folderName).listFiles())) {
+ String extension = MimeTypeMap.getFileExtensionFromUrl(f.getPath());
+ String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ files.add(new FastDocumentFile(f.getName(), mimeType,
+ Uri.parse(f.toURI().toString()), f.length(), f.lastModified()));
+ }
+ return files;
}
}
diff --git a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ID3Reader.java b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ID3Reader.java
index baaff2752..a791baf66 100644
--- a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ID3Reader.java
+++ b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/ID3Reader.java
@@ -46,7 +46,7 @@ public class ID3Reader {
}
protected void readFrame(@NonNull FrameHeader frameHeader) throws IOException, ID3ReaderException {
- Log.d(TAG, "Skipping frame: " + frameHeader.toString());
+ Log.d(TAG, "Skipping frame: " + frameHeader.getId() + ", size: " + frameHeader.getSize());
skipBytes(frameHeader.getSize());
}
@@ -106,7 +106,7 @@ public class ID3Reader {
@NonNull
FrameHeader readFrameHeader() throws IOException {
- String id = readIsoStringFixed(FRAME_ID_LENGTH);
+ String id = readPlainBytesToString(FRAME_ID_LENGTH);
int size = readInt();
if (tagHeader != null && tagHeader.getVersion() >= 0x0400) {
size = unsynchsafe(size);
@@ -136,15 +136,14 @@ public class ID3Reader {
return readEncodedString(encoding, max - 1);
}
- @SuppressWarnings("CharsetObjectCanBeUsed")
- protected String readIsoStringFixed(int length) throws IOException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ protected String readPlainBytesToString(int length) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
int bytesRead = 0;
while (bytesRead < length) {
- bytes.write(readByte());
+ stringBuilder.append((char) readByte());
bytesRead++;
}
- return Charset.forName("ISO-8859-1").newDecoder().decode(ByteBuffer.wrap(bytes.toByteArray())).toString();
+ return stringBuilder.toString();
}
protected String readIsoStringNullTerminated(int max) throws IOException {
diff --git a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/model/FrameHeader.java b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/model/FrameHeader.java
index 1a298a0df..3dd19008c 100644
--- a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/model/FrameHeader.java
+++ b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/id3/model/FrameHeader.java
@@ -1,18 +1,7 @@
package de.danoeh.antennapod.parser.media.id3.model;
-import androidx.annotation.NonNull;
-
public class FrameHeader extends Header {
- private final short flags;
-
public FrameHeader(String id, int size, short flags) {
super(id, size);
- this.flags = flags;
- }
-
- @Override
- @NonNull
- public String toString() {
- return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, size);
}
}