diff options
author | ByteHamster <info@bytehamster.com> | 2020-05-25 11:34:01 +0200 |
---|---|---|
committer | ByteHamster <info@bytehamster.com> | 2020-05-25 11:34:03 +0200 |
commit | 382a54028029fe6297ebab405010d7e5229331d4 (patch) | |
tree | 3848b3fb636b46534e45bd94a91f2fb97e63e598 | |
parent | 3ee3ba3f6e770d03791fa3f0ed68e09778291437 (diff) | |
download | AntennaPod-382a54028029fe6297ebab405010d7e5229331d4.zip |
Basic local feeds support
Co-authored-by: Igor Almeida <igor.contato@gmail.com>
6 files changed, 175 insertions, 3 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 546684f14..d29a36871 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -14,16 +14,22 @@ import android.view.ViewGroup; import android.widget.EditText; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.Fragment; +import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.activity.OpmlImportActivity; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.discovery.CombinedSearcher; import de.danoeh.antennapod.discovery.FyydPodcastSearcher; import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; +import java.util.Collections; + /** * Provides actions for adding new podcast subscriptions. */ @@ -31,6 +37,7 @@ public class AddFeedFragment extends Fragment { public static final String TAG = "AddFeedFragment"; private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; + private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; private EditText combinedFeedSearchBox; private MainActivity activity; @@ -57,8 +64,7 @@ public class AddFeedFragment extends Fragment { root.findViewById(R.id.btn_add_via_url).setOnClickListener(v -> showAddViaUrlDialog()); - View butOpmlImport = root.findViewById(R.id.btn_opml_import); - butOpmlImport.setOnClickListener(v -> { + root.findViewById(R.id.btn_opml_import).setOnClickListener(v -> { try { Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); @@ -68,6 +74,15 @@ public class AddFeedFragment extends Fragment { Log.e(TAG, "No activity found. Should never happen..."); } }); + root.findViewById(R.id.btn_add_local_folder).setOnClickListener(v -> { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + }); root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); return root; } @@ -127,6 +142,25 @@ public class AddFeedFragment extends Fragment { Intent intent = new Intent(getContext(), OpmlImportActivity.class); intent.setData(uri); startActivity(intent); + } else if (requestCode == REQUEST_CODE_ADD_LOCAL_FOLDER) { + try { + getActivity().getContentResolver() + .takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + DocumentFile documentFile = DocumentFile.fromTreeUri(getContext(), uri); + if (documentFile == null) { + throw new IllegalArgumentException("Unable to retrieve document tree"); + } + Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName()); + dirFeed.setDescription(getString(R.string.local_feed_description)); + dirFeed.setItems(Collections.emptyList()); + DBTasks.forceRefreshFeed(getContext(), dirFeed, true); + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(R.string.add_local_folder_success, Snackbar.LENGTH_SHORT); + } catch (Exception e) { + Log.e(TAG, Log.getStackTraceString(e)); + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(e.getLocalizedMessage(), Snackbar.LENGTH_LONG); + } } } } diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index 9d14d209a..7430579b4 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -101,6 +101,20 @@ android:text="@string/add_podcast_by_url"/> <TextView + android:id="@+id/btn_add_local_folder" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:drawablePadding="8dp" + app:drawableStartCompat="?attr/ic_folder" + app:drawableLeftCompat="?attr/ic_folder" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="?android:attr/selectableItemBackground" + android:textColor="?android:attr/textColorPrimary" + android:clickable="true" + android:text="@string/add_local_folder"/> + + <TextView android:id="@+id/btn_search_itunes" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java index 0889e5182..9c1c55d76 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java @@ -24,6 +24,7 @@ public class Feed extends FeedFile implements ImageResource { public static final int FEEDFILETYPE_FEED = 0; public static final String TYPE_RSS2 = "rss"; public static final String TYPE_ATOM1 = "atom"; + public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:"; /* title as defined by the feed */ private String feedTitle; @@ -551,4 +552,7 @@ public class Feed extends FeedFile implements ImageResource { this.lastUpdateFailed = lastUpdateFailed; } + public boolean isLocalFeed() { + return download_url.startsWith(PREFIX_LOCAL_FOLDER); + } } 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 new file mode 100644 index 000000000..f024601d5 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java @@ -0,0 +1,111 @@ +package de.danoeh.antennapod.core.feed; + +import android.content.Context; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.util.Log; +import androidx.documentfile.provider.DocumentFile; +import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.DownloadError; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +public class LocalFeedUpdater { + + public static void updateFeed(Feed feed, Context context) { + String uriString = feed.getDownload_url().replace(Feed.PREFIX_LOCAL_FOLDER, ""); + DocumentFile documentFolder = DocumentFile.fromTreeUri(context, Uri.parse(uriString)); + if (documentFolder == null) { + reportError(feed, "Unable to retrieve document tree"); + return; + } + if (!documentFolder.exists() || !documentFolder.canRead()) { + reportError(feed, "Cannot read local directory"); + return; + } + + if (feed.getItems() == null) { + feed.setItems(new ArrayList<>()); + } + //make sure it is the latest 'version' of this feed from the db (all items etc) + feed = DBTasks.updateFeed(context, feed)[0]; + + List<DocumentFile> mediaFiles = new ArrayList<>(); + for (DocumentFile file : documentFolder.listFiles()) { + String mime = file.getType(); + if (mime != null && (mime.startsWith("audio/") || mime.startsWith("video/"))) { + mediaFiles.add(file); + } + } + + List<FeedItem> newItems = feed.getItems(); + for (DocumentFile f : mediaFiles) { + FeedItem found = feedContainsFile(feed, f.getName()); + if (found != null) { + //TODO make sure the media has not changed (type, duration) + } else { + FeedItem item = createFeedItem(feed, f, context); + newItems.add(item); + } + } + + List<String> iconLocations = Arrays.asList("folder.jpg", "Folder.jpg", "folder.png", "Folder.png"); + for (String iconLocation : iconLocations) { + DocumentFile image = documentFolder.findFile(iconLocation); + if (image != null) { + feed.setImageUrl(image.getUri().toString()); + break; + } + } + + DBTasks.updateFeed(context, feed); + } + + private static FeedItem feedContainsFile(Feed feed, String filename) { + List<FeedItem> items = feed.getItems(); + for (FeedItem i : items) { + if (i.getMedia() != null && i.getLink().equals(filename)) { + return i; + } + } + return null; + } + + private static FeedItem createFeedItem(Feed feed, DocumentFile file, Context context) { + //create item + long globalId = 0; + Date date = new Date(); + String uuid = UUID.randomUUID().toString(); + FeedItem item = new FeedItem(globalId, file.getName(), uuid, file.getName(), date, FeedItem.UNPLAYED, feed); + item.setAutoDownload(false); + + //add the media to the item + long duration = getFileDuration(file, context); + long size = file.length(); + FeedMedia media = new FeedMedia(0, item, (int) duration, 0, size, file.getType(), + file.getUri().toString(), file.getUri().toString(), true, null, 0, 0); + item.setMedia(media); + + return item; + } + + private static void reportError(Feed feed, String reasonDetailed) { + DownloadStatus status = new DownloadStatus(feed, feed.getTitle(), + DownloadError.ERROR_IO_ERROR, false, reasonDetailed, true); + DBWriter.addDownloadStatus(status); + DBWriter.setFeedLastUpdateFailed(feed.getId(), true); + } + + private static long getFileDuration(DocumentFile f, Context context) { + MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(context, f.getUri()); + String durationStr = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + return Long.parseLong(durationStr); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index 16e2825b4..323e34b6a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -16,6 +16,7 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.feed.LocalFeedUpdater; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.sync.SyncService; @@ -241,7 +242,12 @@ public final class DBTasks { feed.getPreferences().getUsername(), feed.getPreferences().getPassword()); } f.setId(feed.getId()); - DownloadRequester.getInstance().downloadFeed(context, f, loadAllPages, force, initiatedByUser); + + if (f.isLocalFeed()) { + new Thread(() -> LocalFeedUpdater.updateFeed(f, context)).start(); + } else { + DownloadRequester.getInstance().downloadFeed(context, f, loadAllPages, force, initiatedByUser); + } } /** diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 2827f666e..4f94a141a 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -718,6 +718,9 @@ <string name="discover">Discover</string> <string name="discover_more">more ยป</string> <string name="search_powered_by">Search powered by %1$s</string> + <string name="add_local_folder">Add local folder</string> + <string name="add_local_folder_success">Adding local folder succeeded</string> + <string name="local_feed_description">This virtual podcast was created by adding a folder to AntennaPod.</string> <string name="filter">Filter</string> |