From e807331b1f402cacd6e54c69832bdc8abc471e18 Mon Sep 17 00:00:00 2001 From: Zsombor Gegesy Date: Sun, 18 Aug 2024 13:58:17 +0200 Subject: [PATCH] Update channel status, when YT reports as "terminated"/"deleted" Display the status in the subs drawer, and do not fetch videos for channels with not-ok state --- .../YouTube/NewPipeChannelVideos.java | 5 +- .../YouTube/POJOs/ChannelView.java | 9 ++- .../YouTube/POJOs/PersistentChannel.java | 23 +++++-- .../businessobjects/YouTube/YouTubeTasks.java | 18 ++++- .../newpipe/GetPlaylistsForChannel.java | 2 +- .../YouTube/newpipe/NewPipeService.java | 14 ++-- .../businessobjects/db/DatabaseTasks.java | 11 ++- .../businessobjects/db/LocalChannelTable.java | 10 +++ .../businessobjects/db/SubscriptionsDb.java | 68 ++++++++++--------- .../db/SubscriptionsTable.java | 6 ++ .../skytube/businessobjects/model/Status.java | 34 ++++++++++ .../SubscriptionsBackupsManager.java | 4 -- .../businessobjects/adapters/SubsAdapter.java | 20 ++++-- app/src/main/res/values/strings_subs.xml | 3 + 14 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/free/rm/skytube/businessobjects/model/Status.java diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/NewPipeChannelVideos.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/NewPipeChannelVideos.java index e1311b727..19ba89aa7 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/NewPipeChannelVideos.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/NewPipeChannelVideos.java @@ -18,6 +18,7 @@ import java.util.Objects; +import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeException; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService; import free.rm.skytube.businessobjects.YouTube.newpipe.VideoPager; @@ -27,11 +28,11 @@ */ public class NewPipeChannelVideos extends NewPipeVideos { - private String channelId; + private ChannelId channelId; // Important, this is called from the channel tab public void setQuery(String query) { - this.channelId = Objects.requireNonNull(query, "query missing"); + this.channelId = new ChannelId(query); } @Override diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/ChannelView.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/ChannelView.java index b13133e2b..4c6d51c30 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/ChannelView.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/ChannelView.java @@ -17,18 +17,21 @@ package free.rm.skytube.businessobjects.YouTube.POJOs; import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; +import free.rm.skytube.businessobjects.model.Status; public class ChannelView { private final ChannelId id; private final String title; private final String thumbnailUrl; private boolean newVideosSinceLastVisit; + private final Status status; - public ChannelView(ChannelId id, String title, String thumbnailUrl, boolean newVideosSinceLastVisit) { + public ChannelView(ChannelId id, String title, String thumbnailUrl, boolean newVideosSinceLastVisit, Status status) { this.id = id; this.title = title; this.thumbnailUrl = thumbnailUrl; this.newVideosSinceLastVisit = newVideosSinceLastVisit; + this.status = status; } public ChannelId getId() { @@ -47,6 +50,10 @@ public boolean isNewVideosSinceLastVisit() { return newVideosSinceLastVisit; } + public Status status() { + return status; + } + public void setNewVideosSinceLastVisit(boolean newVideosSinceLastVisit) { this.newVideosSinceLastVisit = newVideosSinceLastVisit; } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/PersistentChannel.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/PersistentChannel.java index 8b65aa9d0..821e6c766 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/PersistentChannel.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/PersistentChannel.java @@ -16,21 +16,36 @@ */ package free.rm.skytube.businessobjects.YouTube.POJOs; +import java.util.Objects; + +import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; +import free.rm.skytube.businessobjects.model.Status; + public final class PersistentChannel { final YouTubeChannel channel; final long channelPk; final Long subscriptionPk; + final Status status; - public PersistentChannel(YouTubeChannel channel, long channelPk, Long subscriptionPk) { - this.channel = channel; + public PersistentChannel(YouTubeChannel channel, long channelPk, Long subscriptionPk, Status status) { + this.channel = Objects.requireNonNull(channel, "channel"); this.channelPk = channelPk; this.subscriptionPk = subscriptionPk; + this.status = status; } public YouTubeChannel channel() { return channel; } + public Status status() { + return status; + } + + public ChannelId getChannelId() { + return channel.getChannelId(); + } + public long channelPk() { return channelPk; } @@ -45,10 +60,10 @@ public boolean isSubscribed() { public PersistentChannel with(YouTubeChannel newInstance) { newInstance.setUserSubscribed(isSubscribed()); - return new PersistentChannel(newInstance, channelPk, subscriptionPk); + return new PersistentChannel(newInstance, channelPk, subscriptionPk, status); } public PersistentChannel withSubscriptionPk(Long newSubscriptionPk) { - return new PersistentChannel(channel, channelPk, newSubscriptionPk); + return new PersistentChannel(channel, channelPk, newSubscriptionPk, status); } } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/YouTubeTasks.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/YouTubeTasks.java index fb1f4db49..da4da40b7 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/YouTubeTasks.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/YouTubeTasks.java @@ -10,6 +10,8 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException; +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; @@ -42,8 +44,10 @@ import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeException; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService; import free.rm.skytube.businessobjects.YouTube.newpipe.PlaylistPager; +import free.rm.skytube.businessobjects.db.LocalChannelTable; import free.rm.skytube.businessobjects.db.SubscriptionsDb; import free.rm.skytube.businessobjects.interfaces.GetDesiredStreamListener; +import free.rm.skytube.businessobjects.model.Status; import free.rm.skytube.gui.businessobjects.adapters.PlaylistsGridAdapter; import free.rm.skytube.gui.businessobjects.adapters.VideoGridAdapter; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -203,11 +207,23 @@ private static List fetchVideos(@NonNull SubscriptionsDb subscript }); return videos; } catch (NewPipeException e) { - Log.e(TAG, "Error during fetching channel page for " + channelId + ",msg:" + e.getMessage(), e); + handleNewPipeException(channelId, e); return Collections.emptyList(); } } + private static void handleNewPipeException(@NonNull ChannelId channelId, @NonNull NewPipeException e) { + if (e.getCause() instanceof AccountTerminatedException) { + Log.e(TAG, "Account terminated for "+ channelId +" error: "+e.getMessage(), e); + SubscriptionsDb.getSubscriptionsDb().setChannelState(channelId, Status.ACCOUNT_TERMINATED); + } else if (e.getCause() instanceof ContentNotAvailableException) { + Log.e(TAG, "Channel doesn't exists "+ channelId +" error: "+e.getMessage(), e); + SubscriptionsDb.getSubscriptionsDb().setChannelState(channelId, Status.NOT_EXISTS); + } else { + Log.e(TAG, "Error during fetching channel page for " + channelId + ",msg:" + e.getMessage(), e); + } + } + /** * Task to asynchronously get videos for a specific channel. */ diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/GetPlaylistsForChannel.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/GetPlaylistsForChannel.java index 482739b92..98c1afc2b 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/GetPlaylistsForChannel.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/GetPlaylistsForChannel.java @@ -104,7 +104,7 @@ public List getNextPlaylists() throws IOException, ExtractionEx private synchronized Paging getPaging() throws NewPipeException, ParsingException { SkyTubeApp.nonUiThread(); if (paging == null) { - NewPipeService.ChannelWithExtractor cwe = NewPipeService.get().getChannelWithExtractor(channel.getId()); + NewPipeService.ChannelWithExtractor cwe = NewPipeService.get().getChannelWithExtractor(channel.getChannelId()); paging = new Paging(cwe.channel, cwe.findPlaylistTab()); } return paging; diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/NewPipeService.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/NewPipeService.java index 69c137983..62d52cfe4 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/NewPipeService.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/NewPipeService.java @@ -209,7 +209,7 @@ public StreamInfo getStreamInfoByVideoId(String videoId) throws ExtractionExcept * @throws ExtractionException * @throws IOException */ - private List getChannelVideos(String channelId) throws NewPipeException { + private List getChannelVideos(ChannelId channelId) throws NewPipeException { SkyTubeApp.nonUiThread(); VideoPagerWithChannel pager = getChannelPager(channelId); List result = pager.getNextPageAsVideosAndUpdateChannel(null).channel().getYouTubeVideos(); @@ -255,7 +255,7 @@ public List getVideosFromFeedOrFromChannel(ChannelId channelId) th } catch (IOException | ExtractionException | RuntimeException | NewPipeException e) { Logger.e(this, "Unable to get videos from a feed " + channelId + " : "+ e.getMessage(), e); } - return getChannelVideos(channelId.getRawId()); + return getChannelVideos(channelId); } public VideoPager getTrending() throws NewPipeException { @@ -269,7 +269,7 @@ public VideoPager getTrending() throws NewPipeException { } } - public VideoPagerWithChannel getChannelPager(String channelId) throws NewPipeException { + public VideoPagerWithChannel getChannelPager(ChannelId channelId) throws NewPipeException { try { Logger.e(this, "fetching channel info: "+ channelId); ChannelWithExtractor channelExtractor = getChannelWithExtractor(channelId); @@ -279,7 +279,7 @@ public VideoPagerWithChannel getChannelPager(String channelId) throws NewPipeExc } } - public ChannelWithExtractor getChannelWithExtractor(String channelId) throws NewPipeException { + public ChannelWithExtractor getChannelWithExtractor(ChannelId channelId) throws NewPipeException { try { ChannelExtractor channelExtractor = getChannelExtractor(channelId); @@ -320,7 +320,7 @@ public CommentPager getCommentPager(String videoId) throws NewPipeException { */ public PersistentChannel getChannelDetails(ChannelId channelId, PersistentChannel persistentChannel) throws NewPipeException { Logger.i(this, "Fetching channel details for " + channelId); - VideoPagerWithChannel pager = getChannelPager(channelId.getRawId()); + VideoPagerWithChannel pager = getChannelPager(channelId); // get the channel, and add all the videos from the first page try { return pager.getNextPageAsVideosAndUpdateChannel(persistentChannel); @@ -359,11 +359,11 @@ private X callParser(ParserCall parser, X defaultValue) { } } - private ChannelExtractor getChannelExtractor(String channelId) + private ChannelExtractor getChannelExtractor(ChannelId channelId) throws ExtractionException, IOException { // Extract from it ChannelExtractor channelExtractor = streamingService - .getChannelExtractor(getListLinkHandler(Objects.requireNonNull(channelId, "channelId"))); + .getChannelExtractor(getListLinkHandler(Objects.requireNonNull(channelId, "channelId").getRawId())); channelExtractor.fetchPage(); return channelExtractor; } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/DatabaseTasks.java b/app/src/main/java/free/rm/skytube/businessobjects/db/DatabaseTasks.java index c951a325e..8e0006f8e 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/db/DatabaseTasks.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/db/DatabaseTasks.java @@ -26,6 +26,7 @@ import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeException; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService; +import free.rm.skytube.businessobjects.model.Status; import free.rm.skytube.gui.businessobjects.views.ChannelSubscriber; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Completable; @@ -79,7 +80,15 @@ public static PersistentChannel getChannelOrRefresh(Context context, ChannelId c needsRefresh = persistentChannel.channel().getLastCheckTime() < System.currentTimeMillis() - (24 * 60 * 60 * 1000L); } if (needsRefresh && SkyTubeApp.isConnected(context)) { - return NewPipeService.get().getChannelDetails(channelId, persistentChannel); + try { + return NewPipeService.get().getChannelDetails(channelId, persistentChannel); + } catch (NewPipeException newPipeException) { + if (persistentChannel != null && persistentChannel.status() != Status.OK) { + Log.e(TAG, "Channel is blocked/terminated - and kept that way: "+ persistentChannel+", message:"+newPipeException.getMessage()); + return persistentChannel; + } + throw newPipeException; + } } return persistentChannel; } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/LocalChannelTable.java b/app/src/main/java/free/rm/skytube/businessobjects/db/LocalChannelTable.java index 9d9b6286e..67e277364 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/db/LocalChannelTable.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/db/LocalChannelTable.java @@ -19,13 +19,18 @@ import android.database.sqlite.SQLiteDatabase; +import androidx.annotation.NonNull; + import com.github.skytube.components.utils.Column; import com.github.skytube.components.utils.SQLiteHelper; import com.google.common.base.Joiner; import free.rm.skytube.businessobjects.YouTube.POJOs.PersistentChannel; +import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; +import free.rm.skytube.businessobjects.model.Status; public class LocalChannelTable { + public static final String TABLE_NAME = "Channel"; public static final String COL_CHANNEL_ID_name = "Channel_Id"; public static final String COL_LAST_VIDEO_TS = "Last_Video_TS"; @@ -77,6 +82,11 @@ public static void updateLatestVideoTimestamp(SQLiteDatabase db, PersistentChann latestPublishTimestamp, persistentChannel.subscriptionPk()}); } + public static void updateChannelStatus(SQLiteDatabase db, @NonNull ChannelId channelId, @NonNull Status status) { + db.execSQL("update " + TABLE_NAME + " set " + COL_STATE.name() + " = ? where " + COL_CHANNEL_ID.name() + " = ?", + new Object[] { status.code, channelId.getRawId() }); + } + public static void addChannelIdIndex(SQLiteDatabase db) { SQLiteHelper.createIndex(db, "IDX_channel_channelId", TABLE_NAME, COL_CHANNEL_ID); } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsDb.java b/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsDb.java index 28eb8c976..9302ebd9a 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsDb.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsDb.java @@ -44,6 +44,7 @@ import java.util.Objects; import java.util.Set; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import free.rm.skytube.app.SkyTubeApp; @@ -54,6 +55,7 @@ import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel; import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeVideo; import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; +import free.rm.skytube.businessobjects.model.Status; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -70,11 +72,11 @@ public class SubscriptionsDb extends SQLiteOpenHelperEx { private static final String FIND_EMPTY_RETRIEVAL_TS = String.format("SELECT %s,%s FROM %s WHERE %s IS NULL", SubscriptionsVideosTable.COL_YOUTUBE_VIDEO_ID, SubscriptionsVideosTable.COL_YOUTUBE_VIDEO, SubscriptionsVideosTable.TABLE_NAME, SubscriptionsVideosTable.COL_RETRIEVAL_TS); - private static final String SUBSCRIBED_CHANNEL_INFO = "SELECT c.Channel_Id,c.Title,c.Thumbnail_Normal_Url,s.Last_Visit_Time,c.Last_Video_TS as latest_video_ts FROM Subs s,Channel c where s.channel_pk = c._Id "; + private static final String SUBSCRIBED_CHANNEL_INFO = "SELECT c.Channel_Id,c.Title,c.Thumbnail_Normal_Url,s.Last_Visit_Time,c.Last_Video_TS as latest_video_ts,c.state FROM Subs s,Channel c where s.channel_pk = c._Id "; private static final String SUBSCRIBED_CHANNEL_INFO_ORDER_BY = " ORDER BY LOWER(" + LocalChannelTable.COL_TITLE + ") ASC"; private static final String SUBSCRIBED_CHANNEL_LIMIT_BY_TITLE = " and LOWER(c." +LocalChannelTable.COL_TITLE + ") like ?"; - private static final String GET_ALL_SUBSCRIBED_CHANNEL_ID = "SELECT "+SubscriptionsTable.COL_CHANNEL_ID + " FROM "+SubscriptionsTable.TABLE_NAME; + private static final String GET_ALL_SUBSCRIBED_CHANNEL_ID = "SELECT s." + SubscriptionsTable.COL_CHANNEL_ID + " FROM " + SubscriptionsTable.TABLE_NAME + " s, " + LocalChannelTable.TABLE_NAME + " c where s.channel_pk = c._Id and c." + LocalChannelTable.COL_STATE.name() + " = 0"; private static final String IS_SUBSCRIBED_QUERY = String.format("SELECT EXISTS(SELECT %s FROM %s WHERE %s =?) AS VAL ", SubscriptionsTable.COL_ID, SubscriptionsTable.TABLE_NAME, SubscriptionsTable.COL_CHANNEL_ID); private static final String GET_PK_FROM_CHANNEL_ID = "SELECT " + SubscriptionsTable.COL_ID + " FROM " + SubscriptionsTable.TABLE_NAME + " WHERE " + SubscriptionsTable.COL_CHANNEL_ID + " = ?"; @@ -307,7 +309,7 @@ public DatabaseResult subscribe(PersistentChannel persistentChannel, Collection< SkyTubeApp.nonUiThread(); saveChannelVideos(videos, persistentChannel, false); - return saveSubscription(persistentChannel.channelPk(), persistentChannel.channel().getChannelId()); + return saveSubscription(persistentChannel.channelPk(), persistentChannel.getChannelId()); } /** @@ -352,7 +354,7 @@ private DatabaseResult saveSubscription(long channelPk, ChannelId channelId) { */ public DatabaseResult unsubscribe(PersistentChannel channel) { SkyTubeApp.nonUiThread(); - Logger.i(this, "unsubscribing subs_id= %s, channel_id = %s, channel_pk = %s", channel.subscriptionPk(), channel.channel().getChannelId(), channel.channelPk()); + Logger.i(this, "unsubscribing subs_id= %s, channel_id = %s, channel_pk = %s", channel.subscriptionPk(), channel.getChannelId(), channel.channelPk()); // delete any feed videos pertaining to this channel getWritableDatabase().delete(SubscriptionsVideosTable.TABLE_NAME_V2, SubscriptionsVideosTable.COL_SUBS_ID.name() + " = ?", @@ -416,7 +418,7 @@ public void updateVideo(YouTubeVideo video) { private ContentValues convertToContentValues(final YouTubeVideo video, @Nullable PersistentChannel persistentChannel) { ContentValues values = new ContentValues(); if (persistentChannel != null) { - ChannelId chId = persistentChannel.channel().getChannelId(); + ChannelId chId = persistentChannel.getChannelId(); values.put(SubscriptionsVideosTable.COL_CHANNEL_ID_V2.name(), chId.getRawId()); values.put(SubscriptionsVideosTable.COL_SUBS_ID.name(), persistentChannel.subscriptionPk()); values.put(SubscriptionsVideosTable.COL_CHANNEL_PK.name(), persistentChannel.channelPk()); @@ -447,6 +449,7 @@ private ContentValues convertToContentValues(final YouTubeVideo video, @Nullable } return values; } + public int setPublishTimestamp(YouTubeVideo video) { ContentValues values = new ContentValues(); values.put(SubscriptionsVideosTable.COL_PUBLISH_TIME.name(), video.getPublishTimestamp()); @@ -474,7 +477,19 @@ public Single> getSubscribedChannelIdsAsync() { .subscribeOn(Schedulers.io()); } - /** + public void setChannelState(@NonNull PersistentChannel channel, @NonNull Status status) { + Logger.i(this, "Set channel %s pk=%s, id=%s state to %s", channel.channel().getTitle(), channel.channelPk(), channel.getChannelId(), status); + SkyTubeApp.nonUiThread(); + LocalChannelTable.updateChannelStatus(getWritableDatabase(), channel.getChannelId(), status); + } + + public void setChannelState(@NonNull ChannelId channelId, @NonNull Status status) { + Logger.i(this, "Set channel id=%s state to %s", channelId, status); + SkyTubeApp.nonUiThread(); + LocalChannelTable.updateChannelStatus(getWritableDatabase(), channelId, status); + } + + /** * Returns a list of channels that the user subscribed to, without accessing the network * * @@ -559,29 +574,6 @@ public Single updateLastVisitTimeAsync(ChannelId channelId) { }).subscribeOn(Schedulers.io()); } - /** - * Updates the given channel's last visit time. - * - * @param channelId Channel ID - * - * @return last visit time, if the update was successful; -1 otherwise. - */ - public long updateLastVideoFetch(ChannelId channelId) { - SQLiteDatabase db = getWritableDatabase(); - long currentTime = System.currentTimeMillis(); - - ContentValues values = new ContentValues(); - values.put(SubscriptionsTable.COL_LAST_VIDEO_FETCH, currentTime); - - int count = db.update( - SubscriptionsTable.TABLE_NAME, - values, - SubscriptionsTable.COL_CHANNEL_ID + " = ?", - new String[]{channelId.getRawId()}); - - return (count > 0 ? currentTime : -1); - } - private boolean hasVideo(YouTubeVideo video) { return SQLiteHelper.executeQueryForInteger(getReadableDatabase(), HAS_VIDEO_QUERY, new String[]{video.getId()}, 0) > 0; } @@ -737,10 +729,11 @@ public PersistentChannel getCachedChannel(ChannelId channelId) { String banner = cursor.getString(cursor.getColumnIndexOrThrow(LocalChannelTable.COL_BANNER_URL)); long subscriberCount = SQLiteHelper.getLong(cursor, LocalChannelTable.COL_SUBSCRIBER_COUNT); long lastCheckTs = SQLiteHelper.getLong(cursor, LocalChannelTable.COL_LAST_CHECK_TS); + Status statusCode = getStatusCode(cursor); // TODO: use Long lastVideoTs = SQLiteHelper.getOptionalLong(cursor, LocalChannelTable.COL_LAST_VIDEO_TS); YouTubeChannel channel = new YouTubeChannel(channelId.getRawId(), title, description, thumbnail, banner, subscriberCount, subscriptionPk != null, -1, lastCheckTs, null, Collections.emptyList()); - return new PersistentChannel(channel, channelPk, subscriptionPk); + return new PersistentChannel(channel, channelPk, subscriptionPk, statusCode); } } return null; @@ -757,6 +750,10 @@ public PersistentChannel cacheChannel(@Nullable PersistentChannel persistentChan return cacheChannel(db, persistentChannel, channel); } + private Status getStatusCode(Cursor cursor) { + return Status.lookup(SQLiteHelper.getLong(cursor, LocalChannelTable.COL_STATE.name())); + } + private String[] toArray(Object obj) { return new String[] { String.valueOf(obj)}; } @@ -778,10 +775,13 @@ private PersistentChannel cacheChannel(SQLiteDatabase db, @Nullable PersistentCh ContentValues values = toContentValues(channel); final Long channelPk; + final Status status; if (persistentChannel != null) { channelPk = persistentChannel.channelPk(); + status = persistentChannel.status(); } else { channelPk = getChannelPk(db, channel.getChannelId()); + status = Status.OK; } Long subPk = persistentChannel != null ? persistentChannel.subscriptionPk() : null; @@ -796,11 +796,11 @@ private PersistentChannel cacheChannel(SQLiteDatabase db, @Nullable PersistentCh if (count != 1) { throw new IllegalStateException("Unable to update channel " + channel + ", with pk= " + channelPk); } - return new PersistentChannel(channel, channelPk, subPk); + return new PersistentChannel(channel, channelPk, subPk, status); } values.put(LocalChannelTable.COL_CHANNEL_ID.name(), channel.getChannelId().getRawId()); long newPk = db.insert(LocalChannelTable.TABLE_NAME, null, values); - return new PersistentChannel(channel, newPk, subPk); + return new PersistentChannel(channel, newPk, subPk, status); } private static ContentValues toContentValues(YouTubeChannel channel) { @@ -835,11 +835,13 @@ public List getSubscribedChannelsByText(String searchText, boolean final int thumbnail = cursor.getColumnIndexOrThrow(LocalChannelTable.COL_THUMBNAIL_NORMAL_URL); final int colLastVisit = cursor.getColumnIndexOrThrow(SubscriptionsTable.COL_LAST_VISIT_TIME); final int colLatestVideoTs = cursor.getColumnIndexOrThrow("latest_video_ts"); + final int colStatus = cursor.getColumnIndexOrThrow(LocalChannelTable.COL_STATE.name()); while(cursor.moveToNext()) { Long lastVisit = cursor.getLong(colLastVisit); Long latestVideoTs = cursor.getLong(colLatestVideoTs); boolean hasNew = (latestVideoTs != null && (lastVisit == null || latestVideoTs > lastVisit)); - result.add(new ChannelView(new ChannelId(cursor.getString(channelId)), cursor.getString(title), cursor.getString(thumbnail), hasNew)); + Status status = Status.lookup(cursor.getInt(colStatus)); + result.add(new ChannelView(new ChannelId(cursor.getString(channelId)), cursor.getString(title), cursor.getString(thumbnail), hasNew, status)); } return result; } diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsTable.java b/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsTable.java index 600073ce7..fcbb25ed0 100644 --- a/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsTable.java +++ b/app/src/main/java/free/rm/skytube/businessobjects/db/SubscriptionsTable.java @@ -82,6 +82,12 @@ public static void cleanupTable(SQLiteDatabase db) { ") select _id," + COL_CHANNEL_ID + "," + COL_CATEGORY_ID.name() + "," + COL_LAST_VISIT_TIME + "," + COL_LAST_CHECK_TIME ); } + /** + * Updates the given channel's last fetch time with the current timestamp. + * + * @param persistentChannel The channel + * + */ public static void updateLastVideoFetchTimestamps(SQLiteDatabase db, PersistentChannel persistentChannel) { if (persistentChannel.isSubscribed()) { db.execSQL("update " + TABLE_NAME + " set " + COL_LAST_VIDEO_FETCH + " = ? where " + COL_ID + " = ?", new Object[] { diff --git a/app/src/main/java/free/rm/skytube/businessobjects/model/Status.java b/app/src/main/java/free/rm/skytube/businessobjects/model/Status.java new file mode 100644 index 000000000..d88da27ae --- /dev/null +++ b/app/src/main/java/free/rm/skytube/businessobjects/model/Status.java @@ -0,0 +1,34 @@ +package free.rm.skytube.businessobjects.model; + +public enum Status { + OK (0), + ACCOUNT_TERMINATED (1), + NOT_EXISTS (2); + + public final int code; + Status(int code) { + this.code = code; + } + + + public static Status lookup(Long code) { + if (code != null){ + return lookup(code.intValue()); + } else { + throw new IllegalArgumentException("Missing code: "+ code); + } + } + + public static Status lookup(int code) { + switch (code) { + case 0: + return OK; + case 1: + return ACCOUNT_TERMINATED; + case 2: + return NOT_EXISTS; + } + throw new IllegalArgumentException("Unknown code: " + code); + } +} + diff --git a/app/src/main/java/free/rm/skytube/gui/businessobjects/SubscriptionsBackupsManager.java b/app/src/main/java/free/rm/skytube/gui/businessobjects/SubscriptionsBackupsManager.java index 95f8840d7..cb3bc48ac 100644 --- a/app/src/main/java/free/rm/skytube/gui/businessobjects/SubscriptionsBackupsManager.java +++ b/app/src/main/java/free/rm/skytube/gui/businessobjects/SubscriptionsBackupsManager.java @@ -52,18 +52,14 @@ import free.rm.skytube.app.SkyTubeApp; import free.rm.skytube.businessobjects.Logger; import free.rm.skytube.businessobjects.YouTube.POJOs.PersistentChannel; -import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel; -import free.rm.skytube.businessobjects.YouTube.YouTubeTasks; import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; import free.rm.skytube.businessobjects.YouTube.newpipe.ContentId; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeException; import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService; -import free.rm.skytube.businessobjects.db.DatabaseResult; import free.rm.skytube.businessobjects.db.DatabaseTasks; import free.rm.skytube.businessobjects.db.SubscriptionsDb; import free.rm.skytube.gui.businessobjects.preferences.BackupDatabases; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; diff --git a/app/src/main/java/free/rm/skytube/gui/businessobjects/adapters/SubsAdapter.java b/app/src/main/java/free/rm/skytube/gui/businessobjects/adapters/SubsAdapter.java index 27c417016..8b48adbc2 100644 --- a/app/src/main/java/free/rm/skytube/gui/businessobjects/adapters/SubsAdapter.java +++ b/app/src/main/java/free/rm/skytube/gui/businessobjects/adapters/SubsAdapter.java @@ -29,9 +29,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import java.util.HashSet; import java.util.Iterator; -import java.util.Set; import free.rm.skytube.R; import free.rm.skytube.app.EventBus; @@ -39,7 +37,6 @@ import free.rm.skytube.businessobjects.YouTube.newpipe.ChannelId; import free.rm.skytube.businessobjects.db.DatabaseTasks; import free.rm.skytube.databinding.SubChannelBinding; -import free.rm.skytube.gui.businessobjects.MainActivityListener; import io.reactivex.rxjava3.disposables.CompositeDisposable; /** @@ -174,7 +171,22 @@ void updateInfo(ChannelView channel) { .apply(new RequestOptions().placeholder(R.drawable.channel_thumbnail_default)) .into(binding.subChannelThumbnailImageView); - binding.subChannelNameTextView.setText(channel.getTitle()); + final String prefix; + switch (channel.status()) { + case ACCOUNT_TERMINATED: { + prefix = itemView.getContext().getString(R.string.status_account_terminated); + break; + } + case NOT_EXISTS: { + prefix = itemView.getContext().getString(R.string.status_not_exists); + break; + } + default: { + prefix = ""; + break; + } + } + binding.subChannelNameTextView.setText(prefix + channel.getTitle()); binding.subChannelNewVideosNotification.setVisibility(channel.isNewVideosSinceLastVisit() ? View.VISIBLE : View.INVISIBLE); this.channel = channel; } diff --git a/app/src/main/res/values/strings_subs.xml b/app/src/main/res/values/strings_subs.xml index 9f17b5d23..6959ffd0c 100644 --- a/app/src/main/res/values/strings_subs.xml +++ b/app/src/main/res/values/strings_subs.xml @@ -30,4 +30,7 @@ Could not import your YouTube subscriptions. About + [Deleted] + [Terminated] +