Skip to content

Commit b5cb367

Browse files
committed
Track downloaded streams and surface status on video detail
1 parent abfde87 commit b5cb367

22 files changed

Lines changed: 1949 additions & 13 deletions

app/schemas/org.schabi.newpipe.database.AppDatabase/10.json

Lines changed: 856 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/org/schabi/newpipe/App.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins
2525
import org.acra.ACRA.init
2626
import org.acra.ACRA.isACRASenderServiceProcess
2727
import org.acra.config.CoreConfigurationBuilder
28+
import org.schabi.newpipe.download.DownloadMaintenance
2829
import org.schabi.newpipe.error.ReCaptchaActivity
2930
import org.schabi.newpipe.extractor.NewPipe
3031
import org.schabi.newpipe.extractor.downloader.Downloader
@@ -120,6 +121,8 @@ open class App :
120121
configureRxJavaErrorHandler()
121122

122123
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl)
124+
125+
DownloadMaintenance.schedule(this)
123126
}
124127

125128
override fun newImageLoader(context: Context): ImageLoader =

app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
1010
import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
1111
import static org.schabi.newpipe.database.Migrations.MIGRATION_8_9;
12+
import static org.schabi.newpipe.database.Migrations.MIGRATION_9_10;
1213

1314
import android.content.Context;
1415
import android.database.Cursor;
@@ -29,7 +30,7 @@ private static AppDatabase getDatabase(final Context context) {
2930
return Room
3031
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
3132
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
32-
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
33+
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10)
3334
.build();
3435
}
3536

app/src/main/java/org/schabi/newpipe/database/AppDatabase.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org.schabi.newpipe.database;
22

3-
import static org.schabi.newpipe.database.Migrations.DB_VER_9;
3+
import static org.schabi.newpipe.database.Migrations.DB_VER_10;
44

55
import androidx.room.Database;
66
import androidx.room.RoomDatabase;
77
import androidx.room.TypeConverters;
88

