diff --git a/app/build.gradle b/app/build.gradle
index 40b936c65..63605c259 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -164,9 +164,11 @@ dependencies {
// implementation 'com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:v0.22.6'
implementation 'com.github.SkyTubeTeam.NewPipeExtractor:NewPipeExtractor:skytube-2024-07-24'
- implementation ('com.github.SkyTubeTeam.components:okhttp-client:0.0.7') {
+ def skytubeComponents = '0.0.9'
+ implementation ("com.github.SkyTubeTeam.components:okhttp-client:$skytubeComponents") {
exclude group: 'com.github.SkyTubeTeam.NewPipeExtractor', module: 'extractor'
}
+ implementation "com.github.SkyTubeTeam.components:android-utils:$skytubeComponents"
// Java HTML parser
implementation "org.jsoup:jsoup:1.17.2"
diff --git a/app/src/extra/java/free/rm/skytube/gui/fragments/ChromecastControllerFragment.java b/app/src/extra/java/free/rm/skytube/gui/fragments/ChromecastControllerFragment.java
index bc398a5f4..3546dbd44 100644
--- a/app/src/extra/java/free/rm/skytube/gui/fragments/ChromecastControllerFragment.java
+++ b/app/src/extra/java/free/rm/skytube/gui/fragments/ChromecastControllerFragment.java
@@ -17,6 +17,7 @@
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import free.rm.skytube.R;
+import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel;
import free.rm.skytube.businessobjects.db.DatabaseTasks;
import free.rm.skytube.databinding.FragmentChromecastControllerBinding;
import free.rm.skytube.databinding.VideoDescriptionBinding;
@@ -106,8 +107,9 @@ private void setupDescription() {
compositeDisposable.add(
DatabaseTasks.getChannelInfo(requireContext(), video.getChannelId(), false)
- .subscribe(youTubeChannel -> {
- videoDescriptionBinding.videoDescSubscribeButton.setChannel(youTubeChannel);
+ .subscribe(subscribedChannel -> {
+ YouTubeChannel youTubeChannel = subscribedChannel.channel();
+ videoDescriptionBinding.videoDescSubscribeButton.setChannel(youTubeChannel);
Glide.with(requireContext())
.load(youTubeChannel.getThumbnailUrl())
diff --git a/app/src/main/java/free/rm/skytube/app/FeedUpdateTask.java b/app/src/main/java/free/rm/skytube/app/FeedUpdateTask.java
index 095d791b6..908c788ed 100644
--- a/app/src/main/java/free/rm/skytube/app/FeedUpdateTask.java
+++ b/app/src/main/java/free/rm/skytube/app/FeedUpdateTask.java
@@ -65,10 +65,10 @@ public synchronized boolean start(Context context) {
if (refreshInProgress) {
return false;
}
- createNotificationChannel(context);
if (!SkyTubeApp.isConnected(context)) {
return false;
}
+ createNotificationChannel(context);
SkyTubeApp.getSettings().setRefreshSubsFeedFull(false);
refreshInProgress = true;
diff --git a/app/src/main/java/free/rm/skytube/app/SkyTubeApp.java b/app/src/main/java/free/rm/skytube/app/SkyTubeApp.java
index 4e888f290..90ca0ec9e 100644
--- a/app/src/main/java/free/rm/skytube/app/SkyTubeApp.java
+++ b/app/src/main/java/free/rm/skytube/app/SkyTubeApp.java
@@ -516,7 +516,7 @@ public static boolean openContent(Context ctx, ContentId content) {
public static void launchChannel(ChannelId channelId, Context context) {
if (channelId != null) {
DatabaseTasks.getChannelInfo(context, channelId, true)
- .subscribe(youTubeChannel -> launchChannel(youTubeChannel, context));
+ .subscribe(youTubeChannel -> launchChannel(youTubeChannel.channel(), context));
}
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/FileDownloader.java b/app/src/main/java/free/rm/skytube/businessobjects/FileDownloader.java
index dcd10c459..dac3d90c1 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/FileDownloader.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/FileDownloader.java
@@ -201,13 +201,12 @@ private void fileDownloadStatus() {
.query(new DownloadManager.Query().setFilterById(downloadId));
if (cursor != null && cursor.moveToFirst()) {
- final int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
- downloadSuccessful = (cursor.getInt(columnIndex) == DownloadManager.STATUS_SUCCESSFUL);
+ downloadSuccessful = (cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL);
if (downloadSuccessful) {
- downloadedFileUri = Uri.parse(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
+ downloadedFileUri = Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)));
} else {
- final int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
+ final int columnReason = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON);
final int reason = cursor.getInt(columnReason);
// output why the download has failed...
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/Sponsorblock/SBTasks.java b/app/src/main/java/free/rm/skytube/businessobjects/Sponsorblock/SBTasks.java
index cc87387a3..154739d71 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/Sponsorblock/SBTasks.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/Sponsorblock/SBTasks.java
@@ -9,19 +9,11 @@
import androidx.annotation.StringRes;
import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-import free.rm.skytube.BuildConfig;
import free.rm.skytube.R;
import free.rm.skytube.app.SkyTubeApp;
import free.rm.skytube.businessobjects.YouTube.newpipe.NewPipeService;
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
new file mode 100644
index 000000000..821e6c766
--- /dev/null
+++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/PersistentChannel.java
@@ -0,0 +1,69 @@
+/*
+ * SkyTube
+ * Copyright (C) 2024 Zsombor Gegesy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 3 of the License).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+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, 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;
+ }
+
+ public Long subscriptionPk() {
+ return subscriptionPk;
+ }
+
+ public boolean isSubscribed() {
+ return subscriptionPk != null;
+ }
+
+ public PersistentChannel with(YouTubeChannel newInstance) {
+ newInstance.setUserSubscribed(isSubscribed());
+ return new PersistentChannel(newInstance, channelPk, subscriptionPk, status);
+ }
+
+ public PersistentChannel withSubscriptionPk(Long newSubscriptionPk) {
+ return new PersistentChannel(channel, channelPk, newSubscriptionPk, status);
+ }
+}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/YouTubeChannel.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/YouTubeChannel.java
index f3b0f2d26..67651aac9 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/YouTubeChannel.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/POJOs/YouTubeChannel.java
@@ -127,7 +127,7 @@ public void setUserSubscribed(boolean userSubscribed) {
}
public Disposable updateLastVisitTime() {
- return SubscriptionsDb.getSubscriptionsDb().updateLastVisitTimeAsync(id).subscribe(timestamp -> {
+ return SubscriptionsDb.getSubscriptionsDb().updateLastVisitTimeAsync(getChannelId()).subscribe(timestamp -> {
lastVisitTime = timestamp;
if (lastVisitTime < 0) {
Logger.e(YouTubeChannel.this, "Unable to update channel's last visit time. ChannelID=" + id);
@@ -143,6 +143,10 @@ public long getLastVideoTime() {
return lastVideoTime;
}
+ public void setLastVideoTime(long lastVideoTime) {
+ this.lastVideoTime = lastVideoTime;
+ }
+
public List getTags() {
return tags;
}
@@ -174,12 +178,10 @@ public Single blockChannel() {
* out.
*/
public Single blockChannel(boolean displayToastMessage) {
- return SubscriptionsDb.getSubscriptionsDb().getUserSubscribedToChannel(getChannelId())
- .flatMap(isSubscribed -> DatabaseTasks.subscribeToChannel(false,
- null, SkyTubeApp.getContext(), this, false))
- .map(result -> SkyTubeApp.getSettings().isChannelDenyListEnabled())
- .flatMap(isDenyListEnabled -> {
- if (isDenyListEnabled) {
+ return DatabaseTasks.subscribeToChannel(false,
+ null, SkyTubeApp.getContext(), getChannelId(), false)
+ .flatMap(result -> {
+ if (SkyTubeApp.getSettings().isChannelDenyListEnabled()) {
return dennyChannel(displayToastMessage);
} else {
return removeAllowedChannel(displayToastMessage);
@@ -196,17 +198,9 @@ public Single unblockChannel() {
return unblockChannel(true);
}
- public Single unblockChannel(boolean displayToastMessage) {
- return Single.fromCallable(() -> SkyTubeApp.getSettings().isChannelDenyListEnabled())
- .flatMap(isDenyListEnabled -> {
- if (isDenyListEnabled) {
- return removeDeniedChannel(displayToastMessage);
- } else {
- return allowChannel(displayToastMessage);
- }
- })
- .observeOn(AndroidSchedulers.mainThread());
- }
+ public Single unblockChannel(boolean displayToastMessage) {
+ return (SkyTubeApp.getSettings().isChannelDenyListEnabled() ? removeDeniedChannel(displayToastMessage) : allowChannel(displayToastMessage)).observeOn(AndroidSchedulers.mainThread());
+ }
/**
* Denny the channel.
@@ -292,39 +286,6 @@ private Single removeDeniedChannel(boolean displayToastMessage) {
});
}
- public static Disposable subscribeChannel(final Context context, final ChannelId channelId) {
- if (channelId != null) {
- return DatabaseTasks.getChannelInfo(context, channelId, false)
- .observeOn(Schedulers.io())
- .map(youTubeChannel ->
- new Pair<>(youTubeChannel, SubscriptionsDb.getSubscriptionsDb().subscribe(youTubeChannel))
- )
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(youTubeChannelWithResult -> {
- switch(youTubeChannelWithResult.second) {
- case SUCCESS: {
- youTubeChannelWithResult.first.setUserSubscribed(true);
- EventBus.getInstance().notifyMainTabChanged(EventBus.SettingChange.SUBSCRIPTION_LIST_CHANGED);
- SkyTubeApp.getSettings().setRefreshSubsFeedFromCache(true);
- Toast.makeText(context, R.string.channel_subscribed, Toast.LENGTH_LONG).show();
- break;
- }
- case NOT_MODIFIED: {
- Toast.makeText(context, R.string.channel_already_subscribed, Toast.LENGTH_LONG).show();
- break;
- }
- default: {
- Toast.makeText(context, R.string.channel_subscribe_failed, Toast.LENGTH_LONG).show();
- break;
- }
- }
- });
- } else {
- Toast.makeText(context, "Channel is not specified", Toast.LENGTH_LONG).show();
- return Disposable.empty();
- }
- }
-
public String getChannelUrl() {
return getChannelId().toURL();
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/VideoBlocker.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/VideoBlocker.java
index 7df7d5ae8..4f18dd28f 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/VideoBlocker.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/VideoBlocker.java
@@ -60,7 +60,7 @@ public class VideoBlocker {
/** Default preferred language(s) -- by default, no language shall be filtered out. */
private static final Set defaultPrefLanguages = new HashSet<>(SkyTubeApp.getStringArrayAsList(R.array.languages_iso639_codes));
- private Settings settings;
+ private final Settings settings;
public VideoBlocker() {
settings = SkyTubeApp.getSettings();
@@ -119,7 +119,7 @@ public List filter(List videosList) {
* @return True if the user wants to use the video blocker, false otherwise.
*/
private boolean isVideoBlockerEnabled() {
- return SkyTubeApp.getSettings().isEnableVideoBlocker();
+ return settings.isEnableVideoBlocker();
}
/**
@@ -448,9 +448,9 @@ public String toString() {
*/
public static class BlockedVideo implements Serializable {
- private YouTubeVideo video;
- private FilterType filteringType;
- private String reason;
+ private final YouTubeVideo video;
+ private final FilterType filteringType;
+ private final String reason;
BlockedVideo(YouTubeVideo video, FilterType filteringType, String reason) {
@@ -496,8 +496,8 @@ public interface VideoBlockerListener {
private static class LanguageDetectionSingleton {
private static LanguageDetectionSingleton languageDetectionSingleton = null;
- private TextObjectFactory textObjectFactory;
- private LanguageDetector languageDetector;
+ private final TextObjectFactory textObjectFactory;
+ private final LanguageDetector languageDetector;
private LanguageDetectionSingleton() throws IOException {
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 073ed1d45..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;
@@ -32,6 +34,7 @@
import free.rm.skytube.app.Utils;
import free.rm.skytube.businessobjects.VideoCategory;
import free.rm.skytube.businessobjects.YouTube.POJOs.CardData;
+import free.rm.skytube.businessobjects.YouTube.POJOs.PersistentChannel;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeAPIKey;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubePlaylist;
@@ -41,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;
@@ -88,15 +93,6 @@ public static Single refreshAllSubscriptions(Context context, @Nullable
});
}
- public static Single refreshSubscribedChannel(ChannelId channelId, @Nullable Consumer newVideosFound) {
- if (SkyTubeApp.getSettings().isUseNewPipe() || !YouTubeAPIKey.get().isUserApiKeySet()) {
- return YouTubeTasks.getBulkSubscriptionVideos(Collections.singletonList(channelId), newVideosFound);
- } else {
- return YouTubeTasks.getChannelVideos(channelId, null, false, newVideosFound)
- .map(items -> items.size());
- }
- }
-
private static Single refreshSubscriptions(@NonNull List channelIds, @Nullable Consumer newVideosFound) {
if (SkyTubeApp.getSettings().isUseNewPipe() || !YouTubeAPIKey.get().isUserApiKeySet()) {
return YouTubeTasks.getBulkSubscriptionVideos(channelIds, newVideosFound);
@@ -148,7 +144,7 @@ private static Single getBulkSubscriptionVideos(@NonNull List newVideos = fetchVideos(subscriptionsDb, alreadyKnownVideos, channelId);
List detailedList = new ArrayList<>();
if (!newVideos.isEmpty()) {
- YouTubeChannel dbChannel = subscriptionsDb.getCachedSubscribedChannel(channelId);
+ PersistentChannel dbChannel = subscriptionsDb.getCachedChannel(channelId);
for (YouTubeVideo vid : newVideos) {
YouTubeVideo details;
try {
@@ -157,7 +153,7 @@ private static Single getBulkSubscriptionVideos(@NonNull List getBulkSubscriptionVideos(@NonNull 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.
*/
@@ -243,7 +251,8 @@ private static Single> getChannelVideos(@NonNull ChannelId ch
realVideos.add((YouTubeVideo) cd);
}
}
- db.saveVideos(realVideos, channelId);
+ PersistentChannel channel = db.getCachedChannel(channelId);
+ db.saveChannelVideos(realVideos, channel, true);
return realVideos;
})
.subscribeOn(Schedulers.io())
@@ -257,7 +266,7 @@ private static Single> getChannelVideos(@NonNull ChannelId ch
.doOnError(throwable ->
Toast.makeText(getContext(),
String.format(getContext().getString(R.string.could_not_get_videos),
- db.getCachedChannel(channelId).getTitle()),
+ db.getCachedChannel(channelId).channel().getTitle()),
Toast.LENGTH_LONG).show()
);
}
@@ -406,19 +415,20 @@ public static Maybe> getYouTubeVideos(@NonNull GetYouTubeVideos g
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
+ final boolean subscriptionFeedVideos = videoGridAdapter.getCurrentVideoCategory() == VideoCategory.SUBSCRIPTIONS_FEED_VIDEOS;
return Maybe.fromCallable(() -> {
// get videos from YouTube or the database.
- List videosList;
+ final List videosList;
- if (clearList && videoGridAdapter.getCurrentVideoCategory() == VideoCategory.SUBSCRIPTIONS_FEED_VIDEOS) {
+ if (clearList && subscriptionFeedVideos) {
final int currentSize = videoGridAdapter.getItemCount();
List result = new ArrayList<>(currentSize);
boolean hasNew;
do {
- videosList = getYouTubeVideos.getNextVideos();
- hasNew = !videosList.isEmpty();
- result.addAll(videosList);
+ final List nextVideos = getYouTubeVideos.getNextVideos();
+ hasNew = !nextVideos.isEmpty();
+ result.addAll(nextVideos);
} while(result.size() < currentSize && hasNew);
videosList = result;
} else {
@@ -427,21 +437,29 @@ public static Maybe> getYouTubeVideos(@NonNull GetYouTubeVideos g
if (videosList != null) {
// filter videos
+ final List filteredVideos;
if (videoGridAdapter.getCurrentVideoCategory().isVideoFilteringEnabled()) {
- videosList = new VideoBlocker().filter(videosList);
+ filteredVideos = new VideoBlocker().filter(videosList);
+ } else {
+ filteredVideos = videosList;
}
+ // This is not used for subscriptionFeedVideos
if (channel != null && channel.isUserSubscribed()) {
- for (CardData video : videosList) {
+ for (CardData video : filteredVideos) {
if (video instanceof YouTubeVideo) {
channel.addYouTubeVideo((YouTubeVideo) video);
}
}
- SubscriptionsDb.getSubscriptionsDb().saveChannelVideos(channel.getYouTubeVideos(), channel.getChannelId());
+ SubscriptionsDb db = SubscriptionsDb.getSubscriptionsDb();
+ PersistentChannel persistentChannel = db.getCachedChannel(channel.getChannelId());
+ db.saveChannelVideos(channel.getYouTubeVideos(), persistentChannel, false);
}
+ return filteredVideos;
+ } else {
+ return Collections.emptyList();
}
- return videosList;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
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 3edeebd4c..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
@@ -16,15 +16,12 @@
*/
package free.rm.skytube.businessobjects.YouTube.newpipe;
-import android.util.Log;
-
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
-import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
@@ -107,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 593096653..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
@@ -65,6 +65,7 @@
import free.rm.skytube.app.Settings;
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.POJOs.YouTubeVideo;
@@ -208,10 +209,10 @@ 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.getNextPageAsVideos();
+ List result = pager.getNextPageAsVideosAndUpdateChannel(null).channel().getYouTubeVideos();
Logger.i(this, "getChannelVideos for %s(%s) -> %s videos", pager.getChannel().getTitle(), channelId, result.size());
return result;
}
@@ -254,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 {
@@ -268,8 +269,9 @@ 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);
return new VideoPagerWithChannel(streamingService, channelExtractor.findVideosTab(), channelExtractor.channel);
} catch (ParsingException | RuntimeException e) {
@@ -277,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);
@@ -310,23 +312,22 @@ public CommentPager getCommentPager(String videoId) throws NewPipeException {
}
/**
- * Return detailed information for a channel from it's id.
- * @param channelId
+ * Return detailed, fresh information for a channel from it's id.
+ * @param persistentChannel
* @return the {@link YouTubeChannel}, with a list of recent videos.
* @throws ExtractionException
* @throws IOException
*/
- public YouTubeChannel getChannelDetails(ChannelId channelId) throws NewPipeException {
+ public PersistentChannel getChannelDetails(ChannelId channelId, PersistentChannel persistentChannel) throws NewPipeException {
Logger.i(this, "Fetching channel details for " + channelId);
- VideoPagerWithChannel pager = getChannelPager(Objects.requireNonNull(channelId, "channelId").getRawId());
+ VideoPagerWithChannel pager = getChannelPager(channelId);
// get the channel, and add all the videos from the first page
- YouTubeChannel channel = pager.getChannel();
try {
- channel.getYouTubeVideos().addAll(pager.getNextPageAsVideos());
+ return pager.getNextPageAsVideosAndUpdateChannel(persistentChannel);
} catch (NewPipeException e) {
Logger.e(this, "Unable to retrieve videos for "+channelId+", error: "+e.getMessage(), e);
+ throw e;
}
- return channel;
}
private YouTubeChannel createInternalChannelFromFeed(FeedExtractor extractor) throws ParsingException {
@@ -358,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/YouTube/newpipe/VideoPagerWithChannel.java b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/VideoPagerWithChannel.java
index 3cb96735e..bbad0e387 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/VideoPagerWithChannel.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/YouTube/newpipe/VideoPagerWithChannel.java
@@ -16,14 +16,20 @@
*/
package free.rm.skytube.businessobjects.YouTube.newpipe;
+import androidx.annotation.Nullable;
+
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
+import java.util.List;
+
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.POJOs.YouTubeVideo;
+import free.rm.skytube.businessobjects.db.SubscriptionsDb;
public class VideoPagerWithChannel extends VideoPager {
private final YouTubeChannel channel;
@@ -48,4 +54,16 @@ protected YouTubeVideo convert(StreamInfoItem item, String id) {
item.getViewCount(), date.instant, date.exact, NewPipeService.getThumbnailUrl(id));
}
+ /**
+ * Fetch the first page of videos from the channel, and update the subscription database with the
+ * fresh data.
+ */
+ public PersistentChannel getNextPageAsVideosAndUpdateChannel(@Nullable PersistentChannel persistentChannel) throws NewPipeException {
+ List videos = getNextPageAsVideos();
+ channel.getYouTubeVideos().addAll(videos);
+ long lastPublish = videos.stream().mapToLong(YouTubeVideo::getPublishTimestamp).max().orElse(0);
+ channel.setLastVideoTime(lastPublish);
+ return SubscriptionsDb.getSubscriptionsDb().cacheChannel(persistentChannel, channel);
+ }
+
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/BookmarksDb.java b/app/src/main/java/free/rm/skytube/businessobjects/db/BookmarksDb.java
index b3d5c9882..e8e84a706 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/BookmarksDb.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/BookmarksDb.java
@@ -22,11 +22,11 @@
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
-import android.view.Menu;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
+import com.github.skytube.components.utils.SQLiteHelper;
import com.google.gson.Gson;
import org.json.JSONException;
@@ -39,17 +39,14 @@
import free.rm.skytube.app.SkyTubeApp;
import free.rm.skytube.app.Utils;
-import free.rm.skytube.app.utils.WeakList;
import free.rm.skytube.businessobjects.Logger;
import free.rm.skytube.businessobjects.YouTube.POJOs.CardData;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeVideo;
import free.rm.skytube.businessobjects.YouTube.newpipe.VideoId;
-import free.rm.skytube.businessobjects.interfaces.CardListener;
import free.rm.skytube.businessobjects.interfaces.OrderableDatabase;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
@@ -170,29 +167,26 @@ private DatabaseResult remove(VideoId video) {
if (rowsDeleted > 0) {
// Since we've removed a video, we will need to update the order column for all the videos.
int order = 1;
- Cursor cursor = getReadableDatabase().query(
+ try (Cursor cursor = getReadableDatabase().query(
BookmarksTable.TABLE_NAME,
new String[]{BookmarksTable.COL_YOUTUBE_VIDEO, BookmarksTable.COL_ORDER},
null,
- null, null, null, BookmarksTable.COL_ORDER + " ASC");
- if (cursor.moveToNext()) {
- Gson gson = new Gson();
- do {
- byte[] blob = cursor.getBlob(cursor.getColumnIndex(BookmarksTable.COL_YOUTUBE_VIDEO));
- YouTubeVideo uvideo = gson.fromJson(new String(blob), YouTubeVideo.class).updatePublishTimestampFromDate();
- ContentValues contentValues = new ContentValues();
- contentValues.put(BookmarksTable.COL_ORDER, order++);
-
- getWritableDatabase().update(BookmarksTable.TABLE_NAME, contentValues, BookmarksTable.COL_YOUTUBE_VIDEO_ID + " = ?",
- new String[]{uvideo.getId()});
- } while (cursor.moveToNext());
- }
-
- cursor.close();
-
- return DatabaseResult.SUCCESS;
- }
- return DatabaseResult.NOT_MODIFIED;
+ null, null, null, BookmarksTable.COL_ORDER + " ASC")) {
+ int blobCol = cursor.getColumnIndexOrThrow(BookmarksTable.COL_YOUTUBE_VIDEO);
+ Gson gson = new Gson();
+ while (cursor.moveToNext()) {
+ byte[] blob = cursor.getBlob(blobCol);
+ YouTubeVideo uvideo = gson.fromJson(new String(blob), YouTubeVideo.class).updatePublishTimestampFromDate();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(BookmarksTable.COL_ORDER, order++);
+
+ getWritableDatabase().update(BookmarksTable.TABLE_NAME, contentValues, BookmarksTable.COL_YOUTUBE_VIDEO_ID + " = ?",
+ new String[]{uvideo.getId()});
+ }
+ }
+ return DatabaseResult.SUCCESS;
+ }
+ return DatabaseResult.NOT_MODIFIED;
} catch (SQLException e) {
Logger.e(this, "Database error: " + e.getMessage(), e);
return DatabaseResult.ERROR;
@@ -227,7 +221,7 @@ public void updateOrder(List videos) {
*/
public boolean isBookmarked(String videoId) {
SkyTubeApp.nonUiThread();
- return executeQueryForInteger(BookmarksTable.IS_BOOKMARKED_QUERY, new String[]{videoId}, 0) > 0;
+ return SQLiteHelper.executeQueryForInteger(getReadableDatabase(), BookmarksTable.IS_BOOKMARKED_QUERY, new String[]{videoId}, 0) > 0;
}
/**
@@ -235,7 +229,7 @@ public boolean isBookmarked(String videoId) {
*/
public Single getTotalBookmarkCount() {
return Single.fromCallable(() ->
- executeQueryForInteger(BookmarksTable.COUNT_ALL_BOOKMARKS, 0)
+ SQLiteHelper.executeQueryForInteger(getReadableDatabase(), BookmarksTable.COUNT_ALL_BOOKMARKS, 0)
).subscribeOn(Schedulers.io());
}
@@ -244,7 +238,7 @@ public Single getTotalBookmarkCount() {
*/
public int getMaximumOrderNumber() {
SkyTubeApp.nonUiThread();
- return executeQueryForInteger(BookmarksTable.MAXIMUM_ORDER_QUERY, 0);
+ return SQLiteHelper.executeQueryForInteger(getReadableDatabase(), BookmarksTable.MAXIMUM_ORDER_QUERY, 0);
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/CategoryManagement.java b/app/src/main/java/free/rm/skytube/businessobjects/db/CategoryManagement.java
index 106eafe86..505f3600c 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/CategoryManagement.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/CategoryManagement.java
@@ -24,10 +24,9 @@
import androidx.annotation.StringRes;
+import com.github.skytube.components.utils.SQLiteHelper;
import com.mikepenz.iconics.typeface.library.materialdesigniconic.MaterialDesignIconic;
-import org.apache.commons.codec.binary.StringUtils;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -109,11 +108,11 @@ public List getCategories() {
}
Category addNew(String name, MaterialDesignIconic.Icon icon, boolean builtin) {
- int count = SQLiteOpenHelperEx.executeQueryForInteger(db, CategoriesTable.COUNT_BY_LABEL_QUERY, new String[] { name }, 0);
+ int count = SQLiteHelper.executeQueryForInteger(db, CategoriesTable.COUNT_BY_LABEL_QUERY, new String[] { name }, 0);
if (count > 0) {
return null;
}
- final int priority = SQLiteOpenHelperEx.executeQueryForInteger(db, CategoriesTable.MAXIMUM_PRIORITY_QUERY, 0) + 1;
+ final int priority = SQLiteHelper.executeQueryForInteger(db, CategoriesTable.MAXIMUM_PRIORITY_QUERY, 0) + 1;
ContentValues values = new ContentValues();
values.put(CategoriesTable.COL_ENABLED, 1);
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/Column.java b/app/src/main/java/free/rm/skytube/businessobjects/db/Column.java
deleted file mode 100644
index 63654b426..000000000
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/Column.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package free.rm.skytube.businessobjects.db;
-
-import android.database.Cursor;
-
-class Column {
-
- final String name;
- final String type;
- final String modifier;
- public Column(final String name, final String type) {
- this.name = name;
- this.type = type;
- this.modifier = null;
- }
-
- public Column(final String name, final String type, String modifier) {
- this.name = name;
- this.type = type;
- this.modifier = modifier;
- }
-
- public String format() {
- return name + ' ' + type + (modifier != null ? " "+ modifier : "");
- }
-
- public int getColumn(Cursor cursor) {
- return cursor.getColumnIndex(name);
- }
-}
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 8976c8b48..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
@@ -10,24 +10,26 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
+import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import free.rm.skytube.R;
import free.rm.skytube.app.EventBus;
import free.rm.skytube.app.SkyTubeApp;
import free.rm.skytube.businessobjects.YouTube.POJOs.ChannelView;
+import free.rm.skytube.businessobjects.YouTube.POJOs.PersistentChannel;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeChannel;
import free.rm.skytube.businessobjects.YouTube.POJOs.YouTubeVideo;
import free.rm.skytube.businessobjects.YouTube.VideoBlocker;
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 free.rm.skytube.gui.businessobjects.views.SubscribeButton;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;
@@ -45,29 +47,10 @@ private DatabaseTasks() {}
* Task to retrieve channel information - from the local cache, or from the remote service if the
* value is old or doesn't exist.
*/
- public static Maybe getChannelInfo(@NonNull Context context,
- @NonNull ChannelId channelId,
- boolean staleAcceptable) {
- return Maybe.fromCallable(() -> {
- final SubscriptionsDb db = SubscriptionsDb.getSubscriptionsDb();
- YouTubeChannel channel = db.getCachedChannel(channelId);
- final boolean needsRefresh;
- if (channel == null || TextUtils.isEmpty(channel.getTitle())) {
- needsRefresh = true;
- } else if (staleAcceptable) {
- needsRefresh = false;
- } else {
- needsRefresh = channel.getLastCheckTime() < System.currentTimeMillis() - (24 * 60 * 60 * 1000L);
- }
- if (needsRefresh && SkyTubeApp.isConnected(context)) {
- channel = NewPipeService.get().getChannelDetails(channelId);
- db.cacheChannel(channel);
- }
- if (channel != null) {
- channel.setUserSubscribed(db.isUserSubscribedToChannel(channelId));
- }
- return channel;
- })
+ public static Maybe getChannelInfo(@NonNull Context context,
+ @NonNull ChannelId channelId,
+ boolean staleAcceptable) {
+ return Maybe.fromCallable(() -> getChannelOrRefresh(context, channelId, staleAcceptable))
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
Log.e(TAG, "Error: " + throwable.getMessage(), throwable);
@@ -80,7 +63,37 @@ public static Maybe getChannelInfo(@NonNull Context context,
.subscribeOn(Schedulers.io());
}
- public static Single> getSubscribedChannelView(@Nullable View progressBar,
+ /**
+ * Returns the cached information about the channel, or tries to retrieve it from the network.
+ */
+ public static PersistentChannel getChannelOrRefresh(Context context, ChannelId channelId, boolean staleAcceptable) throws NewPipeException {
+ SkyTubeApp.nonUiThread();
+
+ final SubscriptionsDb db = SubscriptionsDb.getSubscriptionsDb();
+ PersistentChannel persistentChannel = db.getCachedChannel(channelId);
+ final boolean needsRefresh;
+ if (persistentChannel == null || TextUtils.isEmpty(persistentChannel.channel().getTitle())) {
+ needsRefresh = true;
+ } else if (staleAcceptable) {
+ needsRefresh = false;
+ } else {
+ needsRefresh = persistentChannel.channel().getLastCheckTime() < System.currentTimeMillis() - (24 * 60 * 60 * 1000L);
+ }
+ if (needsRefresh && SkyTubeApp.isConnected(context)) {
+ 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;
+ }
+
+ public static Single> getSubscribedChannelView(Context context, @Nullable View progressBar,
@Nullable String searchText) {
final boolean sortChannelsAlphabetically = SkyTubeApp.getPreferenceManager()
.getBoolean(SkyTubeApp.getStr(R.string.pref_key_subscriptions_alphabetical_order), false);
@@ -97,6 +110,11 @@ public static Single> getSubscribedChannelView(@Nullable View
if (progressBar != null) {
progressBar.setVisibility(View.INVISIBLE);
}
+ }).onErrorReturn(error -> {
+ Log.e(TAG, "Error: " + error.getMessage(), error);
+ String msg = context.getString(R.string.could_not_get_channel_detailed, error.getMessage());
+ Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
+ return Collections.emptyList();
});
}
@@ -146,25 +164,30 @@ public static Disposable isVideoWatched(@NonNull String videoId, @NonNull Menu m
* @param subscribeToChannel Whether the channel should be subscribed to.
* @param subscribeButton The subscribe button that the user has just clicked.
* @param context The context to be used to show the toast, if necessary.
- * @param channel The channel the user wants to subscribe / unsubscribe.
+ * @param channelId The channel id the user wants to subscribe / unsubscribe.
* @param displayToastMessage Whether or not a toast should be shown.
*/
- public static Single subscribeToChannel(boolean subscribeToChannel,
+ public static Single> subscribeToChannel(boolean subscribeToChannel,
@Nullable ChannelSubscriber subscribeButton,
@NonNull Context context,
- @NonNull YouTubeChannel channel,
+ @NonNull ChannelId channelId,
boolean displayToastMessage) {
return Single.fromCallable(() -> {
+ PersistentChannel channel = DatabaseTasks.getChannelOrRefresh(context, channelId, true);
+ SubscriptionsDb db = SubscriptionsDb.getSubscriptionsDb();
+ final DatabaseResult result;
if (subscribeToChannel) {
- return SubscriptionsDb.getSubscriptionsDb().subscribe(channel);
+ result = db.subscribe(channel, channel.channel().getYouTubeVideos());
} else {
- return SubscriptionsDb.getSubscriptionsDb().unsubscribe(channel.getChannelId());
+ result = db.unsubscribe(channel);
}
+ return Pair.create(channel, result);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .doOnSuccess(databaseResult -> {
- if (databaseResult == DatabaseResult.SUCCESS) {
+ .doOnSuccess(databaseResultPair -> {
+ YouTubeChannel channel = databaseResultPair.first.channel();
+ if (databaseResultPair.second == DatabaseResult.SUCCESS) {
// we need to refresh the Feed tab so it shows videos from the newly subscribed (or
// unsubscribed) channels
SkyTubeApp.getSettings().setRefreshSubsFeedFromCache(true);
@@ -196,7 +219,7 @@ public static Single subscribeToChannel(boolean subscribeToChann
Toast.makeText(context, R.string.unsubscribed, Toast.LENGTH_LONG).show();
}
}
- } else if (databaseResult == DatabaseResult.NOT_MODIFIED) {
+ } else if (databaseResultPair.second == DatabaseResult.NOT_MODIFIED) {
if (subscribeToChannel) {
Toast.makeText(context, R.string.channel_already_subscribed, Toast.LENGTH_LONG).show();
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/DownloadedVideosDb.java b/app/src/main/java/free/rm/skytube/businessobjects/db/DownloadedVideosDb.java
index fba19361b..8084cd8b1 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/DownloadedVideosDb.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/DownloadedVideosDb.java
@@ -10,6 +10,7 @@
import androidx.annotation.NonNull;
+import com.github.skytube.components.utils.SQLiteHelper;
import com.google.gson.Gson;
import org.json.JSONException;
@@ -521,7 +522,7 @@ public void updateOrder(List videos) {
*/
public Single getTotalCount() {
return Single.fromCallable(() ->
- executeQueryForInteger(DownloadedVideosTable.COUNT_ALL, 0)
+ SQLiteHelper.executeQueryForInteger(getReadableDatabase(), DownloadedVideosTable.COUNT_ALL, 0)
).subscribeOn(Schedulers.io());
}
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 1abbc1034..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
@@ -17,9 +17,22 @@
package free.rm.skytube.businessobjects.db;
+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 = "Channel_Id";
+ public static final String COL_CHANNEL_ID_name = "Channel_Id";
public static final String COL_LAST_VIDEO_TS = "Last_Video_TS";
public static final String COL_LAST_CHECK_TS = "Last_Check_TS";
public static final String COL_TITLE = "Title";
@@ -27,9 +40,14 @@ public class LocalChannelTable {
public static final String COL_THUMBNAIL_NORMAL_URL = "Thumbnail_Normal_Url";
public static final String COL_BANNER_URL = "Banner_Url";
public static final String COL_SUBSCRIBER_COUNT = "Subscriber_Count";
+ public static final Column COL_ID = new Column("_id", "integer", " primary key");
+ public static final Column COL_CHANNEL_ID = new Column(COL_CHANNEL_ID_name, "text", "UNIQUE NOT NULL");
+ public static final Column COL_STATE = new Column("state", "integer", "default 0");
+
+ static final String GET_ID_AND_CHANNEL_ID = String.format("SELECT %s, %s FROM %s", LocalChannelTable.COL_ID.name(), LocalChannelTable.COL_CHANNEL_ID.name(), LocalChannelTable.TABLE_NAME);
- public static final String[] ALL_COLUMNS = new String[]{
- LocalChannelTable.COL_CHANNEL_ID,
+ private static final String[] ALL_COLUMNS = new String[]{
+ LocalChannelTable.COL_CHANNEL_ID.name(),
LocalChannelTable.COL_TITLE,
LocalChannelTable.COL_DESCRIPTION,
LocalChannelTable.COL_BANNER_URL,
@@ -38,9 +56,11 @@ public class LocalChannelTable {
LocalChannelTable.COL_LAST_VIDEO_TS,
LocalChannelTable.COL_LAST_CHECK_TS};
- public static String getCreateStatement() {
+ public static String getCreateStatement(boolean withPk) {
return "CREATE TABLE " + TABLE_NAME + " (" +
- COL_CHANNEL_ID + " TEXT UNIQUE NOT NULL, " +
+ (withPk ? COL_ID.format() + "," : "") +
+ COL_CHANNEL_ID.format() + ", " +
+ COL_STATE.format() + ", " +
COL_TITLE + " TEXT, " +
COL_DESCRIPTION + " TEXT, " +
COL_THUMBNAIL_NORMAL_URL+ " TEXT, " +
@@ -51,4 +71,27 @@ public static String getCreateStatement() {
" )";
}
+ public static final void addIdColumn(SQLiteDatabase db) {
+ final String allRowNames = Joiner.on(',').join(ALL_COLUMNS);
+ SQLiteHelper.updateTableSchema(db, TABLE_NAME, getCreateStatement(true),
+ "insert into " + TABLE_NAME + " (_id," + allRowNames + ") select rowid," + allRowNames);
+ }
+
+ public static void updateLatestVideoTimestamp(SQLiteDatabase db, PersistentChannel persistentChannel, long latestPublishTimestamp) {
+ db.execSQL("update " + TABLE_NAME + " set " + COL_LAST_VIDEO_TS + " = max(?, coalesce(" + COL_LAST_VIDEO_TS + ",0)) where " + COL_ID.name() + " = ?", new Object[]{
+ 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);
+ }
+
+ public static void addStateColumn(SQLiteDatabase db) {
+ SQLiteHelper.addColumn(db, TABLE_NAME, COL_STATE);
+ }
}
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/PlaybackStatusDb.java b/app/src/main/java/free/rm/skytube/businessobjects/db/PlaybackStatusDb.java
index 909d05272..070383e51 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/PlaybackStatusDb.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/PlaybackStatusDb.java
@@ -75,22 +75,23 @@ public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
*/
public synchronized VideoWatchedStatus getVideoWatchedStatus(@NonNull String videoId) {
if(playbackHistoryMap == null) {
- Cursor cursor = getReadableDatabase().query(
- PlaybackStatusTable.TABLE_NAME,
- new String[]{PlaybackStatusTable.COL_YOUTUBE_VIDEO_ID, PlaybackStatusTable.COL_YOUTUBE_VIDEO_POSITION, PlaybackStatusTable.COL_YOUTUBE_VIDEO_WATCHED},
- null,
- null, null, null, null);
- playbackHistoryMap = new HashMap<>();
- if(cursor.moveToFirst()) {
- do {
- String video_id = cursor.getString(cursor.getColumnIndex(PlaybackStatusTable.COL_YOUTUBE_VIDEO_ID));
- int position = cursor.getInt(cursor.getColumnIndex(PlaybackStatusTable.COL_YOUTUBE_VIDEO_POSITION));
- int finished = cursor.getInt(cursor.getColumnIndex(PlaybackStatusTable.COL_YOUTUBE_VIDEO_WATCHED));
- VideoWatchedStatus status = new VideoWatchedStatus(position, finished == 1);
- playbackHistoryMap.put(video_id, status);
- } while (cursor.moveToNext());
- }
- cursor.close();
+ try (Cursor cursor = getReadableDatabase().query(
+ PlaybackStatusTable.TABLE_NAME,
+ new String[]{PlaybackStatusTable.COL_YOUTUBE_VIDEO_ID, PlaybackStatusTable.COL_YOUTUBE_VIDEO_POSITION, PlaybackStatusTable.COL_YOUTUBE_VIDEO_WATCHED},
+ null,
+ null, null, null, null)) {
+ playbackHistoryMap = new HashMap<>();
+ final int videoIdIdx = cursor.getColumnIndexOrThrow(PlaybackStatusTable.COL_YOUTUBE_VIDEO_ID);
+ final int positionIdx = cursor.getColumnIndexOrThrow(PlaybackStatusTable.COL_YOUTUBE_VIDEO_POSITION);
+ final int finishedIdx = cursor.getColumnIndexOrThrow(PlaybackStatusTable.COL_YOUTUBE_VIDEO_WATCHED);
+ while (cursor.moveToNext()) {
+ String video_id = cursor.getString(videoIdIdx);
+ int position = cursor.getInt(positionIdx);
+ int finished = cursor.getInt(finishedIdx);
+ VideoWatchedStatus status = new VideoWatchedStatus(position, finished == 1);
+ playbackHistoryMap.put(video_id, status);
+ }
+ }
}
if(playbackHistoryMap.get(videoId) == null) {
// Requested video has no entry in the database, so create one in the Map. No need to create it in the Database yet - if needed,
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/SQLiteOpenHelperEx.java b/app/src/main/java/free/rm/skytube/businessobjects/db/SQLiteOpenHelperEx.java
index 62ecf29be..7113094bb 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/SQLiteOpenHelperEx.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/SQLiteOpenHelperEx.java
@@ -18,15 +18,11 @@
package free.rm.skytube.businessobjects.db;
import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import java.io.File;
import free.rm.skytube.app.SkyTubeApp;
-import free.rm.skytube.businessobjects.Logger;
/**
* An extended {@link SQLiteOpenHelper} with extra goodies.
@@ -39,17 +35,6 @@ public SQLiteOpenHelperEx(Context context, String name, android.database.sqlite.
super(context, name, factory, version);
}
-
-// /**
-// * Closes the database and clears any singleton instances.
-// */
-// @Override
-// public synchronized void close() {
-// super.close();
-// clearDatabaseInstance();
-// }
-
-
/**
* @return The database (full) path.
*/
@@ -65,108 +50,4 @@ public File getDatabaseDirectory() {
return SkyTubeApp.getContext().getDatabasePath(getDatabaseName()).getParentFile();
}
- /**
- * Execute a constant query, and return the number in the first row, first column.
- *
- * @param query the query to execute
- * @param selectionArgs the arguments for the query.
- * @return a number.
- */
- public Integer executeQueryForInteger(String query, String[] selectionArgs, Integer defaultValue) {
- return executeQueryForInteger(getReadableDatabase(), query, selectionArgs, defaultValue);
- }
-
- /**
- * Execute a constant query, and return the number in the first row, first column.
- *
- * @param query the query to execute
- * @return a number.
- */
- public Integer executeQueryForInteger(String query, Integer defaultValue) {
- return executeQueryForInteger(getReadableDatabase(), query, null, defaultValue);
- }
-
- /**
- * Execute a constant query, and return the number in the first row, first column.
- *
- * @param db the database to execute on.
- * @param query the query to execute
- * @param selectionArgs the arguments for the query.
- * @return a number.
- */
- public static Integer executeQueryForInteger(SQLiteDatabase db, String query, String[] selectionArgs, Integer defaultValue) {
- try (Cursor cursor = db.rawQuery(query, selectionArgs)) {
- if (cursor.moveToFirst()) {
- return cursor.getInt(0);
- }
- }
- return defaultValue;
- }
-
- /**
- * Execute a constant query, and return the number in the first row, first column.
- *
- * @param query the query to execute
- * @return a number.
- */
- public static Integer executeQueryForInteger(SQLiteDatabase db, String query, Integer defaultValue) {
- return executeQueryForInteger(db, query, null, defaultValue);
- }
-
- /**
- * Execute the given sql updates, one-by-one. Throws an exception if any of them fails.
- */
- public static void execSQLUpdates(SQLiteDatabase db, String[] sqlUpdates) {
- for (String sqlUpdate : sqlUpdates) {
- db.execSQL(sqlUpdate);
- }
- }
-
- public static void continueOnError(SQLiteDatabase db, String update) {
- try {
- db.execSQL(update);
- } catch (SQLiteException e) {
- Logger.e(db, e,"Unable to execute %s , because: %s", update, e.getMessage());
- }
- }
-
- public static void addColumn(SQLiteDatabase db, String tableName, Column column) {
- try {
- db.execSQL("ALTER TABLE " + tableName + " ADD COLUMN " + column.format());
- } catch (SQLiteException e) {
- Logger.e(db, e,"Unable to add '%s' '%s' to table: '%s', because: %s", column.name, column.type, tableName, e.getMessage());
- }
- }
-
- public static void createIndex(SQLiteDatabase db, String indexName, String tableName, Column... columns) {
- db.execSQL("CREATE INDEX "+indexName+" ON "+tableName + "("+listColumns(true, columns)+")");
- }
-
- public static void createTable(SQLiteDatabase db, String tableName, Column... columns) {
- try {
- db.execSQL("CREATE TABLE " + tableName + " (" + listColumns(false, columns) + ")");
- } catch (SQLiteException e) {
- Logger.e(db, e,"Unable to create table: '%s', because: %s", tableName, e.getMessage());
- throw e;
- }
- }
-
- private static String listColumns(boolean justNames, final Column[] columns) {
- boolean first = true;
- final StringBuilder sql = new StringBuilder();
- for (Column col : columns) {
- if (first) {
- first = false;
- } else {
- sql.append(", ");
- }
- if (justNames) {
- sql.append(col.name);
- } else {
- sql.append(col.format());
- }
- }
- return sql.toString();
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/free/rm/skytube/businessobjects/db/SearchHistoryDb.java b/app/src/main/java/free/rm/skytube/businessobjects/db/SearchHistoryDb.java
index c1bcb4480..6edf2ce0f 100644
--- a/app/src/main/java/free/rm/skytube/businessobjects/db/SearchHistoryDb.java
+++ b/app/src/main/java/free/rm/skytube/businessobjects/db/SearchHistoryDb.java
@@ -59,32 +59,37 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// grab all the values from the database, recreate it with the new column, and add the search terms back in.
// The current timestamp will be used.
if(oldVersion == 1 && newVersion == 2) {
- Cursor cursor = db.query(SearchHistoryTable.TABLE_NAME,
+ try (Cursor cursor = db.query(SearchHistoryTable.TABLE_NAME,
new String[] {SearchHistoryTable.COL_SEARCH_ID, SearchHistoryTable.COL_SEARCH_TEXT},
null,
null,
null,
null,
- SearchHistoryTable.COL_SEARCH_ID + " ASC");
- List