diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java index 815270295..c67b50796 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java @@ -57,7 +57,7 @@ public View getView(final int position, View view, ViewGroup parent) { }); holder.binding.flDeletePodcastWrapper.setOnClickListener(view13 -> { - if(NewsFileUtils.deletePodcastFile(getContext(), podcastItem.link)) { + if(NewsFileUtils.deletePodcastFile(getContext(), podcastItem.fingerprint, podcastItem.link)) { podcastItem.offlineCached = false; podcastItem.downloadProgress = PodcastItem.DOWNLOAD_NOT_STARTED; notifyDataSetChanged(); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/SubscriptionExpandableListAdapter.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/SubscriptionExpandableListAdapter.java index d1e09fa0e..6d6f98f9b 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/SubscriptionExpandableListAdapter.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/SubscriptionExpandableListAdapter.java @@ -21,6 +21,7 @@ package de.luhmer.owncloudnewsreader.ListView; +import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_DOWNLOADED_PODCASTS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ITEMS_WITHOUT_FOLDER; @@ -77,13 +78,14 @@ public class SubscriptionExpandableListAdapter extends BaseExpandableListAdapter private boolean showOnlyUnread = false; private SparseArray starredCountFeeds; + private SparseArray downloadedPodcastsCount; private SparseArray unreadCountFolders; private SparseArray unreadCountFeeds; private final SharedPreferences mPrefs; public enum SPECIAL_FOLDERS { - ALL_UNREAD_ITEMS(-10), ALL_STARRED_ITEMS(-11), ALL_ITEMS(-12), ITEMS_WITHOUT_FOLDER(-22); + ALL_UNREAD_ITEMS(-10), ALL_STARRED_ITEMS(-11), ALL_ITEMS(-12), ALL_DOWNLOADED_PODCASTS(-13), ITEMS_WITHOUT_FOLDER(-22); private final int id; SPECIAL_FOLDERS(int id) { @@ -115,6 +117,7 @@ public SubscriptionExpandableListAdapter(Context mContext, DatabaseConnectionOrm unreadCountFeeds = new SparseArray<>(); unreadCountFolders = new SparseArray<>(); starredCountFeeds = new SparseArray<>(); + downloadedPodcastsCount = new SparseArray<>(); mCategoriesArrayList = new ArrayList<>(); mItemsArrayList = new SparseArray<>(); @@ -159,6 +162,8 @@ public View getChildView(int groupPosition, int childPosition, String unreadCount; if(item.idFolder == ALL_STARRED_ITEMS.getValue()) { unreadCount = starredCountFeeds.get((int) item.id_database); + } else if(item.idFolder == ALL_DOWNLOADED_PODCASTS.getValue()) { + unreadCount = downloadedPodcastsCount.get((int) item.id_database); } else { unreadCount = unreadCountFeeds.get((int) item.id_database); } @@ -302,9 +307,13 @@ public View getGroupView(final int groupPosition, final boolean isExpanded, View } else { if(group.id_database == ALL_STARRED_ITEMS.getValue()) { viewHolder.binding.imgViewExpandableIndicator.setVisibility(View.GONE); - viewHolder.binding.imgViewFavicon.setVisibility(View.VISIBLE); + viewHolder.binding.imgViewFavicon.setVisibility(View.VISIBLE); rotation = 0; viewHolder.binding.imgViewFavicon.setImageResource(R.drawable.ic_star_border_24dp_theme_aware); + } else if(group.id_database == ALL_DOWNLOADED_PODCASTS.getValue()) { + viewHolder.binding.imgViewExpandableIndicator.setVisibility(View.GONE); + viewHolder.binding.imgViewFavicon.setVisibility(View.VISIBLE); + viewHolder.binding.imgViewFavicon.setImageResource(R.drawable.ic_baseline_play_arrow_24_theme_aware); } else if (getChildrenCount( groupPosition ) == 0 ) { viewHolder.binding.imgViewExpandableIndicator.setVisibility(View.GONE); viewHolder.binding.imgViewFavicon.setVisibility(View.INVISIBLE); @@ -437,6 +446,7 @@ public void ReloadAdapterAsync() { private class NotifyDataSetChangedAsyncTask extends AsyncTask { SparseArray starredCountFeedsTemp; + SparseArray downloadedPodcastsCountFeedsTemp; SparseArray unreadCountFoldersTemp; SparseArray unreadCountFeedsTemp; SparseArray urlsToFavIconsTemp; @@ -452,6 +462,7 @@ protected Void doInBackground(Void... voids) { unreadCountFeedsTemp = temp[1]; // dbConn.getUnreadItemCountForFeed(); starredCountFeedsTemp = dbConn.getStarredItemCount(); + downloadedPodcastsCountFeedsTemp = dbConn.getDownloadedPodcastsCount(mContext); urlsToFavIconsTemp = dbConn.getUrlsToFavIcons(); stopwatch.stop(); @@ -492,7 +503,7 @@ protected void onPostExecute(Void aVoid) { } } - notifyCountDataSetChanged(unreadCountFoldersTemp, unreadCountFeedsTemp, starredCountFeedsTemp); + notifyCountDataSetChanged(unreadCountFoldersTemp, unreadCountFeedsTemp, starredCountFeedsTemp, downloadedPodcastsCountFeedsTemp); super.onPostExecute(aVoid); } } @@ -524,12 +535,79 @@ protected void onPostExecute(Tuple, SparseArray, SparseArray>> ReloadAdapter() + { + showOnlyUnread = mPrefs.getBoolean(SettingsActivity.CB_SHOWONLYUNREAD_STRING, false); + + ArrayList mCategoriesArrayListAsync = new ArrayList<>(); + mCategoriesArrayListAsync.add(new FolderSubscribtionItem(mContext.getString(R.string.allUnreadFeeds), null, ALL_UNREAD_ITEMS.getValue())); + mCategoriesArrayListAsync.add(new FolderSubscribtionItem(mContext.getString(R.string.starredFeeds), null, ALL_STARRED_ITEMS.getValue())); + mCategoriesArrayListAsync.add(new FolderSubscribtionItem(mContext.getString(R.string.downloadedPodcasts), null, ALL_DOWNLOADED_PODCASTS.getValue())); + + + StopWatch sw = new StopWatch(); + sw.start(); + + List folderList; + //if(showOnlyUnread) { + // folderList = dbConn.getListOfFoldersWithUnreadItems(); + //} else { + folderList = dbConn.getListOfFolders(); + //} + + sw.stop(); + Log.v(TAG, "Time needed (fetch folder list): " + sw.toString()); + + + for(Folder folder : folderList) { + mCategoriesArrayListAsync.add(new FolderSubscribtionItem(folder.getLabel(), null, folder.getId())); + } + + for(Feed feed : dbConn.getListOfFeedsWithoutFolders(showOnlyUnread)) { + mCategoriesArrayListAsync.add(new ConcreteFeedItem(feed.getFeedTitle(), (long) ITEMS_WITHOUT_FOLDER.getValue(), feed.getId(), feed.getFaviconUrl(), feed.getId())); + } + + SparseArray> mItemsArrayListAsync = new SparseArray<>(); + + for(int groupPosition = 0; groupPosition < mCategoriesArrayListAsync.size(); groupPosition++) { + //int parent_id = (int)getGroupId(groupPosition); + int parent_id = (int) mCategoriesArrayListAsync.get(groupPosition).id_database; + mItemsArrayListAsync.append(parent_id, new ArrayList<>()); + + List feedItemList = null; + + if(parent_id == ALL_UNREAD_ITEMS.getValue()) { + feedItemList = dbConn.getAllFeedsWithUnreadRssItems(); + } else if(parent_id == ALL_STARRED_ITEMS.getValue()) { + feedItemList = dbConn.getAllFeedsWithStarredRssItems(); + } else if (parent_id == ALL_DOWNLOADED_PODCASTS.getValue()) { + feedItemList = dbConn.getAllFeedsWithDownloadedPodcasts(mContext); + } else { + for(Folder folder : folderList) {//Find the current selected folder + if (folder.getId() == parent_id) {//Current item + feedItemList = dbConn.getAllFeedsWithUnreadRssItemsForFolder(folder.getId()); + break; + } + } + } + + if(feedItemList != null) { + for (Feed feed : feedItemList) { + ConcreteFeedItem newItem = new ConcreteFeedItem(feed.getFeedTitle(), (long) parent_id, feed.getId(), feed.getFaviconUrl(), feed.getId()); + mItemsArrayListAsync.get(parent_id).add(newItem); + } + } + } + + return new Tuple<>(mCategoriesArrayListAsync, mItemsArrayListAsync); + } @SuppressLint("NewApi") // wrongly reports setSelectionFromTop is only available in lollipop - public void notifyCountDataSetChanged(SparseArray unreadCountFolders, SparseArray unreadCountFeeds, SparseArray starredCountFeeds) { + public void notifyCountDataSetChanged(SparseArray unreadCountFolders, SparseArray unreadCountFeeds, SparseArray starredCountFeeds, SparseArray downloadedPodcastsCount) { this.unreadCountFolders = unreadCountFolders; this.unreadCountFeeds = unreadCountFeeds; this.starredCountFeeds = starredCountFeeds; + this.downloadedPodcastsCount = downloadedPodcastsCount; BlockingExpandableListView bView = (BlockingExpandableListView) listView; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java index 310b3023f..7d8ce2a1f 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java @@ -49,6 +49,7 @@ import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; +import java.io.File; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Set; @@ -63,6 +64,7 @@ import de.luhmer.owncloudnewsreader.helper.ThemeUtils; import de.luhmer.owncloudnewsreader.model.PodcastItem; import de.luhmer.owncloudnewsreader.model.TTSItem; +import de.luhmer.owncloudnewsreader.services.PodcastDownloadService; import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout; import de.luhmer.owncloudnewsreader.widget.WidgetProvider; @@ -90,6 +92,7 @@ public class NewsDetailActivity extends PodcastFragmentActivity { private int currentPosition; private MenuItem menuItem_PlayPodcast; + private MenuItem menuItem_RemovePodcast; private MenuItem menuItem_Starred; private MenuItem menuItem_Read; private MenuItem menuItem_Incognito; @@ -401,6 +404,11 @@ public void updateActionBarIcons() { menuItem_PlayPodcast.setVisible(podcastAvailable); } + if(menuItem_RemovePodcast != null) { + File file = new File(PodcastDownloadService.getUrlToPodcastFile(this, podcastItem.fingerprint, podcastItem.link, false)); + menuItem_RemovePodcast.setVisible(file.exists()); + } + if (menuItem_Starred != null) { int res = isStarred ? R.drawable.ic_star_24_theme_aware : R.drawable.ic_star_border_24dp_theme_aware; menuItem_Starred.setIcon(res); @@ -442,6 +450,7 @@ public boolean onCreateOptionsMenu(Menu menu) { menuItem_Starred = menu.findItem(R.id.action_starred); menuItem_Read = menu.findItem(R.id.action_read); menuItem_PlayPodcast = menu.findItem(R.id.action_playPodcast); + menuItem_RemovePodcast = menu.findItem(R.id.action_removePodcast); menuItem_Incognito = menu.findItem(R.id.action_incognito_mode); if (mShowFastActions) { @@ -493,6 +502,12 @@ public boolean onOptionsItemSelected(MenuItem item) { this.openInBrowser(currentPosition); } else if (itemId == R.id.action_playPodcast) { openPodcast(rssItem); + } else if (itemId == R.id.action_removePodcast) { + removePodcastMedia(rssItem, (result) -> { + if (menuItem_RemovePodcast != null) { + menuItem_RemovePodcast.setVisible(!result); + } + }); } else if (itemId == R.id.action_tts) { this.startTTS(currentPosition); } else if (itemId == R.id.action_ShareItem) { diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java index 8838bbeb1..31aea69cf 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java @@ -22,6 +22,7 @@ package de.luhmer.owncloudnewsreader; import static java.util.Objects.requireNonNull; +import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_DOWNLOADED_PODCASTS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS; import static de.luhmer.owncloudnewsreader.SettingsActivity.SP_SWIPE_LEFT_ACTION; @@ -67,6 +68,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.List; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -515,9 +517,9 @@ protected List doInBackground(Void... voids) { } sqlSelectStatement = dbConn.getAllItemsIdsForFeedSQL(idFeed, onlyUnreadItems, onlyStarredItems, sortDirection); } else if (idFolder != null) { - if (idFolder == ALL_STARRED_ITEMS.getValue()) + if (idFolder == ALL_STARRED_ITEMS.getValue() || idFolder == ALL_DOWNLOADED_PODCASTS.getValue()) onlyUnreadItems = false; - sqlSelectStatement = dbConn.getAllItemsIdsForFolderSQL(idFolder, onlyUnreadItems, sortDirection); + sqlSelectStatement = dbConn.getAllItemsIdsForFolderSQL(idFolder, onlyUnreadItems, sortDirection, mActivity); } if (sqlSelectStatement != null) { int index = sqlSelectStatement.indexOf("ORDER BY"); @@ -533,6 +535,13 @@ protected List doInBackground(Void... voids) { List items = dbConn.getCurrentRssItemView(0); + if (idFolder == ALL_DOWNLOADED_PODCASTS.getValue()) { + items = items.stream().filter((rss) -> { + var podcast = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(mActivity, rss); + return podcast.offlineCached; + }).toList(); + } + sw.stop(); Log.v(TAG, "Time needed (init loading): " + sw); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java index 945b45fd7..eebf1b35d 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java @@ -746,6 +746,8 @@ private void updateDetailFragmentTitle() { title = getString(R.string.allUnreadFeeds); } else if (idFolder == -11) { title = getString(R.string.starredFeeds); + } else if (idFolder == -13) { + title = getString(R.string.downloadedPodcasts); } } else { Feed feed = dbConn.getFeedById(id); @@ -834,6 +836,8 @@ private NewsReaderDetailFragment updateDetailFragment(long id, Boolean folder, L title = getString(R.string.allUnreadFeeds); } else if (idFolder == -11) { title = getString(R.string.starredFeeds); + } else if (idFolder == -13) { + title = getString(R.string.downloadedPodcasts); } } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java index 7fe78ec57..514e2b38b 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java @@ -182,7 +182,7 @@ public void onEvent(PodcastDownloadService.DownloadProgressUpdate downloadProgre if (downloadProgress.podcast.downloadProgress == 100) { pItem.downloadProgress = PodcastItem.DOWNLOAD_COMPLETED; - File file = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), pItem.link, false)); + File file = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), pItem.fingerprint, pItem.link, false)); pItem.offlineCached = file.exists(); } else pItem.downloadProgress = downloadProgress.podcast.downloadProgress; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragmentActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragmentActivity.java index 76053600c..9689dc89e 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragmentActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragmentActivity.java @@ -28,6 +28,7 @@ import org.greenrobot.eventbus.Subscribe; import java.io.File; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -294,7 +295,7 @@ public void openMediaItem(final MediaItem mediaItem) { public void openPodcast(final RssItem rssItem) { final PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(this, rssItem); - File file = new File(PodcastDownloadService.getUrlToPodcastFile(this, podcastItem.link, false)); + File file = new File(PodcastDownloadService.getUrlToPodcastFile(this, podcastItem.fingerprint, podcastItem.link, false)); if(file.exists()) { podcastItem.link = file.getAbsolutePath(); openMediaItem(podcastItem); @@ -319,6 +320,35 @@ public void openPodcast(final RssItem rssItem) { } + public void removePodcastMedia(final RssItem rssItem, final Consumer callback) { + final PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(this, rssItem); + File file = new File(PodcastDownloadService.getUrlToPodcastFile(this, podcastItem.fingerprint, podcastItem.link, false)); + + if (!file.exists()) { + callback.accept(true); + } + + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this) + .setNegativeButton("Remove", (dialogInterface, i) -> { + boolean success = file.delete() && file.getParentFile().delete(); // remove audio file and parent folder + if (!success) { + Toast.makeText(PodcastFragmentActivity.this, "Failed to remove media for \"" + podcastItem.title + "\"", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(PodcastFragmentActivity.this, "Media for \"" + podcastItem.title + "\" has been removed", Toast.LENGTH_SHORT).show(); + } + + callback.accept(success); + }) + .setNeutralButton("Cancel", (dialogInterface, i) -> { + callback.accept(false); + }) + .setTitle("Are you sure?") + .setMessage("Do you want to remove downloaded media for \"" + podcastItem.title + "?\""); + + alertDialog.show(); + } + + @Override public void pausePodcast() { MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getTransportControls().pause(); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/adapter/RssItemViewHolder.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/adapter/RssItemViewHolder.java index 1484e7217..0d4986e4c 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/adapter/RssItemViewHolder.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/adapter/RssItemViewHolder.java @@ -352,7 +352,7 @@ public void setPlaying(boolean playing) { public void setDownloadPodcastProgressbar() { float progress; - if (PodcastDownloadService.PodcastAlreadyCached(itemView.getContext(), rssItem.getEnclosureLink())) { + if (PodcastDownloadService.PodcastAlreadyCached(itemView.getContext(), rssItem.getFingerprint(), rssItem.getEnclosureLink())) { progress = 100; } else { progress = downloadProgressList.get(rssItem.getId().intValue(), 0); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnectionOrm.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnectionOrm.java index 8ae14d2e5..662969432 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnectionOrm.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnectionOrm.java @@ -1,6 +1,7 @@ package de.luhmer.owncloudnewsreader.database; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS; +import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_DOWNLOADED_PODCASTS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS; @@ -38,6 +39,7 @@ import de.luhmer.owncloudnewsreader.database.model.RssItem; import de.luhmer.owncloudnewsreader.database.model.RssItemDao; import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper; +import de.luhmer.owncloudnewsreader.helper.NewsFileUtils; import de.luhmer.owncloudnewsreader.helper.StopWatch; import de.luhmer.owncloudnewsreader.model.PodcastFeedItem; import de.luhmer.owncloudnewsreader.model.PodcastItem; @@ -68,6 +70,8 @@ public enum SORT_DIRECTION { asc, desc } private final static int PageSize = 25; + private Context context; + protected @Inject @Named("databaseFileName") String databasePath; public void resetDatabase() { @@ -78,6 +82,7 @@ public void resetDatabase() { } public DatabaseConnectionOrm(Context context) { + this.context = context; if(databasePath == null) { ((NewsReaderApplication) context.getApplicationContext()).getAppComponent().injectDatabaseConnection(this); } @@ -184,6 +189,13 @@ public List getAllFeedsWithStarredRssItems() { new WhereCondition.StringCondition(FeedDao.Properties.Id.columnName + " IN " + "(SELECT " + RssItemDao.Properties.FeedId.columnName + " FROM " + RssItemDao.TABLENAME + " WHERE " + RssItemDao.Properties.Starred_temp.columnName + " = 1)")).list(); } + public List getAllFeedsWithDownloadedPodcasts(Context context) { + var ids = NewsFileUtils.getDownloadedPodcastsFingerprints(context); + var files = Arrays.stream(ids).map((f) -> "\"" + f + "\"").collect(Collectors.toList()); + return daoSession.getFeedDao().queryBuilder().orderAsc(FeedDao.Properties.FeedTitle).where( + new WhereCondition.StringCondition(FeedDao.Properties.Id.columnName + " IN " + "(SELECT " + RssItemDao.Properties.FeedId.columnName + " FROM " + RssItemDao.TABLENAME + " WHERE " + RssItemDao.Properties.Fingerprint.columnName + " in (" + String.join(",", files) + "))")).list(); + } + public List getListOfFeedsWithAudioPodcasts() { WhereCondition whereCondition = new WhereCondition.StringCondition(FeedDao.Properties.Id.columnName + " IN " + "(SELECT " + RssItemDao.Properties.FeedId.columnName + " FROM " + RssItemDao.TABLENAME + " WHERE " + RssItemDao.Properties.EnclosureMime.columnName + " IN(\"" + join(ALLOWED_PODCASTS_TYPES, "\",\"") + "\"))"); List feedsWithPodcast = daoSession.getFeedDao().queryBuilder().orderAsc(FeedDao.Properties.FeedTitle).where(whereCondition).list(); @@ -517,6 +529,7 @@ public static PodcastItem ParsePodcastItemFromRssItem(Context context, RssItem r podcastItem.link = rssItem.getEnclosureLink(); podcastItem.mimeType = rssItem.getEnclosureMime(); podcastItem.favIcon = feed.getFaviconUrl(); + podcastItem.fingerprint = rssItem.getFingerprint(); if("image/jpeg".equals(podcastItem.mimeType)) { // We don't want to accidentally think that enclosed images are podcasts @@ -526,7 +539,7 @@ public static PodcastItem ParsePodcastItemFromRssItem(Context context, RssItem r podcastItem.isVideoPodcast = Arrays.asList(DatabaseConnectionOrm.VIDEO_FORMATS).contains(podcastItem.mimeType); - File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastItem.link, false)); + File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastItem.fingerprint, podcastItem.link, false)); podcastItem.offlineCached = file.exists(); return podcastItem; @@ -587,11 +600,11 @@ public Long getLowestItemIdByFolder(Long id_folder) { } - public String getAllItemsIdsForFolderSQL(long ID_FOLDER, boolean onlyUnread, SORT_DIRECTION sortDirection) { + public String getAllItemsIdsForFolderSQL(long ID_FOLDER, boolean onlyUnread, SORT_DIRECTION sortDirection, Context context) { String buildSQL = "SELECT " + RssItemDao.Properties.Id.columnName + " FROM " + RssItemDao.TABLENAME; - if(!(ID_FOLDER == ALL_UNREAD_ITEMS.getValue() || ID_FOLDER == ALL_STARRED_ITEMS.getValue()) || ID_FOLDER == ALL_ITEMS.getValue())//Wenn nicht Alle Artikel ausgewaehlt wurde (-10) oder (-11) fuer Starred Feeds + if(!(ID_FOLDER == ALL_UNREAD_ITEMS.getValue() || ID_FOLDER == ALL_STARRED_ITEMS.getValue() || ID_FOLDER == ALL_DOWNLOADED_PODCASTS.getValue()) || ID_FOLDER == ALL_ITEMS.getValue())//Wenn nicht Alle Artikel ausgewaehlt wurde (-10) oder (-11) fuer Starred Feeds { buildSQL += " WHERE " + RssItemDao.Properties.FeedId.columnName + " IN " + "(SELECT sc." + FeedDao.Properties.Id.columnName + @@ -606,6 +619,11 @@ else if(ID_FOLDER == ALL_UNREAD_ITEMS.getValue()) buildSQL += " WHERE " + RssItemDao.Properties.Read_temp.columnName + " != 1"; else if(ID_FOLDER == ALL_STARRED_ITEMS.getValue()) buildSQL += " WHERE " + RssItemDao.Properties.Starred_temp.columnName + " = 1"; + else if (ID_FOLDER == ALL_DOWNLOADED_PODCASTS.getValue()) { + var ids = NewsFileUtils.getDownloadedPodcastsFingerprints(context); + var files = Arrays.stream(ids).map((f) -> "\"" + f + "\"").collect(Collectors.toList()); + buildSQL += " WHERE " + RssItemDao.Properties.Fingerprint.columnName + " in (" + String.join(",", files) + ")"; + } buildSQL += " ORDER BY " + RssItemDao.Properties.PubDate.columnName + " " + sortDirection.toString(); @@ -732,6 +750,18 @@ public SparseArray getStarredItemCount() { return getStringSparseArrayFromSQL(buildSQL, 0, 1); } + public SparseArray getDownloadedPodcastsCount(Context context) { + var ids = NewsFileUtils.getDownloadedPodcastsFingerprints(context); + var files = Arrays.stream(ids).map((f) -> "\"" + f + "\"").collect(Collectors.toList()); + + String buildSQL = "SELECT " + RssItemDao.Properties.FeedId.columnName + ", COUNT(1)" + // rowid as _id, + " FROM " + RssItemDao.TABLENAME + + " WHERE " + RssItemDao.Properties.Fingerprint.columnName + " in (" + String.join(",", files) + ")" + + " GROUP BY " + RssItemDao.Properties.FeedId.columnName; + + return getStringSparseArrayFromSQL(buildSQL, 0, 1); + } + public void clearDatabaseOverSize() { //If i have 9023 rows in the database, when i run that query it should delete 8023 rows and leave me with 1000 @@ -755,9 +785,13 @@ public void clearDatabaseOverSize() if(overSize > read) overSize = read; + var downloadedPodcastsFingerprints = NewsFileUtils.getDownloadedPodcastsFingerprints(context); + var files = Arrays.stream(downloadedPodcastsFingerprints).map((f) -> "\"" + f + "\"").collect(Collectors.toList()); + String sqlStatement = "DELETE FROM " + RssItemDao.TABLENAME + " WHERE " + RssItemDao.Properties.Id.columnName + " IN (SELECT " + RssItemDao.Properties.Id.columnName + " FROM " + RssItemDao.TABLENAME + " WHERE " + RssItemDao.Properties.Read_temp.columnName + " = 1 AND " + RssItemDao.Properties.Starred_temp.columnName + " != 1 " + + " AND " + RssItemDao.Properties.Fingerprint.columnName + " NOT IN (" + String.join(",", files) + ")" + // This means that the article has downloaded podcast media " AND " + RssItemDao.Properties.Id.columnName + " NOT IN (SELECT " + CurrentRssItemViewDao.Properties.RssItemId.columnName + " FROM " + CurrentRssItemViewDao.TABLENAME + ")" + " ORDER BY " + RssItemDao.Properties.Id.columnName + " asc LIMIT " + overSize + ")"; daoSession.getDatabase().execSQL(sqlStatement); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/NewsFileUtils.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/NewsFileUtils.java index 0c13954e4..a8006e307 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/NewsFileUtils.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/NewsFileUtils.java @@ -31,6 +31,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import de.luhmer.owncloudnewsreader.services.DownloadWebPageService; import de.luhmer.owncloudnewsreader.services.PodcastDownloadService; @@ -73,9 +77,9 @@ public static void copyFile(FileInputStream fromFile, FileOutputStream toFile) t } - public static boolean deletePodcastFile(Context context, String url) { + public static boolean deletePodcastFile(Context context, String fingerprint, String url) { try { - File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, url, false)); + File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, fingerprint, url, false)); if(file.exists()) return file.delete(); } catch (Exception ex) { @@ -236,4 +240,14 @@ public static void forceDelete(final File file) throws IOException { } } + public static String[] getDownloadedPodcastsFingerprints(Context context) { + File folder = new File(NewsFileUtils.getPathPodcasts(context)); + File[] files = folder.listFiles(); + if (files == null) { + return new String[0]; + } + List ids = Arrays.stream(files).map(File::getName).collect(Collectors.toList()); + return ids.toArray(new String[0]); + } + } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java index 03dd959fc..8b4c77773 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java @@ -6,7 +6,7 @@ public PodcastItem() { } - public PodcastItem(long itemId, String author, String title, String link, String mimeType, boolean offlineCached, String favIcon, boolean isVideoPodcast) { + public PodcastItem(long itemId, String author, String title, String link, String mimeType, boolean offlineCached, String favIcon, boolean isVideoPodcast, String fingerprint) { this.itemId = itemId; this.author = author; this.title = title; @@ -15,9 +15,11 @@ public PodcastItem(long itemId, String author, String title, String link, String this.offlineCached = offlineCached; this.favIcon = favIcon; this.isVideoPodcast = isVideoPodcast; + this.fingerprint = fingerprint; } public String mimeType; + public String fingerprint; public boolean offlineCached; public boolean isVideoPodcast; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java index abafc9839..31781400e 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java @@ -93,7 +93,7 @@ private void handleActionDownload(PodcastItem podcast) { request.allowScanningByMediaScanner(); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - String path = "file://" + getUrlToPodcastFile(this, podcast.link, true); + String path = "file://" + getUrlToPodcastFile(this, podcast.fingerprint, podcast.link, true); request.setDestinationUri(Uri.parse(path)); //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "bla.txt"); @@ -103,33 +103,16 @@ private void handleActionDownload(PodcastItem podcast) { } - public static String getUrlToPodcastFile(Context context, String WEB_URL_TO_FILE, boolean createDir) { + public static String getUrlToPodcastFile(Context context, String fingerprint, String WEB_URL_TO_FILE, boolean createDir) { File file = new File(WEB_URL_TO_FILE); - String path = NewsFileUtils.getPathPodcasts(context) + "/" + getHashOfString(WEB_URL_TO_FILE) + "/"; + String path = NewsFileUtils.getPathPodcasts(context) + "/" + fingerprint + "/"; if(createDir) new File(path).mkdirs(); return path + file.getName(); } - public static String getHashOfString(String WEB_URL_TO_FILE) - { - try { - MessageDigest m = MessageDigest.getInstance("MD5"); - m.reset(); - m.update(WEB_URL_TO_FILE.trim().getBytes()); - byte[] digest = m.digest(); - BigInteger bigInt = new BigInteger(1,digest); - - return bigInt.toString(16); - } catch (Exception e) { - e.printStackTrace(); - } - return WEB_URL_TO_FILE; - } - - private void downloadPodcast(PodcastItem podcast, Context context) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); @@ -141,7 +124,7 @@ private void downloadPodcast(PodcastItem podcast, Context context) { try { String urlTemp = podcast.link; - String path = getUrlToPodcastFile(this, urlTemp, true); + String path = getUrlToPodcastFile(this, podcast.fingerprint, urlTemp, true); Log.v(TAG, "Storing podcast to: " + path); URL url = new URL(urlTemp); @@ -242,8 +225,8 @@ public DownloadProgressUpdate(PodcastItem podcast) { public PodcastItem podcast; } - public static boolean PodcastAlreadyCached(Context context, String podcastUrl) { - File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastUrl, false)); + public static boolean PodcastAlreadyCached(Context context, String podcastFingerprint, String podcastUrl) { + File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastFingerprint, podcastUrl, false)); return file.exists(); } } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java index 3f3dd793e..0b5c7a18b 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java @@ -339,7 +339,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { private void updateMetadata(MediaItem mediaItem) { MediaItem mi = mediaItem; if(mi == null) { - mi = new PodcastItem(-1, "", "", "", "", false, null, false); + mi = new PodcastItem(-1, "", "", "", "", false, null, false, ""); } int totalDuration = 0; diff --git a/News-Android-App/src/main/res/drawable/ic_action_delete_24_theme_aware.xml b/News-Android-App/src/main/res/drawable/ic_action_delete_24_theme_aware.xml new file mode 100644 index 000000000..706763497 --- /dev/null +++ b/News-Android-App/src/main/res/drawable/ic_action_delete_24_theme_aware.xml @@ -0,0 +1,6 @@ + + + diff --git a/News-Android-App/src/main/res/menu/news_detail.xml b/News-Android-App/src/main/res/menu/news_detail.xml index b28f0f688..3150e84e9 100644 --- a/News-Android-App/src/main/res/menu/news_detail.xml +++ b/News-Android-App/src/main/res/menu/news_detail.xml @@ -28,6 +28,12 @@ android:icon="@drawable/ic_action_open_in_browser_24_theme_aware" android:title="@string/action_openInBrowser"/> + + Reload All unread items Starred items + Downloaded podcasts Add new feed Refresh @@ -35,6 +36,7 @@ Starred Read Play Podcast + Remove Podcast Media Open in Web browser Share Server Settings