9+
import org.schabi.newpipe.database.download.DownloadedStreamEntity;
10+
import org.schabi.newpipe.database.download.DownloadedStreamsDao;
911
import org.schabi.newpipe.database.feed.dao.FeedDAO;
1012
import org.schabi.newpipe.database.feed.dao.FeedGroupDAO;
1113
import org.schabi.newpipe.database.feed.model.FeedEntity;
@@ -36,9 +38,9 @@
3638
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
3739
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class,
3840
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
39-
FeedLastUpdatedEntity.class
41+
FeedLastUpdatedEntity.class, DownloadedStreamEntity.class
4042
},
41-
version = DB_VER_9
43+
version = DB_VER_10
4244
)
4345
public abstract class AppDatabase extends RoomDatabase {
4446
public static final String DATABASE_NAME = "newpipe.db";
@@ -62,4 +64,6 @@ public abstract class AppDatabase extends RoomDatabase {
6264
public abstract FeedGroupDAO feedGroupDAO();
6365

6466
public abstract SubscriptionDAO subscriptionDAO();
67+
68+
public abstract DownloadedStreamsDao downloadedStreamsDao();
6569
}

app/src/main/java/org/schabi/newpipe/database/Converters.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.schabi.newpipe.database
22

33
import androidx.room.TypeConverter
4+
import org.schabi.newpipe.database.download.DownloadedStreamStatus
45
import org.schabi.newpipe.extractor.stream.StreamType
56
import org.schabi.newpipe.local.subscription.FeedGroupIcon
67
import java.time.Instant
@@ -49,4 +50,14 @@ class Converters {
4950
fun feedGroupIconOf(id: Int): FeedGroupIcon {
5051
return FeedGroupIcon.entries.first { it.id == id }
5152
}
53+
54+
@TypeConverter
55+
fun downloadedStreamStatusOf(value: Int?): DownloadedStreamStatus? {
56+
return value?.let { DownloadedStreamStatus.fromValue(it) }
57+
}
58+
59+
@TypeConverter
60+
fun integerOf(status: DownloadedStreamStatus?): Int? {
61+
return status?.value
62+
}
5263
}

app/src/main/java/org/schabi/newpipe/database/Migrations.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public final class Migrations {
2727
public static final int DB_VER_7 = 7;
2828
public static final int DB_VER_8 = 8;
2929
public static final int DB_VER_9 = 9;
30+
public static final int DB_VER_10 = 10;
3031

3132
private static final String TAG = Migrations.class.getName();
3233
public static final boolean DEBUG = MainActivity.DEBUG;
@@ -302,6 +303,22 @@ public void migrate(@NonNull final SupportSQLiteDatabase database) {
302303
}
303304
};
304305

306+
public static final Migration MIGRATION_9_10 = new Migration(DB_VER_9, DB_VER_10) {
307+
@Override
308+
public void migrate(@NonNull final SupportSQLiteDatabase database) {
309+
database.execSQL("CREATE TABLE IF NOT EXISTS downloaded_streams "
310+
+ "(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
311+
+ "stream_uid INTEGER NOT NULL, service_id INTEGER NOT NULL, "
312+
+ "url TEXT NOT NULL, file_uri TEXT NOT NULL, parent_uri TEXT, "
313+
+ "display_name TEXT, mime TEXT, size_bytes INTEGER, quality_label TEXT, "
314+
+ "duration_ms INTEGER, status INTEGER NOT NULL, added_at INTEGER NOT NULL, "
315+
+ "last_checked_at INTEGER, missing_since INTEGER, FOREIGN KEY(stream_uid) "
316+
+ "REFERENCES streams(uid) ON UPDATE CASCADE ON DELETE CASCADE)");
317+
database.execSQL("CREATE UNIQUE INDEX index_downloaded_streams_stream_uid "
318+
+ "ON downloaded_streams (stream_uid)");
319+
}
320+
};
321+
305322
private Migrations() {
306323
}
307324
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.schabi.newpipe.database.download
2+
3+
import androidx.room.ColumnInfo
4+
import androidx.room.Entity
5+
import androidx.room.ForeignKey
6+
import androidx.room.Index
7+
import androidx.room.PrimaryKey
8+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_ADDED_AT
9+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_DISPLAY_NAME
10+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_DURATION_MS
11+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_FILE_URI
12+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_ID
13+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_LAST_CHECKED_AT
14+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_MIME
15+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_MISSING_SINCE
16+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_PARENT_URI
17+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_QUALITY_LABEL
18+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_SERVICE_ID
19+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_SIZE_BYTES
20+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_STATUS
21+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_STREAM_UID
22+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.COLUMN_URL
23+
import org.schabi.newpipe.database.download.DownloadedStreamEntity.Companion.TABLE_NAME
24+
import org.schabi.newpipe.database.stream.model.StreamEntity
25+
26+
@Entity(
27+
tableName = TABLE_NAME,
28+
indices = [Index(value = [COLUMN_STREAM_UID], unique = true)],
29+
foreignKeys = [
30+
ForeignKey(
31+
entity = StreamEntity::class,
32+
parentColumns = [StreamEntity.STREAM_ID],
33+
childColumns = [COLUMN_STREAM_UID],
34+
onDelete = ForeignKey.CASCADE
35+
)
36+
]
37+
)
38+
data class DownloadedStreamEntity(
39+
@PrimaryKey(autoGenerate = true)
40+
@ColumnInfo(name = COLUMN_ID)
41+
var id: Long = 0,
42+
43+
@ColumnInfo(name = COLUMN_STREAM_UID)
44+
var streamUid: Long,
45+
46+
@ColumnInfo(name = COLUMN_SERVICE_ID)
47+
var serviceId: Int,
48+
49+
@ColumnInfo(name = COLUMN_URL)
50+
var url: String,
51+
52+
@ColumnInfo(name = COLUMN_FILE_URI)
53+
var fileUri: String,
54+
55+
@ColumnInfo(name = COLUMN_PARENT_URI)
56+
var parentUri: String? = null,
57+
58+
@ColumnInfo(name = COLUMN_DISPLAY_NAME)
59+
var displayName: String? = null,
60+
61+
@ColumnInfo(name = COLUMN_MIME)
62+
var mime: String? = null,
63+
64+
@ColumnInfo(name = COLUMN_SIZE_BYTES)
65+
var sizeBytes: Long? = null,
66+
67+
@ColumnInfo(name = COLUMN_QUALITY_LABEL)
68+
var qualityLabel: String? = null,
69+
70+
@ColumnInfo(name = COLUMN_DURATION_MS)
71+
var durationMs: Long? = null,
72+
73+
@ColumnInfo(name = COLUMN_STATUS)
74+
var status: DownloadedStreamStatus,
75+
76+
@ColumnInfo(name = COLUMN_ADDED_AT)
77+
var addedAt: Long,
78+
79+
@ColumnInfo(name = COLUMN_LAST_CHECKED_AT)
80+
var lastCheckedAt: Long? = null,
81+
82+
@ColumnInfo(name = COLUMN_MISSING_SINCE)
83+
var missingSince: Long? = null
84+
) {
85+
companion object {
86+
const val TABLE_NAME = "downloaded_streams"
87+
const val COLUMN_ID = "id"
88+
const val COLUMN_STREAM_UID = "stream_uid"
89+
const val COLUMN_SERVICE_ID = "service_id"
90+
const val COLUMN_URL = "url"
91+
const val COLUMN_FILE_URI = "file_uri"
92+
const val COLUMN_PARENT_URI = "parent_uri"
93+
const val COLUMN_DISPLAY_NAME = "display_name"
94+
const val COLUMN_MIME = "mime"
95+
const val COLUMN_SIZE_BYTES = "size_bytes"
96+
const val COLUMN_QUALITY_LABEL = "quality_label"
97+
const val COLUMN_DURATION_MS = "duration_ms"
98+
const val COLUMN_STATUS = "status"
99+
const val COLUMN_ADDED_AT = "added_at"
100+
const val COLUMN_LAST_CHECKED_AT = "last_checked_at"
101+
const val COLUMN_MISSING_SINCE = "missing_since"
102+
}
103+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.schabi.newpipe.database.download
2+
3+
enum class DownloadedStreamStatus(val value: Int) {
4+
IN_PROGRESS(0),
5+
AVAILABLE(1),
6+
MISSING(2),
7+
UNLINKED(3);
8+
9+
companion object {
10+
fun fromValue(value: Int): DownloadedStreamStatus = entries.firstOrNull {
11+
it.value == value
12+
} ?: IN_PROGRESS
13+
}
14+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.schabi.newpipe.database.download
2+
3+
import androidx.room.Dao
4+
import androidx.room.Delete
5+
import androidx.room.Insert
6+
import androidx.room.OnConflictStrategy
7+
import androidx.room.Query
8+
import androidx.room.Transaction
9+
import androidx.room.Update
10+
import io.reactivex.rxjava3.core.Flowable
11+
import io.reactivex.rxjava3.core.Maybe
12+
13+
@Dao
14+
interface DownloadedStreamsDao {
15+
@Query("SELECT * FROM downloaded_streams WHERE stream_uid = :streamUid LIMIT 1")
16+
fun observeByStreamUid(streamUid: Long): Flowable<List<DownloadedStreamEntity>>
17+
18+
@Query("SELECT * FROM downloaded_streams WHERE stream_uid = :streamUid LIMIT 1")
19+
fun getByStreamUid(streamUid: Long): Maybe<DownloadedStreamEntity>
20+
21+
@Insert(onConflict = OnConflictStrategy.IGNORE)
22+
fun insert(entity: DownloadedStreamEntity): Long
23+
24+
@Update
25+
fun update(entity: DownloadedStreamEntity): Int
26+
27+
@Query("SELECT * FROM downloaded_streams WHERE stream_uid = :streamUid LIMIT 1")
28+
fun findEntityByStreamUid(streamUid: Long): DownloadedStreamEntity?
29+
30+
@Query("SELECT * FROM downloaded_streams WHERE id = :id LIMIT 1")
31+
fun findEntityById(id: Long): DownloadedStreamEntity?
32+
33+
@Transaction
34+
fun insertOrUpdate(entity: DownloadedStreamEntity): Long {
35+
val newId = insert(entity)
36+
if (newId != -1L) {
37+
entity.id = newId
38+
return newId
39+
}
40+
update(entity)
41+
return entity.id
42+
}
43+
44+
@Query("UPDATE downloaded_streams SET status = :status, last_checked_at = :lastCheckedAt, missing_since = :missingSince WHERE id = :id")
45+
fun updateStatus(id: Long, status: DownloadedStreamStatus, lastCheckedAt: Long?, missingSince: Long?)
46+
47+
@Query("UPDATE downloaded_streams SET file_uri = :fileUri WHERE id = :id")
48+
fun updateFileUri(id: Long, fileUri: String)
49+
50+
@Delete
51+
fun delete(entity: DownloadedStreamEntity)
52+
53+
@Query("DELETE FROM downloaded_streams WHERE stream_uid = :streamUid")
54+
fun deleteByStreamUid(streamUid: Long): Int
55+
56+
@Query("SELECT * FROM downloaded_streams WHERE status = :status ORDER BY last_checked_at ASC LIMIT :limit")
57+
fun listByStatus(status: DownloadedStreamStatus, limit: Int): List<DownloadedStreamEntity>
58+
}

app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public boolean onCreateOptionsMenu(final Menu menu) {
8181
return true;
8282
}
8383

84+
@Override
85+
protected void onResume() {
86+
super.onResume();
87+
new Thread(() ->
88+
DownloadMaintenance.revalidateAvailable(DownloadActivity.this, 10)
89+
).start();
90+
}
91+
8492
@Override
8593
public boolean onOptionsItemSelected(final MenuItem item) {
8694
switch (item.getItemId()) {

0 commit comments

Comments
 (0)