WIP Store/testing

This commit is contained in:
Kelvin 2023-11-17 22:17:49 +01:00
commit 99c06c516f
15 changed files with 439 additions and 29 deletions

View file

@ -39,7 +39,7 @@ protobuf {
android { android {
namespace 'com.futo.platformplayer' namespace 'com.futo.platformplayer'
compileSdk 33 compileSdk 34
flavorDimensions "buildType" flavorDimensions "buildType"
productFlavors { productFlavors {
stable { stable {

View file

@ -1,24 +1,18 @@
package com.futo.platformplayer.states package com.futo.platformplayer.states
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.AudioManager import android.media.AudioManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
import android.net.Uri import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -28,10 +22,9 @@ import com.futo.platformplayer.R
import com.futo.platformplayer.activities.CaptchaActivity import com.futo.platformplayer.activities.CaptchaActivity
import com.futo.platformplayer.activities.IWithResultLauncher import com.futo.platformplayer.activities.IWithResultLauncher
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.DevJSClient
import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
import com.futo.platformplayer.background.BackgroundWorker import com.futo.platformplayer.background.BackgroundWorker
import com.futo.platformplayer.cache.ChannelContentCache import com.futo.platformplayer.cache.ChannelContentCache
import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.casting.StateCasting
@ -43,20 +36,20 @@ import com.futo.platformplayer.logging.AndroidLogConsumer
import com.futo.platformplayer.logging.FileLogConsumer import com.futo.platformplayer.logging.FileLogConsumer
import com.futo.platformplayer.logging.LogLevel import com.futo.platformplayer.logging.LogLevel
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.receivers.AudioNoisyReceiver import com.futo.platformplayer.receivers.AudioNoisyReceiver
import com.futo.platformplayer.services.DownloadService import com.futo.platformplayer.services.DownloadService
import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.db.types.DBHistory
import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ManagedStore
import com.stripe.android.core.utils.encodeToJson
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import kotlin.time.measureTime
/*** /***
* This class contains global context for unconventional cases where obtaining context is hard. * This class contains global context for unconventional cases where obtaining context is hard.
@ -545,7 +538,73 @@ class StateApp {
StateAnnouncement.instance.registerDidYouKnow(); StateAnnouncement.instance.registerDidYouKnow();
Logger.i(TAG, "MainApp Started: Finished"); Logger.i(TAG, "MainApp Started: Finished");
if(true) {
Logger.i(TAG, "TEST:--------(200)---------");
testHistoryDB(200);
Logger.i(TAG, "TEST:--------(1000)---------");
testHistoryDB(1000);
Logger.i(TAG, "TEST:--------(2000)---------");
testHistoryDB(2000);
Logger.i(TAG, "TEST:--------(4000)---------");
testHistoryDB(4000);
Logger.i(TAG, "TEST:--------(6000)---------");
testHistoryDB(6000);
} }
}
fun testHistoryDB(count: Int) {
Logger.i(TAG, "TEST: Starting tests");
StatePlaylists.instance._historyDBStore.deleteAll();
val testHistoryItem = StatePlaylists.instance.getHistory().first();
val testItemJson = StatePlaylists.instance.getHistory().first().video.toJson();
val now = OffsetDateTime.now();
val testSet = (0..count).map { HistoryVideo(Json.decodeFromString<SerializedPlatformVideo>(testItemJson.replace(testHistoryItem.video.url, UUID.randomUUID().toString())), it.toLong(), now.minusHours(it.toLong())) }
Logger.i(TAG, "TEST: Inserting (${testSet.size})");
val insertMS = measureTimeMillis {
for(item in testSet)
StatePlaylists.instance._historyDBStore.insert(item);
};
Logger.i(TAG, "TEST: Inserting in ${insertMS}ms");
var fetched: List<DBHistory.Index>? = null;
val fetchMS = measureTimeMillis {
fetched = StatePlaylists.instance._historyDBStore.getAll();
Logger.i(TAG, "TEST: Fetched: ${fetched?.size}");
};
Logger.i(TAG, "TEST: Fetch speed ${fetchMS}MS");
val deserializeMS = measureTimeMillis {
val deserialized = StatePlaylists.instance._historyDBStore.convertObjects(fetched!!);
Logger.i(TAG, "TEST: Deserialized: ${deserialized.size}");
};
Logger.i(TAG, "TEST: Deserialize speed ${deserializeMS}MS");
var fetchedIndex: List<DBHistory.Index>? = null;
val fetchIndexMS = measureTimeMillis {
fetchedIndex = StatePlaylists.instance._historyDBStore.getAllIndexes();
Logger.i(TAG, "TEST: Fetched Index: ${fetchedIndex!!.size}");
};
Logger.i(TAG, "TEST: Fetched Index speed ${fetchIndexMS}ms");
val fetchFromIndex = measureTimeMillis {
for(preItem in testSet) {
val item = StatePlaylists.instance.historyIndex[preItem.video.url];
if(item == null)
throw IllegalStateException("Missing item [${preItem.video.url}]");
if(item.url != preItem.video.url)
throw IllegalStateException("Mismatch item [${preItem.video.url}]");
}
};
Logger.i(TAG, "TEST: Index Lookup speed ${fetchFromIndex}ms");
val page1 = StatePlaylists.instance._historyDBStore.getPage(0, 20);
val page2 = StatePlaylists.instance._historyDBStore.getPage(1, 20);
val page3 = StatePlaylists.instance._historyDBStore.getPage(2, 20);
}
fun mainAppStartedWithExternalFiles(context: Context) { fun mainAppStartedWithExternalFiles(context: Context) {
if(!Settings.instance.didFirstStart) { if(!Settings.instance.didFirstStart) {
if(StateBackup.hasAutomaticBackup()) { if(StateBackup.hasAutomaticBackup()) {

View file

@ -3,6 +3,7 @@ package com.futo.platformplayer.states
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.sqlite.db.SimpleSQLiteQuery
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
@ -19,6 +20,8 @@ import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.db.ManagedDBStore
import com.futo.platformplayer.stores.db.types.DBHistory
import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ManagedStore
import com.futo.platformplayer.stores.v2.ReconstructStore import com.futo.platformplayer.stores.v2.ReconstructStore
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@ -26,6 +29,8 @@ import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
/*** /***
* Used to maintain playlists * Used to maintain playlists
@ -50,6 +55,11 @@ class StatePlaylists {
.withRestore(PlaylistBackup()) .withRestore(PlaylistBackup())
.load(); .load();
val historyIndex: ConcurrentMap<Any, DBHistory.Index> = ConcurrentHashMap();
val _historyDBStore = ManagedDBStore.create("history", DBHistory.Descriptor())
.withIndex({ it.url }, historyIndex)
.load();
val playlistShareDir = FragmentedStorage.getOrCreateDirectory("shares"); val playlistShareDir = FragmentedStorage.getOrCreateDirectory("shares");
var onHistoricVideoChanged = Event2<IPlatformVideo, Long>(); var onHistoricVideoChanged = Event2<IPlatformVideo, Long>();

View file

@ -2,6 +2,8 @@ package com.futo.platformplayer.stores
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.stores.db.ManagedDBIndex
import com.futo.platformplayer.stores.db.ManagedDBStore
import com.futo.platformplayer.stores.v2.JsonStoreSerializer import com.futo.platformplayer.stores.v2.JsonStoreSerializer
import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ManagedStore
import com.futo.platformplayer.stores.v2.StoreSerializer import com.futo.platformplayer.stores.v2.StoreSerializer

View file

@ -0,0 +1,27 @@
package com.futo.platformplayer.stores.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
/*
@Dao
class ManagedDBContext<T, I: ManagedDBIndex<T>> {
fun get(id: Int): I;
fun gets(vararg id: Int): List<I>;
fun getAll(): List<I>;
@Insert
fun insert(index: I);
@Insert
fun insertAll(vararg indexes: I)
@Update
fun update(index: I);
@Delete
fun delete(index: I);
}*/

View file

@ -0,0 +1,11 @@
package com.futo.platformplayer.stores.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Update
@Dao
interface ManagedDBContextPaged<T, I: ManagedDBIndex<T>> {
fun getPaged(page: Int, pageSize: Int): List<I>;
}

View file

@ -0,0 +1,33 @@
package com.futo.platformplayer.stores.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
@Dao
interface ManagedDBDAOBase<T, I: ManagedDBIndex<T>> {
@RawQuery
fun get(query: SupportSQLiteQuery): I;
@RawQuery
fun getMultiple(query: SupportSQLiteQuery): List<I>;
@RawQuery
fun action(query: SupportSQLiteQuery): Int
@Insert
fun insert(index: I): Long;
@Insert
fun insertAll(vararg indexes: I)
@Update
fun update(index: I);
@Delete
fun delete(index: I);
}

View file

@ -0,0 +1,7 @@
package com.futo.platformplayer.stores.db
import androidx.room.RoomDatabase
abstract class ManagedDBDatabase<T, I: ManagedDBIndex<T>, D: ManagedDBDAOBase<T, I>>: RoomDatabase() {
abstract fun base(): D;
}

View file

@ -0,0 +1,16 @@
package com.futo.platformplayer.stores.db
import androidx.sqlite.db.SimpleSQLiteQuery
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.stores.db.types.DBHistory
abstract class ManagedDBDescriptor<T, I: ManagedDBIndex<T>, D: ManagedDBDatabase<T, I, DA>, DA: ManagedDBDAOBase<T, I>> {
abstract fun dbClass(): Class<D>;
abstract fun create(obj: T): I;
open val ordered: String? = null;
open fun sqlIndexOnly(tableName: String): SimpleSQLiteQuery? = null;
open fun sqlPage(tableName: String, page: Int, length: Int): SimpleSQLiteQuery? = null;
}

View file

@ -1,8 +1,14 @@
package com.futo.platformplayer.stores.db package com.futo.platformplayer.stores.db
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.futo.platformplayer.api.media.Serializer
open class ManagedDBIndex( interface ManagedDBIndex<T> {
@PrimaryKey(true) var id: Long?
val id: Int? = null var serialized: ByteArray?
)
@get:Ignore
var obj: T?;
}

View file

@ -0,0 +1,11 @@
package com.futo.platformplayer.stores.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Update
@Dao
interface ManagedDBIndexOnly<T, I: ManagedDBIndex<T>> {
fun getIndex(): List<I>;
}

View file

@ -1,28 +1,40 @@
package com.futo.platformplayer.stores.db package com.futo.platformplayer.stores.db
import androidx.room.Room
import androidx.sqlite.db.SimpleSQLiteQuery
import com.futo.platformplayer.assume import com.futo.platformplayer.assume
import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.stores.v2.ReconstructStore import com.futo.platformplayer.stores.v2.JsonStoreSerializer
import com.futo.platformplayer.stores.v2.StoreSerializer import com.futo.platformplayer.stores.v2.StoreSerializer
import java.io.File import kotlinx.serialization.KSerializer
import java.util.concurrent.ConcurrentMap
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
class ManagedDBStore<I, T> { class ManagedDBStore<I: ManagedDBIndex<T>, T, D: ManagedDBDatabase<T, I, DA>, DA: ManagedDBDAOBase<T, I>> {
private val _class: KType; private val _class: KType;
private val _name: String; private val _name: String;
private val _serializer: StoreSerializer<T>; private val _serializer: StoreSerializer<T>;
private var _db: ManagedDBDatabase<T, I, *>? = null;
private var _dbDaoBase: ManagedDBDAOBase<T, I>? = null;
val dbDaoBase: ManagedDBDAOBase<T, I> get() = _dbDaoBase ?: throw IllegalStateException("Not initialized db [${name}]");
private var _isLoaded = false; private var _dbDescriptor: ManagedDBDescriptor<T, I, D, DA>;
private var _withUnique: ((I) -> Any)? = null; private val _sqlAll: SimpleSQLiteQuery;
private val _sqlDeleteAll: SimpleSQLiteQuery;
private var _sqlIndexed: SimpleSQLiteQuery? = null;
val className: String? get() = _class.classifier?.assume<KClass<*>>()?.simpleName; val className: String? get() = _class.classifier?.assume<KClass<*>>()?.simpleName;
val name: String; val name: String;
constructor(name: String, clazz: KType, serializer: StoreSerializer<T>, niceName: String? = null) { private val _indexes: ArrayList<Pair<(I)->Any, ConcurrentMap<Any, I>>> = arrayListOf();
constructor(name: String, descriptor: ManagedDBDescriptor<T, I, D, DA>, clazz: KType, serializer: StoreSerializer<T>, niceName: String? = null) {
_dbDescriptor = descriptor;
_name = name; _name = name;
this.name = niceName ?: name.let { this.name = niceName ?: name.let {
if(it.isNotEmpty()) if(it.isNotEmpty())
@ -31,11 +43,119 @@ class ManagedDBStore<I, T> {
}; };
_serializer = serializer; _serializer = serializer;
_class = clazz; _class = clazz;
_sqlAll = SimpleSQLiteQuery("SELECT * FROM $_name" + if(descriptor.ordered.isNullOrEmpty()) "" else " ${descriptor.ordered}");
_sqlDeleteAll = SimpleSQLiteQuery("DELETE FROM ${_name}");
_sqlIndexed = descriptor.sqlIndexOnly(_name);
} }
fun load() { fun withIndex(keySelector: (I)->Any, indexContainer: ConcurrentMap<Any, I>): ManagedDBStore<I, T, D, DA> {
throw NotImplementedError(); if(_sqlIndexed == null)
_isLoaded = true; throw IllegalStateException("Can only create indexes if sqlIndexOnly is implemented");
_indexes.add(Pair(keySelector, indexContainer));
return this;
} }
fun load(): ManagedDBStore<I, T, D, DA> {
_db = Room.databaseBuilder(StateApp.instance.context, _dbDescriptor.dbClass(), _name)
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
_dbDaoBase = _db!!.base() as ManagedDBDAOBase<T, I>;
if(_indexes.any()) {
val allItems = _dbDaoBase!!.getMultiple(_sqlIndexed!!);
for(index in _indexes)
index.second.putAll(allItems.associateBy(index.first));
}
return this;
}
fun insert(obj: T) {
val newIndex = _dbDescriptor.create(obj);
newIndex.serialized = serialize(obj);
newIndex.id = dbDaoBase.insert(newIndex);
newIndex.serialized = null;
if(!_indexes.isEmpty()) {
for (index in _indexes) {
val key = index.first(newIndex);
index.second.put(key, newIndex);
}
}
}
fun update(id: Long, obj: T) {
val newIndex = _dbDescriptor.create(obj);
newIndex.id = id;
newIndex.serialized = serialize(obj);
dbDaoBase.update(newIndex);
newIndex.serialized = null;
if(!_indexes.isEmpty()) {
for (index in _indexes) {
val key = index.first(newIndex);
index.second.put(key, newIndex);
}
}
}
fun getAllIndexes(): List<I> {
if(_sqlIndexed == null)
throw IllegalStateException("Can only create indexes if sqlIndexOnly is implemented");
return dbDaoBase.getMultiple(_sqlIndexed!!);
}
fun getAllObjects(): List<T> = convertObjects(getAll());
fun getAll(): List<I> {
return dbDaoBase.getMultiple(_sqlAll);
}
fun getObject(id: Long) = convertObject(get(id));
fun get(id: Long): I {
return dbDaoBase.get(SimpleSQLiteQuery("SELECT * FROM $_name WHERE id = ?", arrayOf(id)));
}
fun getAllObjects(vararg id: Long): List<T> = convertObjects(getAll(*id));
fun getAll(vararg id: Long): List<I> {
return dbDaoBase.getMultiple(SimpleSQLiteQuery("SELECT * FROM $_name WHERE id IN (?)", arrayOf(id)));
}
fun getPageObjects(page: Int, length: Int): List<T> = convertObjects(getPage(page, length));
fun getPage(page: Int, length: Int): List<I> {
val query = _dbDescriptor.sqlPage(_name, page, length) ?: throw IllegalStateException("Paged db not setup for ${_name}");
return dbDaoBase.getMultiple(query);
}
fun delete(item: I) {
dbDaoBase.delete(item);
for(index in _indexes)
index.second.remove(index.first(item));
}
fun deleteAll() {
dbDaoBase.action(_sqlDeleteAll);
for(index in _indexes)
index.second.clear();
}
fun convertObject(index: ManagedDBIndex<T>): T? {
return index.serialized?.let {
_serializer.deserialize(_class, it);
};
}
fun convertObjects(indexes: List<ManagedDBIndex<T>>): List<T> {
return indexes.mapNotNull { convertObject(it) };
}
fun serialize(obj: T): ByteArray {
return _serializer.serialize(_class, obj);
}
companion object {
inline fun <reified T, I: ManagedDBIndex<T>, D: ManagedDBDatabase<T, I, DA>, DA: ManagedDBDAOBase<T, I>> create(name: String, descriptor: ManagedDBDescriptor<T, I, D, DA>, serializer: KSerializer<T>? = null)
= ManagedDBStore(name, descriptor, kotlin.reflect.typeOf<T>(), JsonStoreSerializer.create(serializer));
}
} }

View file

@ -0,0 +1,36 @@
package com.futo.platformplayer.stores.db.types
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.stores.db.ManagedDBIndex
class DBChannelCache {
companion object {
const val TABLE_NAME = "channelCache";
}
class Index: ManagedDBIndex<SerializedPlatformContent> {
@PrimaryKey(true)
override var id: Long? = null;
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
override var serialized: ByteArray? = null;
@Ignore
override var obj: SerializedPlatformContent? = null;
var feedType: String? = null;
var channelUrl: String? = null;
constructor() {}
constructor(sCache: SerializedPlatformContent) {
id = null;
serialized = null;
obj = sCache;
channelUrl = sCache.author.url;
}
}
}

View file

@ -0,0 +1,69 @@
package com.futo.platformplayer.stores.db.types
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.sqlite.db.SimpleSQLiteQuery
import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.stores.db.ManagedDBDAOBase
import com.futo.platformplayer.stores.db.ManagedDBDatabase
import com.futo.platformplayer.stores.db.ManagedDBDescriptor
import com.futo.platformplayer.stores.db.ManagedDBIndex
import com.futo.platformplayer.stores.db.ManagedDBStore
import kotlin.reflect.KType
class DBHistory {
companion object {
const val TABLE_NAME = "history";
}
@Dao
interface DBDAO: ManagedDBDAOBase<HistoryVideo, Index> {}
@Database(entities = [Index::class], version = 2)
abstract class DB: ManagedDBDatabase<HistoryVideo, Index, DBDAO>() {
abstract override fun base(): DBDAO;
}
class Descriptor: ManagedDBDescriptor<HistoryVideo, Index, DB, DBDAO>() {
override fun create(obj: HistoryVideo): Index = Index(obj);
override fun dbClass(): Class<DB> = DB::class.java;
//Optional
override fun sqlIndexOnly(tableName: String): SimpleSQLiteQuery = SimpleSQLiteQuery("SELECT id, url, position, date FROM $TABLE_NAME");
override fun sqlPage(tableName: String, page: Int, length: Int): SimpleSQLiteQuery = SimpleSQLiteQuery("SELECT * FROM $TABLE_NAME ORDER BY date DESC, id DESC LIMIT ? OFFSET ?", arrayOf(length, page * length));
}
@Entity(TABLE_NAME)
class Index: ManagedDBIndex<HistoryVideo> {
@PrimaryKey(true)
override var id: Long? = null;
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
override var serialized: ByteArray? = null;
@Ignore
override var obj: HistoryVideo? = null;
var url: String;
var position: Long;
var date: Long;
constructor() {
url = "";
position = 0;
date = 0;
}
constructor(historyVideo: HistoryVideo) {
id = null;
serialized = null;
url = historyVideo.video.url;
position = historyVideo.position;
date = historyVideo.date.toEpochSecond();
obj = historyVideo;
}
}
}

View file

@ -92,7 +92,11 @@ class GestureControlView : LinearLayout {
override fun onDown(p0: MotionEvent): Boolean { return false; } override fun onDown(p0: MotionEvent): Boolean { return false; }
override fun onShowPress(p0: MotionEvent) = Unit; override fun onShowPress(p0: MotionEvent) = Unit;
override fun onSingleTapUp(p0: MotionEvent): Boolean { return false; } override fun onSingleTapUp(p0: MotionEvent): Boolean { return false; }
override fun onScroll(p0: MotionEvent, p1: MotionEvent, distanceX: Float, distanceY: Float): Boolean { override fun onFling(p0: MotionEvent?, p1: MotionEvent, p2: Float, p3: Float): Boolean { return false; }
override fun onScroll(p0: MotionEvent?, p1: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if(p0 == null)
return false;
if (_isFullScreen && _adjustingBrightness) { if (_isFullScreen && _adjustingBrightness) {
val adjustAmount = (distanceY * 2) / height; val adjustAmount = (distanceY * 2) / height;
_brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
@ -132,8 +136,7 @@ class GestureControlView : LinearLayout {
return true; return true;
} }
override fun onLongPress(p0: MotionEvent) = Unit; override fun onLongPress(p0: MotionEvent) = Unit
override fun onFling(p0: MotionEvent, p1: MotionEvent, p2: Float, p3: Float): Boolean { return false; }
}); });
gestureController.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { gestureController.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener {