Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ dependencies {
implementation(libs.androidx.cardview)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core)
implementation(libs.androidx.core.i18n)
implementation(libs.androidx.documentfile)
implementation(libs.androidx.fragment)
implementation(libs.androidx.lifecycle.livedata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class DatabaseMigrationTest {
assertEquals(DEFAULT_THUMBNAIL, streamFromMigratedDatabase.thumbnailUrl)
assertNull(streamFromMigratedDatabase.viewCount)
assertNull(streamFromMigratedDatabase.textualUploadDate)
assertNull(streamFromMigratedDatabase.uploadDate)
assertNull(streamFromMigratedDatabase.uploadInstant)
assertNull(streamFromMigratedDatabase.isUploadDateApproximation)

val secondStreamFromMigratedDatabase = listFromDB[1]
Expand All @@ -158,7 +158,7 @@ class DatabaseMigrationTest {
assertEquals("", secondStreamFromMigratedDatabase.thumbnailUrl)
assertNull(secondStreamFromMigratedDatabase.viewCount)
assertNull(secondStreamFromMigratedDatabase.textualUploadDate)
assertNull(secondStreamFromMigratedDatabase.uploadDate)
assertNull(secondStreamFromMigratedDatabase.uploadInstant)
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
}

Expand Down
71 changes: 56 additions & 15 deletions app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.channel.ChannelInfo
import org.schabi.newpipe.extractor.stream.StreamType
import java.io.IOException
import java.time.OffsetDateTime
import kotlin.streams.toList
import java.time.LocalDate
import java.time.Month
import java.time.ZoneOffset

class FeedDAOTest {
private lateinit var db: AppDatabase
Expand All @@ -32,16 +33,56 @@ class FeedDAOTest {

private val serviceId = ServiceList.YouTube.serviceId

private val stream1 = StreamEntity(1, serviceId, "https://youtube.com/watch?v=1", "stream 1", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-01", OffsetDateTime.parse("2023-01-01T00:00:00Z"))
private val stream2 = StreamEntity(2, serviceId, "https://youtube.com/watch?v=2", "stream 2", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-02", OffsetDateTime.parse("2023-01-02T00:00:00Z"))
private val stream3 = StreamEntity(3, serviceId, "https://youtube.com/watch?v=3", "stream 3", StreamType.LIVE_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-03", OffsetDateTime.parse("2023-01-03T00:00:00Z"))
private val stream4 = StreamEntity(4, serviceId, "https://youtube.com/watch?v=4", "stream 4", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
private val stream5 = StreamEntity(5, serviceId, "https://youtube.com/watch?v=5", "stream 5", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-20", OffsetDateTime.parse("2023-08-20T00:00:00Z"))
private val stream6 = StreamEntity(6, serviceId, "https://youtube.com/watch?v=6", "stream 6", StreamType.VIDEO_STREAM, 1000, "channel-3", "https://youtube.com/channel/3", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-09-01", OffsetDateTime.parse("2023-09-01T00:00:00Z"))
private val stream7 = StreamEntity(7, serviceId, "https://youtube.com/watch?v=7", "stream 7", StreamType.VIDEO_STREAM, 1000, "channel-4", "https://youtube.com/channel/4", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
private val stream1 =
createStreamEntity(
1, "https://youtube.com/watch?v=1", "stream 1", uploader = "channel-1",
uploaderUrl = "https://youtube.com/channel/1", date = LocalDate.of(2023, Month.JANUARY, 2),
)
private val stream2 =
createStreamEntity(
2, "https://youtube.com/watch?v=2", "stream 2", uploader = "channel-1",
uploaderUrl = "https://youtube.com/channel/1", date = LocalDate.of(2023, Month.JANUARY, 2),
)
private val stream3 =
createStreamEntity(
3, "https://youtube.com/watch?v=3", "stream 3", StreamType.LIVE_STREAM,
"channel-1", "https://youtube.com/channel/1", LocalDate.of(2023, Month.JANUARY, 3),
)
private val stream4 =
createStreamEntity(
4, "https://youtube.com/watch?v=4", "stream 4", uploader = "channel-2",
uploaderUrl = "https://youtube.com/channel/2", date = LocalDate.of(2023, Month.AUGUST, 10),
)
private val stream5 =
createStreamEntity(
5, "https://youtube.com/watch?v=5", "stream 5", uploader = "channel-2",
uploaderUrl = "https://youtube.com/channel/2", date = LocalDate.of(2023, Month.AUGUST, 20),
)
private val stream6 =
createStreamEntity(
6, "https://youtube.com/watch?v=6", "stream 6", uploader = "channel-3",
uploaderUrl = "https://youtube.com/channel/3", date = LocalDate.of(2023, Month.SEPTEMBER, 1),
)
private val stream7 =
createStreamEntity(
7, "https://youtube.com/watch?v=7", "stream 7", uploader = "channel-4",
uploaderUrl = "https://youtube.com/channel/4", date = LocalDate.of(2023, Month.AUGUST, 10),
)

private val allStreams = listOf(stream1, stream2, stream3, stream4, stream5, stream6, stream7)

private val allStreams = listOf(
stream1, stream2, stream3, stream4, stream5, stream6, stream7
private fun createStreamEntity(
uid: Long,
url: String,
title: String,
type: StreamType = StreamType.VIDEO_STREAM,
uploader: String,
uploaderUrl: String,
date: LocalDate,
) = StreamEntity(
uid, serviceId, url, title, type, duration = 1000, uploader, uploaderUrl,
thumbnailUrl = "https://i.ytimg.com/vi/1/hqdefault.jpg", viewCount = 100, textualUploadDate = date.toString(),
uploadInstant = date.atStartOfDay(ZoneOffset.UTC).toInstant(),
)

@Before
Expand All @@ -63,7 +104,7 @@ class FeedDAOTest {

@Test
fun testUnlinkStreamsOlderThan_KeepOne() {
setupUnlinkDelete("2023-08-15T00:00:00Z")
setupUnlinkDelete(LocalDate.of(2023, Month.AUGUST, 15))
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
)
Expand All @@ -74,7 +115,7 @@ class FeedDAOTest {

@Test
fun testUnlinkStreamsOlderThan_KeepMultiple() {
setupUnlinkDelete("2023-08-01T00:00:00Z")
setupUnlinkDelete(LocalDate.of(2023, Month.AUGUST, 1))
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
)
Expand All @@ -94,10 +135,10 @@ class FeedDAOTest {
)
}

private fun setupUnlinkDelete(time: String) {
private fun setupUnlinkDelete(localDate: LocalDate) {
clearAndFillTables()
Single.fromCallable {
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
feedDAO.unlinkStreamsOlderThan(localDate.atStartOfDay(ZoneOffset.UTC).toInstant())
}.blockingSubscribe()
Single.fromCallable {
streamDAO.deleteOrphans()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset

class HistoryRecordManagerTest {
Expand Down Expand Up @@ -163,7 +162,7 @@ class HistoryRecordManagerTest {
}

companion object {
private val time = OffsetDateTime.of(LocalDateTime.of(2000, 1, 1, 1, 1), ZoneOffset.UTC)
private val time = LocalDateTime.of(2000, 1, 1, 1, 1).toInstant(ZoneOffset.UTC)

private val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(time.minusSeconds(7), 2, "AC"),
Expand Down
18 changes: 7 additions & 11 deletions app/src/main/java/org/schabi/newpipe/database/Converters.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,27 @@ import androidx.room.TypeConverter
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset

class Converters {
/**
* Convert a long value to a [OffsetDateTime].
* Convert a long value to an [Instant].
*
* @param value the long value
* @return the `OffsetDateTime`
* @return the `Instant`
*/
@TypeConverter
fun offsetDateTimeFromTimestamp(value: Long?): OffsetDateTime? {
return value?.let { OffsetDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) }
fun timestampToInstant(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}

/**
* Convert a [OffsetDateTime] to a long value.
* Convert an [Instant] to a long value.
*
* @param offsetDateTime the `OffsetDateTime`
* @param instant the `Instant`
* @return the long value
*/
@TypeConverter
fun offsetDateTimeToTimestamp(offsetDateTime: OffsetDateTime?): Long? {
return offsetDateTime?.withOffsetSameInstant(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
}
fun instantToTimestamp(instant: Instant?) = instant?.toEpochMilli()

@TypeConverter
fun streamTypeOf(value: String): StreamType {
Expand Down
24 changes: 12 additions & 12 deletions app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import org.schabi.newpipe.database.stream.StreamWithState
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
import java.time.Instant

@Dao
abstract class FeedDAO {
Expand Down Expand Up @@ -90,7 +90,7 @@ abstract class FeedDAO {
groupId: Long,
includePlayed: Boolean,
includePartiallyPlayed: Boolean,
uploadDateBefore: OffsetDateTime?
uploadDateBefore: Instant?
): Maybe<List<StreamWithState>>

/**
Expand All @@ -99,7 +99,7 @@ abstract class FeedDAO {
*
* One stream per uploader is kept because it is needed as reference
* when fetching new streams to check if they are new or not.
* @param offsetDateTime the newest date to keep, older streams are removed
* @param instant the newest date to keep, older streams are removed
*/
@Query(
"""
Expand All @@ -115,11 +115,11 @@ abstract class FeedDAO {
INNER JOIN feed f
ON s.uid = f.stream_id

WHERE s.upload_date < :offsetDateTime
WHERE s.upload_date < :instant
AND s.upload_date <> max_upload_date))
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
abstract fun unlinkStreamsOlderThan(instant: Instant)

@Query(
"""
Expand Down Expand Up @@ -168,13 +168,13 @@ abstract class FeedDAO {
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
"""
)
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
abstract fun getOldestSubscriptionUpdate(groupId: Long): Flowable<List<Instant>>

@Query("SELECT MIN(last_updated) FROM feed_last_updated")
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
abstract fun getOldestSubscriptionUpdateFromAll(): Flowable<List<Instant>>

@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable<Long>
abstract fun getNotLoadedCount(): Flowable<Long>

@Query(
"""
Expand All @@ -189,7 +189,7 @@ abstract class FeedDAO {
WHERE lu.last_updated IS NULL
"""
)
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
abstract fun getNotLoadedCountForGroup(groupId: Long): Flowable<Long>

@Query(
"""
Expand All @@ -201,7 +201,7 @@ abstract class FeedDAO {
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
"""
)
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
abstract fun getAllOutdated(outdatedThreshold: Instant): Flowable<List<SubscriptionEntity>>

@Query(
"""
Expand All @@ -216,7 +216,7 @@ abstract class FeedDAO {
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
"""
)
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: Instant): Flowable<List<SubscriptionEntity>>

@Query(
"""
Expand All @@ -231,7 +231,7 @@ abstract class FeedDAO {
"""
)
abstract fun getOutdatedWithNotificationMode(
outdatedThreshold: OffsetDateTime,
outdatedThreshold: Instant,
@NotificationMode notificationMode: Int
): Flowable<List<SubscriptionEntity>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.room.PrimaryKey
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
import java.time.Instant

@Entity(
tableName = FEED_LAST_UPDATED_TABLE,
Expand All @@ -26,7 +26,7 @@ data class FeedLastUpdatedEntity(
var subscriptionId: Long,

@ColumnInfo(name = LAST_UPDATED)
var lastUpdated: OffsetDateTime? = null
var lastUpdated: Instant? = null
) {
companion object {
const val FEED_LAST_UPDATED_TABLE = "feed_last_updated"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,24 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.time.OffsetDateTime
import java.time.Instant

@Entity(
tableName = SearchHistoryEntry.TABLE_NAME,
indices = [Index(value = [SearchHistoryEntry.SEARCH])]
)
data class SearchHistoryEntry(
@field:ColumnInfo(name = CREATION_DATE) var creationDate: OffsetDateTime?,
@field:ColumnInfo(
name = SERVICE_ID
) var serviceId: Int,
@field:ColumnInfo(name = SEARCH) var search: String?
@ColumnInfo(name = CREATION_DATE) var creationInstant: Instant?,
@ColumnInfo(name = SERVICE_ID) var serviceId: Int,
@ColumnInfo(name = SEARCH) var search: String?
) {
@ColumnInfo(name = ID)
@PrimaryKey(autoGenerate = true)
var id: Long = 0

@Ignore
fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean {
return (
serviceId == otherEntry.serviceId &&
search == otherEntry.search
)
return serviceId == otherEntry.serviceId && search == otherEntry.search
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import org.schabi.newpipe.database.stream.model.StreamEntity;

import java.time.OffsetDateTime;
import java.time.Instant;

import static androidx.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
Expand Down Expand Up @@ -36,21 +36,21 @@ public class StreamHistoryEntity {

@NonNull
@ColumnInfo(name = STREAM_ACCESS_DATE)
private OffsetDateTime accessDate;
private Instant accessInstant;

@ColumnInfo(name = STREAM_REPEAT_COUNT)
private long repeatCount;

/**
* @param streamUid the stream id this history item will refer to
* @param accessDate the last time the stream was accessed
* @param accessInstant the last time the stream was accessed
* @param repeatCount the total number of views this stream received
*/
public StreamHistoryEntity(final long streamUid,
@NonNull final OffsetDateTime accessDate,
@NonNull final Instant accessInstant,
final long repeatCount) {
this.streamUid = streamUid;
this.accessDate = accessDate;
this.accessInstant = accessInstant;
this.repeatCount = repeatCount;
}

Expand All @@ -63,12 +63,12 @@ public void setStreamUid(final long streamUid) {
}

@NonNull
public OffsetDateTime getAccessDate() {
return accessDate;
public Instant getAccessInstant() {
return accessInstant;
}

public void setAccessDate(@NonNull final OffsetDateTime accessDate) {
this.accessDate = accessDate;
public void setAccessInstant(@NonNull final Instant accessInstant) {
this.accessInstant = accessInstant;
}

public long getRepeatCount() {
Expand Down
Loading