more stuff

This commit is contained in:
deniscerri 2025-02-15 20:09:05 +01:00
parent 29fff7e24c
commit 37aec538cc
No known key found for this signature in database
GPG key ID: 95C43D517D830350
37 changed files with 1400 additions and 172 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 21,
"identityHash": "3be2d00eeed0d077b84c352d10f6628f",
"identityHash": "60367016912c1948d70c556647e93114",
"entities": [
{
"tableName": "results",
@ -384,7 +384,7 @@
},
{
"tableName": "commandTemplates",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0, `preferredCommandTemplate` INTEGER NOT NULL DEFAULT 0, `urlRegex` TEXT NOT NULL DEFAULT '[]')",
"fields": [
{
"fieldPath": "id",
@ -431,6 +431,20 @@
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "preferredCommandTemplate",
"columnName": "preferredCommandTemplate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "urlRegex",
"columnName": "urlRegex",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
}
],
"primaryKey": {
@ -747,7 +761,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3be2d00eeed0d077b84c352d10f6628f')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '60367016912c1948d70c556647e93114')"
]
}
}

View file

@ -0,0 +1,766 @@
{
"formatVersion": 1,
"database": {
"version": 22,
"identityHash": "a7ed801eeaef776e0fb063303b9892df",
"entities": [
{
"tableName": "results",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `creationTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formats",
"columnName": "formats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "urls",
"columnName": "urls",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "chapters",
"columnName": "chapters",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "playlistURL",
"columnName": "playlistURL",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
},
{
"fieldPath": "playlistIndex",
"columnName": "playlistIndex",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "creationTime",
"columnName": "creationTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `filesize` INTEGER NOT NULL DEFAULT 0, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "filesize",
"columnName": "filesize",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "downloadId",
"columnName": "downloadId",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `incognito` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "container",
"columnName": "container",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Default'"
},
{
"fieldPath": "downloadSections",
"columnName": "downloadSections",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "allFormats",
"columnName": "allFormats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadSize",
"columnName": "downloadSize",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "audioPreferences",
"columnName": "audioPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "videoPreferences",
"columnName": "videoPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "extraCommands",
"columnName": "extraCommands",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "customFileNameTemplate",
"columnName": "customFileNameTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "SaveThumb",
"columnName": "SaveThumb",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Queued'"
},
{
"fieldPath": "downloadStartTime",
"columnName": "downloadStartTime",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "logID",
"columnName": "logID",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "playlistURL",
"columnName": "playlistURL",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
},
{
"fieldPath": "playlistIndex",
"columnName": "playlistIndex",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "incognito",
"columnName": "incognito",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "commandTemplates",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0, `preferredCommandTemplate` INTEGER NOT NULL DEFAULT 0, `urlRegex` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "useAsExtraCommandAudio",
"columnName": "useAsExtraCommandAudio",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandVideo",
"columnName": "useAsExtraCommandVideo",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandDataFetching",
"columnName": "useAsExtraCommandDataFetching",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "preferredCommandTemplate",
"columnName": "preferredCommandTemplate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "urlRegex",
"columnName": "urlRegex",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "templateShortcuts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cookies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "logs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadType",
"columnName": "downloadType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadTime",
"columnName": "downloadTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "terminalDownloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `syncWithSource` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadItemTemplate",
"columnName": "downloadItemTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyNr",
"columnName": "everyNr",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "everyCategory",
"columnName": "everyCategory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyTime",
"columnName": "everyTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "weeklyConfig",
"columnName": "weeklyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "monthlyConfig",
"columnName": "monthlyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "startsTime",
"columnName": "startsTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endsDate",
"columnName": "endsDate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "endsAfterCount",
"columnName": "endsAfterCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "runCount",
"columnName": "runCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "getOnlyNewUploads",
"columnName": "getOnlyNewUploads",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "retryMissingDownloads",
"columnName": "retryMissingDownloads",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ignoredLinks",
"columnName": "ignoredLinks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "alreadyProcessedLinks",
"columnName": "alreadyProcessedLinks",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "syncWithSource",
"columnName": "syncWithSource",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a7ed801eeaef776e0fb063303b9892df')"
]
}
}

View file

@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
TerminalItem::class,
ObserveSourcesItem::class
],
version = 21,
version = 22,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -63,6 +63,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
AutoMigration (from = 18, to = 19),
AutoMigration (from = 19, to = 20),
//AutoMigration (from = 20, to = 21) MANUALLY HANDLED
//AutoMigration(from = 21, to = 22) MANUALLY HANDLED
]
)
abstract class DBManager : RoomDatabase(){

View file

@ -42,6 +42,15 @@ object Migrations {
database.execSQL("UPDATE history SET filesize = ${parsed.filesize} WHERE id = $id")
}
}
},
//add preferred command template and url regexes
Migration(21, 22) { database ->
// Add the `preferredCommandTemplate` column as INTEGER (since SQLite does not support BOOLEAN)
database.execSQL("ALTER TABLE commandTemplates ADD COLUMN preferredCommandTemplate INTEGER NOT NULL DEFAULT 0")
// Add `urlRegex` as a JSON string (since lists are not supported in SQLite)
database.execSQL("ALTER TABLE commandTemplates ADD COLUMN urlRegex TEXT NOT NULL DEFAULT '[]'")
}
)

View file

@ -38,11 +38,14 @@ interface CommandTemplateDao {
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1")
fun getAllTemplatesAsExtraCommands() : List<String>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1")
fun getAllTemplatesAsExtraCommandsForAudio() : List<String>
@Query("SELECT * FROM commandTemplates WHERE useAsExtraCommandDataFetching is 1")
fun getAllTemplatesAsDataFetchingExtraCommands() : List<CommandTemplate>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1")
fun getAllTemplatesAsExtraCommandsForVideo() : List<String>
@Query("SELECT * FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1")
fun getAllTemplatesAsExtraCommandsForAudio() : List<CommandTemplate>
@Query("SELECT * FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1")
fun getAllTemplatesAsExtraCommandsForVideo() : List<CommandTemplate>
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>
@ -65,6 +68,9 @@ interface CommandTemplateDao {
@Query("SELECT * FROM commandTemplates ORDER BY id DESC LIMIT 1")
fun getFirst() : CommandTemplate?
@Query("SELECT * FROM commandTemplates WHERE preferredCommandTemplate is 1")
fun getPreferredCommandTemplates() : List<CommandTemplate>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: CommandTemplate)

View file

@ -19,5 +19,8 @@ data class CommandTemplate(
@ColumnInfo(defaultValue = "1")
var useAsExtraCommandVideo: Boolean,
@ColumnInfo(defaultValue = "0")
var useAsExtraCommandDataFetching: Boolean
var useAsExtraCommandDataFetching: Boolean,
@ColumnInfo(defaultValue = "0")
var preferredCommandTemplate : Boolean = false,
var urlRegex: MutableList<String> = mutableListOf()
)

View file

@ -4,8 +4,9 @@ data class YoutubePlayerClientItem(
var playerClient: String,
var poTokens: MutableList<YoutubePoTokenItem>,
var enabled: Boolean = true,
var useOnlyPoToken: Boolean = false
)
var useOnlyPoToken: Boolean = false,
var urlRegex: MutableList<String> = mutableListOf()
)
data class YoutubePoTokenItem(
var context: String,

View file

@ -3,6 +3,7 @@ package com.deniscerri.ytdl.database.repository
import android.content.Context
import android.util.Patterns
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.database.dao.CommandTemplateDao
import com.deniscerri.ytdl.database.dao.ResultDao
import com.deniscerri.ytdl.database.models.ChapterItem
import com.deniscerri.ytdl.database.models.DownloadItem
@ -21,7 +22,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
val YTDLNIS_SEARCH = "YTDLNIS_SEARCH"
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableStateFlow(-1)
@ -31,7 +32,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
}
private val youtubeApiUtil = YoutubeApiUtil(context)
private val ytdlpUtil = YTDLPUtil(context)
private val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
private var newPipeUtil = NewPipeUtil(context)
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

View file

@ -13,6 +13,7 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.media3.common.Player.Command
import androidx.paging.PagingData
import androidx.paging.filter
import androidx.paging.map
@ -76,7 +77,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
private val commandTemplateDao: CommandTemplateDao
private val formatUtil = FormatUtil(application)
private val notificationUtil = NotificationUtil(application)
private val ytdlpUtil = YTDLPUtil(application)
private val ytdlpUtil: YTDLPUtil
private val resources : Resources
val allDownloads : Flow<PagingData<DownloadItem>>
@ -115,8 +116,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
mutableListOf()
)
private var extraCommandsForAudio: String = ""
private var extraCommandsForVideo: String = ""
private var extraCommandsForAudio: List<CommandTemplate> = listOf()
private var extraCommandsForVideo: List<CommandTemplate> = listOf()
private val dao: DownloadDao
private val historyRepository: HistoryRepository
@ -138,11 +139,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
init {
dbManager = DBManager.getInstance(application)
dao = dbManager.downloadDao
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
repository = DownloadRepository(dao)
historyRepository = HistoryRepository(dbManager.historyDao)
resultRepository = ResultRepository(dbManager.resultDao, application)
resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
ytdlpUtil = YTDLPUtil(application, commandTemplateDao)
activeDownloadsCount = repository.activeDownloadsCount
activePausedDownloadsCount = repository.activePausedDownloadsCount
@ -164,8 +166,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
erroredDownloads = repository.erroredDownloads.flow
viewModelScope.launch(Dispatchers.IO){
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommandsForAudio = commandTemplateDao.getAllTemplatesAsExtraCommandsForAudio().joinToString(" ")
extraCommandsForVideo = commandTemplateDao.getAllTemplatesAsExtraCommandsForVideo().joinToString(" ")
extraCommandsForAudio = commandTemplateDao.getAllTemplatesAsExtraCommandsForAudio()
extraCommandsForVideo = commandTemplateDao.getAllTemplatesAsExtraCommandsForVideo()
}
}
@ -320,8 +322,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val extraCommands = when(type){
Type.audio -> extraCommandsForAudio
Type.video -> extraCommandsForVideo
else -> ""
}
else -> listOf()
}.filter {
it.urlRegex.isEmpty() || it.urlRegex.any { u ->
Regex(u).containsMatchIn(resultItem.url)
}
}.joinToString(" ") { it.content }
return DownloadItem(0,
resultItem.url,
@ -330,7 +336,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
resultItem.thumb,
resultItem.duration,
type,
getFormat(resultItem.formats, type),
getFormat(resultItem.formats, type, resultItem.url),
container!!,
"",
resultItem.formats,
@ -412,7 +418,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
fun switchDownloadType(list: List<DownloadItem>, type: Type) : List<DownloadItem>{
list.forEach {
val format = getFormat(it.allFormats, type)
val format = getFormat(it.allFormats, type, it.url)
it.format = format
var updatedDownloadPath = ""
@ -475,11 +481,15 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
val extraCommands = when(historyItem.type){
val extraCommands = when (historyItem.type) {
Type.audio -> extraCommandsForAudio
Type.video -> extraCommandsForVideo
else -> ""
}
else -> listOf()
}.filter {
it.urlRegex.isEmpty() || it.urlRegex.any { u ->
Regex(u).containsMatchIn(historyItem.url)
}
}.joinToString(" ") { it.content }
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo)
@ -521,7 +531,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
fun getFormat(formats: List<Format>, type: Type) : Format {
fun getFormat(formats: List<Format>, type: Type, url: String? = null) : Format {
when(type) {
Type.audio -> {
return cloneFormat (
@ -550,19 +560,27 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
)
}
else -> {
val preferredTemplateContent =
sharedPreferences.getString("preferred_command_template", "")!!.ifEmpty {
sharedPreferences.getString("lastCommandTemplateUsed", "")!!
}
val c = if (preferredTemplateContent.isNotBlank()) {
commandTemplateDao.getTemplateByContent(preferredTemplateContent)
}else {
commandTemplateDao.getFirst()
val preferredCommandTemplates = commandTemplateDao.getPreferredCommandTemplates()
var template : CommandTemplate? = null
if (url != null) {
template = preferredCommandTemplates.firstOrNull { it.urlRegex.isEmpty() || it.urlRegex.any { u ->
Regex(u).containsMatchIn(url)
} }
}
if (template == null) {
template = commandTemplateDao.getFirst()
}
return generateCommandFormat(
c ?: CommandTemplate(0, "", preferredTemplateContent, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false)
template ?: CommandTemplate(
0,
"",
sharedPreferences.getString("lastCommandTemplateUsed", "") ?: "",
useAsExtraCommand = false,
useAsExtraCommandAudio = false,
useAsExtraCommandVideo = false,
useAsExtraCommandDataFetching = false
)
)
}
}
@ -1128,7 +1146,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val item = repository.getItemByID(id)
item.allFormats.clear()
item.allFormats.addAll(list)
item.format = getFormat(list, item.type)
item.format = getFormat(list, item.type, item.url)
runCatching {
resultRepository.getAllByURL(item.url).forEach {
@ -1144,7 +1162,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
items.forEach { item ->
item.allFormats.clear()
item.allFormats.addAll(list)
item.format = getFormat(list, item.type)
item.format = getFormat(list, item.type, item.url)
repository.update(item)
}

View file

@ -71,8 +71,10 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
private val sharedPreferences: SharedPreferences
init {
dao = DBManager.getInstance(application).resultDao
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
val dbManager = DBManager.getInstance(application)
dao = dbManager.resultDao
val commandTemplateDao = dbManager.commandTemplateDao
repository = ResultRepository(dao, commandTemplateDao, getApplication<Application>().applicationContext)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults.asLiveData()

View file

@ -0,0 +1,91 @@
package com.deniscerri.ytdl.database.viewmodel
import android.app.Application
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Parcelable
import android.util.DisplayMetrics
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.filter
import androidx.paging.map
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.dao.CommandTemplateDao
import com.deniscerri.ytdl.database.dao.DownloadDao
import com.deniscerri.ytdl.database.models.AudioPreferences
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.models.ResultItem
import com.deniscerri.ytdl.database.models.VideoPreferences
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple
import com.deniscerri.ytdl.util.Extensions.needsDataUpdating
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.FormatUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import java.io.File
import java.util.Locale
class YTDLPViewModel(private val application: Application) : AndroidViewModel(application) {
private val dbManager: DBManager
private val commandTemplateDao: CommandTemplateDao
private val ytdlpUtil: YTDLPUtil
init {
dbManager = DBManager.getInstance(application)
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
ytdlpUtil = YTDLPUtil(application, commandTemplateDao)
}
fun parseYTDLRequestString(item: DownloadItem) : String {
val req = ytdlpUtil.buildYoutubeDLRequest(item)
return ytdlpUtil.parseYTDLRequestString(req)
}
fun getVersion(channel: String) : String {
return ytdlpUtil.getVersion(application, channel)
}
}

View file

@ -76,8 +76,7 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
}
card.findViewById<TextView>(R.id.preferredTemplate).apply {
val preferred = sharedPreferences.getString("preferred_command_template", "")
isVisible = preferred == item.content
isVisible = item.preferredCommandTemplate
}
if (checkedItems.contains(item.id)) {
@ -170,7 +169,9 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
oldItem.useAsExtraCommand == newItem.useAsExtraCommand &&
oldItem.useAsExtraCommandAudio == newItem.useAsExtraCommandAudio &&
oldItem.useAsExtraCommandVideo == newItem.useAsExtraCommandVideo &&
oldItem.useAsExtraCommandDataFetching == newItem.useAsExtraCommandDataFetching
oldItem.useAsExtraCommandDataFetching == newItem.useAsExtraCommandDataFetching &&
oldItem.preferredCommandTemplate == newItem.preferredCommandTemplate &&
oldItem.urlRegex.joinToString("") == newItem.urlRegex.joinToString("")
}
}
}

View file

@ -115,11 +115,10 @@ class YoutubePlayerClientAdapter(onItemClickListener: OnItemClickListener, activ
}
override fun areContentsTheSame(oldItem: YoutubePlayerClientItem, newItem: YoutubePlayerClientItem): Boolean {
return oldItem.playerClient == newItem.playerClient && oldItem.enabled == newItem.enabled && oldItem.poTokens.joinToString(
"\n"
) { it.token } == newItem.poTokens.joinToString(
"\n"
) { it.token }
return oldItem.playerClient == newItem.playerClient &&
oldItem.enabled == newItem.enabled &&
oldItem.poTokens.joinToString("\n") { it.token } == newItem.poTokens.joinToString("\n") { it.token } &&
oldItem.urlRegex.joinToString("\n") == newItem.urlRegex.joinToString("\n")
}
}
}

View file

@ -19,6 +19,7 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
@ -31,15 +32,15 @@ import kotlinx.coroutines.withContext
class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val callback: ExtraCommandsListener? = null) : BottomSheetDialogFragment() {
private lateinit var ytdlpUtil: YTDLPUtil
private lateinit var sharedPreferences: SharedPreferences
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private lateinit var ytdlpViewModel: YTDLPViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ytdlpUtil = YTDLPUtil(requireActivity().applicationContext)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
}
override fun onCreateView(
@ -86,7 +87,7 @@ class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val
val currentText = view.findViewById<TextView>(R.id.currentText)
if (item != null){
val currentCommand = ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item))
val currentCommand = ytdlpViewModel.parseYTDLRequestString(item)
currentText?.text = currentCommand
}else{
view.findViewById<View>(R.id.current).visibility = View.GONE

View file

@ -549,15 +549,15 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
val url = url.editText?.text?.isNotBlank() == true
val category = ObserveSourcesRepository.EveryCategory.values()[categoryAdapter.getPosition(everyCat.text.toString())]
val everyNr = everyNr.editText?.text?.isNotBlank() == true
val everyNrBool = everyNr.editText!!.text.isNotBlank() && everyNr.editText!!.text.toString() != "0"
val everyTime = everyTime.editText?.text?.isNotBlank() == true
val every = when(category){
ObserveSourcesRepository.EveryCategory.HOUR -> {
everyNr
everyNrBool
}
else -> {
everyNr && everyTime
everyNrBool && everyTime
}
}

View file

@ -44,6 +44,7 @@ import com.deniscerri.ytdl.database.models.ResultItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.setFullScreen
@ -79,6 +80,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
private lateinit var videoView: PlayerView
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var ytdlpViewModel: YTDLPViewModel
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
private lateinit var queuedAdapter: GenericDownloadAdapter
@ -94,6 +96,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
notificationUtil = NotificationUtil(requireActivity())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
}
@ -446,6 +449,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide()
removeQueuedItem(itemID)

View file

@ -36,6 +36,7 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -60,6 +61,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences
private lateinit var adapter : GenericDownloadAdapter
@ -79,6 +81,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView
}
@ -174,6 +177,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet)
},

View file

@ -35,6 +35,7 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -58,6 +59,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var erroredRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences
private lateinit var noResults : RelativeLayout
@ -77,6 +79,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView
}
@ -174,6 +177,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet)

View file

@ -36,6 +36,7 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -61,6 +62,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var queuedRecyclerView : RecyclerView
private lateinit var adapter : QueuedDownloadAdapter
private lateinit var noResults : RelativeLayout
@ -80,6 +82,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
activity = getActivity()
notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
return fragmentView
}
@ -450,6 +453,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide()
removeItem(it.id)

View file

@ -34,6 +34,7 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
@ -57,6 +58,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var savedRecyclerView: RecyclerView
private lateinit var adapter: GenericDownloadAdapter
private lateinit var preferences: SharedPreferences
@ -76,6 +78,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView
}
@ -183,6 +186,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
removeItem(it, sheet)
},

View file

@ -35,6 +35,7 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdl.ui.adapter.ScheduledDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
@ -58,6 +59,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var ytdlpViewModel : YTDLPViewModel
private lateinit var scheduledRecyclerView : RecyclerView
private lateinit var preferences : SharedPreferences
private lateinit var adapter : ScheduledDownloadAdapter
@ -77,6 +79,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView
}
@ -179,6 +182,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
item,
requireActivity(),
DownloadRepository.Status.valueOf(item.status),
ytdlpViewModel,
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
sheet.hide()
removeItem(it, sheet)

View file

@ -29,15 +29,20 @@ import com.deniscerri.ytdl.database.viewmodel.LogViewModel
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.slider.Slider
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class DownloadLogFragment : Fragment() {
@ -48,6 +53,7 @@ class DownloadLogFragment : Fragment() {
private lateinit var mainActivity: MainActivity
private lateinit var logViewModel: LogViewModel
private lateinit var sharedPreferences: SharedPreferences
private var logID: Long? = null
private var autoScroll : Boolean = true
private var scrollDownBtn : MenuItem? = null
@ -95,8 +101,8 @@ class DownloadLogFragment : Fragment() {
.show()
}
val id = arguments?.getLong("logID")
if (id == null || id == 0L) {
logID = arguments?.getLong("logID")
if (logID == null || logID == 0L) {
mainActivity.onBackPressedDispatcher.onBackPressed()
}
@ -105,7 +111,7 @@ class DownloadLogFragment : Fragment() {
CoroutineScope(Dispatchers.IO).launch {
runCatching {
val logItem = logViewModel.getItemById(id!!)
val logItem = logViewModel.getItemById(logID!!)
withContext(Dispatchers.Main){
topAppBar.title = logItem.title
}
@ -202,7 +208,7 @@ class DownloadLogFragment : Fragment() {
}
}
logViewModel.getLogFlowByID(id!!).observe(viewLifecycleOwner){logItem ->
logViewModel.getLogFlowByID(logID!!).observe(viewLifecycleOwner){logItem ->
kotlin.runCatching {
requireActivity().runOnUiThread{
if (logItem != null){
@ -227,6 +233,26 @@ class DownloadLogFragment : Fragment() {
scrollDownBtn?.isVisible = canVerticallyScroll
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
//dont remove
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewById<LinearProgressIndicator>(R.id.progress)
if (event.logItemID == logID) {
progressBar.isVisible = true
progressBar.setProgress(event.progress, true)
}
}
companion object {
private const val TAG = "DownloadLogActivity"
}

View file

@ -29,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceManager
@ -36,6 +37,8 @@ import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.UiUtil.showShortcutsSheet
import com.deniscerri.ytdl.util.UpdateUtil
@ -64,7 +67,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
private var updateUtil: UpdateUtil? = null
private var version: Preference? = null
private lateinit var preferences: SharedPreferences
private lateinit var ytdlpUtil: YTDLPUtil
private lateinit var ytdlpViewModel: YTDLPViewModel
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -74,7 +77,9 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
updateYTDL = findPreference("update_ytdl")
ytdlVersion = findPreference("ytdl-version")
ytdlSource = findPreference("ytdlp_source_label")
ytdlpUtil = YTDLPUtil(requireContext())
ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java]
ytdlSource?.apply {
summary = preferences.getString("ytdlp_source_label", "")
setOnPreferenceClickListener {
@ -141,7 +146,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
lifecycleScope.launch {
ytdlVersion!!.summary = getString(R.string.loading)
val version = withContext(Dispatchers.IO){
ytdlpUtil.getVersion(requireContext(), preferences.getString("ytdlp_source", "stable")!!)
ytdlpViewModel.getVersion(preferences.getString("ytdlp_source", "stable")!!)
}
preferences.edit().apply {
putString("ytdl-version", version)

View file

@ -5,7 +5,6 @@ import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@ -15,11 +14,9 @@ import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@ -35,20 +32,14 @@ import android.widget.PopupMenu
import android.widget.RadioButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DimenRes
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager
@ -56,7 +47,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.afollestad.materialdialogs.utils.MDUtil.textChanged
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.DownloadItem
@ -68,8 +58,8 @@ import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener
import com.deniscerri.ytdl.ui.more.WebViewActivity
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.toStringDuration
@ -91,21 +81,17 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -214,6 +200,8 @@ object UiUtil {
val extraCommandsDataFetchingSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandDataFetching)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
val urlRegex : TextInputLayout = bottomSheet.findViewById(R.id.url_regex)!!
val urlRegexChips : ChipGroup = bottomSheet.findViewById(R.id.urlRegexChipGroup)!!
extraCommandsDataFetchingSwitch.isVisible = sharedPreferences.getBoolean("enable_data_fetching_extra_commands", false)
@ -274,7 +262,7 @@ object UiUtil {
}
if (item != null){
preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "")
preferredCommandSwitch.isChecked = item.preferredCommandTemplate
extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching && extraCommandsDataFetchingSwitch.isVisible
extraCommandsSwitch.isChecked = item.useAsExtraCommand
@ -289,15 +277,40 @@ object UiUtil {
extraCommandsVideo.isVisible = false
extraCommandsVideo.isChecked = false
}
val canUseURLRegex = item.useAsExtraCommand || item.useAsExtraCommandDataFetching || item.preferredCommandTemplate
urlRegex.isVisible = canUseURLRegex
urlRegexChips.isVisible = canUseURLRegex
for(chip in item.urlRegex) {
val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip
tmp.text = chip
tmp.setOnClickListener {
urlRegexChips.removeView(tmp)
}
urlRegexChips.addView(tmp)
}
}
preferredCommandSwitch.setOnCheckedChangeListener { compoundButton, b ->
val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked
urlRegex.isVisible = canUseURLRegex
urlRegexChips.isVisible = canUseURLRegex
}
extraCommandsSwitch.setOnCheckedChangeListener { compoundButton, b ->
extraCommandsAudio.isVisible = extraCommandsSwitch.isChecked
extraCommandsAudio.isChecked = true
extraCommandsVideo.isVisible = extraCommandsSwitch.isChecked
extraCommandsVideo.isChecked = true
val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked
urlRegex.isVisible = canUseURLRegex
urlRegexChips.isVisible = canUseURLRegex
}
extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
@ -306,6 +319,28 @@ object UiUtil {
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
extraCommandsDataFetchingSwitch.setOnCheckedChangeListener { compoundButton, b ->
val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked
urlRegex.isVisible = canUseURLRegex
urlRegexChips.isVisible = canUseURLRegex
}
urlRegex.isEndIconVisible = false
urlRegex.editText!!.doOnTextChanged { text, start, before, count ->
urlRegex.isEndIconVisible = urlRegex.editText!!.text.isNotBlank()
}
urlRegex.setEndIconOnClickListener {
val text = urlRegex.editText!!.text
urlRegex.editText!!.setText("")
val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip
tmp.text = text
tmp.setOnClickListener {
urlRegexChips.removeView(tmp)
}
urlRegexChips.addView(tmp)
}
commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews()
it.forEach {shortcut ->
@ -323,15 +358,20 @@ object UiUtil {
}
ok.setOnClickListener {
if (preferredCommandSwitch.isChecked) {
sharedPreferences.edit().putString("preferred_command_template", content.editText!!.text.toString()).apply()
}else if (sharedPreferences.getString("preferred_command_template", "") == item?.content){
sharedPreferences.edit().putString("preferred_command_template", "").apply()
}
val urlRegexes = urlRegexChips.children.map { (it as Chip).text.toString() }.toMutableList()
if (item == null){
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked, extraCommandsDataFetchingSwitch.isChecked)
val t = CommandTemplate(
0,
title.editText!!.text.toString(),
content.editText!!.text.toString(),
extraCommandsSwitch.isChecked,
extraCommandsAudio.isChecked,
extraCommandsVideo.isChecked,
extraCommandsDataFetchingSwitch.isChecked,
preferredCommandSwitch.isChecked,
urlRegexes
)
commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
@ -341,6 +381,8 @@ object UiUtil {
item.useAsExtraCommandAudio = extraCommandsAudio.isChecked
item.useAsExtraCommandVideo = extraCommandsVideo.isChecked
item.useAsExtraCommandDataFetching = extraCommandsDataFetchingSwitch.isChecked
item.preferredCommandTemplate = preferredCommandSwitch.isChecked
item.urlRegex = urlRegexes
commandTemplateViewModel.update(item)
newTemplate(item)
}
@ -490,6 +532,7 @@ object UiUtil {
item: DownloadItem,
context: Activity,
status: DownloadRepository.Status,
ytdlpViewModel: YTDLPViewModel,
removeItem : (DownloadItem, BottomSheetDialog) -> Unit,
downloadItem: (DownloadItem) -> Unit,
longClickDownloadButton: (DownloadItem) -> Unit,
@ -598,10 +641,8 @@ object UiUtil {
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
val ytdlpUtil = YTDLPUtil(context)
command?.setOnClickListener {
showGeneratedCommand(context, ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item)))
showGeneratedCommand(context, ytdlpViewModel.parseYTDLRequestString(item))
}
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
@ -2314,7 +2355,7 @@ object UiUtil {
fun showYoutubePlayerClientSheet(context: Activity, preferences: SharedPreferences, currentValue: YoutubePlayerClientItem?, newValue: (item: YoutubePlayerClientItem) -> Unit, deleted: () -> Unit){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.youtube_player_clients)
bottomSheet.setContentView(R.layout.youtube_player_client_create_bottom_sheet)
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val chipGroup : ChipGroup = bottomSheet.findViewById(R.id.chipGroup)!!
@ -2376,6 +2417,35 @@ object UiUtil {
okBtn.text = context.getString(R.string.edit)
}
val urlRegexInput = contentLinear.findViewById<TextInputLayout>(R.id.url_regex)
urlRegexInput.isEndIconVisible = false
urlRegexInput.editText!!.doOnTextChanged { text, start, before, count ->
urlRegexInput.isEndIconVisible = urlRegexInput.editText!!.text.isNotBlank()
}
val urlRegexChips = contentLinear.findViewById<ChipGroup>(R.id.urlRegexChipGroup)
currentValue?.apply {
for(chip in this.urlRegex) {
val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip
tmp.text = chip
tmp.setOnClickListener {
urlRegexChips.removeView(tmp)
}
urlRegexChips.addView(tmp)
}
}
urlRegexInput.setEndIconOnClickListener {
val text = urlRegexInput.editText!!.text
urlRegexInput.editText!!.setText("")
val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip
tmp.text = text
tmp.setOnClickListener {
urlRegexChips.removeView(tmp)
}
urlRegexChips.addView(tmp)
}
okBtn.setOnClickListener {
val titleVal = title.editText!!.text.toString()
if (titleVal.isBlank()) {
@ -2397,6 +2467,10 @@ object UiUtil {
poTokenInputs.filter { it.editText!!.text.isNotBlank() && it.tag.toString().startsWith("potoken") }.forEach {
obj.poTokens.add(YoutubePoTokenItem(it.tag.toString().split("potoken_")[1], it.editText!!.text.toString()))
}
val urlRegexes = urlRegexChips.children.map { (it as Chip).text.toString() }
obj.urlRegex.addAll(urlRegexes)
bottomSheet.cancel()
newValue(obj)
}

View file

@ -12,6 +12,7 @@ import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.anggrayudi.storage.extension.count
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.CommandTemplateDao
import com.deniscerri.ytdl.database.models.ChapterItem
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format
@ -21,6 +22,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.util.Extensions.getIntByAny
import com.deniscerri.ytdl.util.Extensions.getStringByAny
import com.deniscerri.ytdl.util.Extensions.isURL
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL
import com.deniscerri.ytdl.util.Extensions.toStringDuration
@ -42,12 +44,12 @@ import java.util.Locale
import java.util.StringJoiner
import java.util.UUID
class YTDLPUtil(private val context: Context) {
class YTDLPUtil(private val context: Context, private val commandTemplateDao: CommandTemplateDao) {
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val formatUtil = FormatUtil(context)
private val handler = Handler(Looper.getMainLooper())
private fun YoutubeDLRequest.applyDefaultOptionsForFetchingData() {
private fun YoutubeDLRequest.applyDefaultOptionsForFetchingData(url: String?) {
addOption("--skip-download")
addOption("-R", "1")
addOption("--compat-options", "manifest-filesize-approx")
@ -80,10 +82,17 @@ class YTDLPUtil(private val context: Context) {
addOption("--no-check-certificates")
}
val extraCommands = sharedPreferences.getString("data_fetching_extra_commands", "")!!
if (extraCommands.isNotBlank()){
var extraCommands = commandTemplateDao.getAllTemplatesAsDataFetchingExtraCommands()
if (url != null) {
extraCommands = extraCommands.filter { it.urlRegex.any { u -> Regex(u).containsMatchIn(url) } }
}else{
extraCommands = extraCommands.filter { it.urlRegex.isEmpty() }
}
if (extraCommands.isNotEmpty()){
//addCommands(extraCommands.split(" ", "\t", "\n"))
addConfig(extraCommands)
addConfig(extraCommands.joinToString(" ") { it.content })
}
}
@ -114,12 +123,12 @@ class YTDLPUtil(private val context: Context) {
}
}
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(query)
}
request.addOption("--flat-playlist")
request.addOption(if (singleItem) "-J" else "-j")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(if (query.isURL()) query else null)
println(parseYTDLRequestString(request))
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
@ -249,15 +258,15 @@ class YTDLPUtil(private val context: Context) {
items.add(res)
}
return items;
return items
}
fun getYoutubeWatchLater() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(null)
request.addOption( "-j")
request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytwatchlater")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try {
@ -273,10 +282,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeRecommendations() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(null)
request.addOption( "-j")
request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytrec")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try {
@ -292,10 +301,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeLikedVideos() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(null)
request.addOption( "-j")
request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ytfav")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try {
@ -311,10 +320,10 @@ class YTDLPUtil(private val context: Context) {
fun getYoutubeWatchHistory() : ArrayList<ResultItem> {
val request = YoutubeDLRequest(listOf())
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(null)
request.addOption( "-j")
request.addOption("--flat-playlist")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(null)
request.addOption(":ythis")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: List<String?> = try {
@ -344,9 +353,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(emptyList())
request.addOption("--print", "formats")
request.addOption("-a", urlsFile.absolutePath)
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(urls.firstOrNull { it.isURL() })
if (urls.all { it.isYoutubeURL() }) {
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(urls[0])
}
val txt = parseYTDLRequestString(request)
@ -416,9 +425,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(url)
if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(url)
}
val res = YoutubeDL.getInstance().execute(request)
@ -501,9 +510,9 @@ class YTDLPUtil(private val context: Context) {
val request = YoutubeDLRequest(url)
request.addOption("--get-url")
request.addOption("--print", "%(.{urls,chapters})s")
request.applyDefaultOptionsForFetchingData()
request.applyDefaultOptionsForFetchingData(url)
if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs()
request.setYoutubeExtractorArgs(url)
}
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
@ -752,7 +761,7 @@ class YTDLPUtil(private val context: Context) {
}
if (downloadItem.url.isYoutubeURL()) {
ytDlRequest.setYoutubeExtractorArgs()
ytDlRequest.setYoutubeExtractorArgs(downloadItem.url)
}
//TODO REVIEW TO ADD THIS AGAIN LATER?
@ -1242,7 +1251,7 @@ class YTDLPUtil(private val context: Context) {
return YoutubeDL.getInstance().execute(req).out.trim()
}
private fun YoutubeDLRequest.setYoutubeExtractorArgs() {
private fun YoutubeDLRequest.setYoutubeExtractorArgs(url: String?) {
val extractorArgs = mutableListOf<String>()
val playerClients = mutableListOf<String>()
val poTokens = mutableListOf<String>()
@ -1256,8 +1265,15 @@ class YTDLPUtil(private val context: Context) {
playerClients.add(value.playerClient)
}
value.poTokens.forEach { pt ->
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
var canUsePoToken = true
if (value.urlRegex.isNotEmpty() && url != null) {
canUsePoToken = value.urlRegex.any { url.matches(it.toRegex()) }
}
if (canUsePoToken) {
value.poTokens.forEach { pt ->
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
}
}
}
}

View file

@ -65,12 +65,13 @@ class DownloadWorker(
}
val notificationUtil = NotificationUtil(App.instance)
val ytdlpUtil = YTDLPUtil(context)
val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao
val historyDao = dbManager.historyDao
val commandTemplateDao = dbManager.commandTemplateDao
val logRepo = LogRepository(dbManager.logDao)
val resultRepo = ResultRepository(dbManager.resultDao, context)
val resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context)
val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
val handler = Handler(Looper.getMainLooper())
val alarmScheduler = AlarmScheduler(context)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@ -187,7 +188,7 @@ class DownloadWorker(
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Command: \n $commandString \n\n"
"Command:\n$commandString \n\n"
val logString = StringBuilder(initialLogDetails)
val logItem = LogItem(
0,
@ -210,7 +211,7 @@ class DownloadWorker(
runCatching {
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id))
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID))
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(),
@ -233,7 +234,7 @@ class DownloadWorker(
var finalPaths = mutableListOf<String>()
if (noCache){
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID))
val outputSequence = it.out.split("\n")
finalPaths =
outputSequence.asSequence()
@ -255,16 +256,16 @@ class DownloadWorker(
FileUtil.scanMedia(finalPaths, context)
}else{
//move file from internal to set download directory
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
try {
finalPaths = withContext(Dispatchers.IO){
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
if (finalPaths.isNotEmpty()){
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}catch (e: Exception){
e.printStackTrace()
@ -346,7 +347,7 @@ class DownloadWorker(
dao.delete(downloadItem.id)
if (logDownloads){
logRepo.update(initialLogDetails + "\n" + it.out, logItem.id, true)
logRepo.update(initialLogDetails + it.out, logItem.id, true)
}
}
@ -395,7 +396,7 @@ class DownloadWorker(
resources
)
eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id))
eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id, downloadItem.logID))
}
}
}
@ -423,7 +424,8 @@ class DownloadWorker(
class WorkerProgress(
val progress: Int,
val output: String,
val downloadItemID: Long
val downloadItemID: Long,
val logItemID: Long?
)
}

View file

@ -43,10 +43,10 @@ class ObserveSourceWorker(
if (sourceID == 0L) return Result.failure()
val dbManager = DBManager.getInstance(context)
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
val resultsRepo = ResultRepository(dbManager.resultDao, App.instance)
val historyRepo = HistoryRepository(dbManager.historyDao)
val downloadRepo = DownloadRepository(dbManager.downloadDao)
val ytdlpUtil = YTDLPUtil(context)
val commandTemplateDao = dbManager.commandTemplateDao
val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val item = repo.getByID(sourceID)
@ -193,16 +193,9 @@ class ObserveSourceWorker(
item.runCount += 1
if (item.runCount > item.endsAfterCount && item.endsAfterCount > 0){
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
withContext(Dispatchers.IO){
repo.update(item)
}
return Result.failure()
}
val currentTime = System.currentTimeMillis()
if (item.endsDate >= currentTime || item.endsAfterCount == item.runCount){
val isFinished = (item.endsAfterCount > 0 && item.runCount >= item.endsAfterCount) || item.endsDate >= currentTime
if (isFinished) {
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
withContext(Dispatchers.IO){
repo.update(item)

View file

@ -101,7 +101,7 @@ class TerminalDownloadWorker(
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val initialLogDetails = "Terminal Task\n" +
"Command: \n ${command}\n\n"
"Command:\n${command.trim()}\n\n"
val logItem = LogItem(
0,
"Terminal Task",
@ -122,7 +122,7 @@ class TerminalDownloadWorker(
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
runBlocking {
eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong()))
eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong(), logItem.id))
}
val title: String = command.take(65)
@ -142,7 +142,7 @@ class TerminalDownloadWorker(
//move file from internal to set download directory
try {
FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p ->
eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong()))
eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong(), logItem.id))
}
}catch (e: Exception){
e.printStackTrace()

View file

@ -22,7 +22,8 @@ class UpdateMultipleDownloadsDataWorker(
val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao
val resDao = dbManager.resultDao
val resultRepo = ResultRepository(resDao, context)
val commandTemplateDao = dbManager.commandTemplateDao
val resultRepo = ResultRepository(resDao,commandTemplateDao, context)
val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList()
val workID = inputData.getInt("id", 0)

View file

@ -22,7 +22,8 @@ class UpdateMultipleDownloadsFormatsWorker(
val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao
val resDao = dbManager.resultDao
val resultRepo = ResultRepository(resDao, context)
val commandTemplateDao = dbManager.commandTemplateDao
val resultRepo = ResultRepository(resDao, commandTemplateDao, context)
val vm = DownloadViewModel(App.instance)
val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList()

View file

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
@ -172,6 +173,35 @@
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_regex"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="url_regex"
android:hint="URL Regex"
android:visibility="gone"
app:endIconDrawable="@drawable/ic_plus"
app:endIconMode="custom"
android:paddingBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/urlRegexChipGroup"
android:layout_width="wrap_content"
app:chipSpacingVertical="-10dp"
app:singleSelection="true"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

View file

@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="15dp"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<com.facebook.shimmer.ShimmerFrameLayout
@ -131,8 +131,7 @@
android:id="@+id/download_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="start"
app:tabMinWidth="0dp"

View file

@ -22,12 +22,11 @@
<TextView
android:id="@+id/container"
android:layout_width="60dp"
android:layout_height="0dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimaryInverse"
android:textColor="@color/white"
android:clickable="false"
app:layout_constraintDimensionRatio="H,1:1"
android:layout_height="55dp"
android:gravity="center"
android:minWidth="30dp"
app:cornerRadius="10dp"

View file

@ -82,6 +82,13 @@
</FrameLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/coordinatorLayout3"
android:layout_height="wrap_content"/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout3"
android:layout_width="0dp"

View file

@ -488,34 +488,141 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/get_new_uploads"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/get_new_uploads"/>
android:layout_height="wrap_content">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sync_with_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/sync_with_source"/>
<TextView
android:id="@+id/textView9"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/get_new_uploads"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/get_new_uploads"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/retry_missing_downloads"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/retry_missing_downloads"/>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/get_new_uploads"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/reset_processed_links"
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/reset_processed_links"/>
android:layout_marginTop="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/retry_missing_downloads"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/retry_missing_downloads"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView11"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/retry_missing_downloads_desc"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/retry_missing_downloads"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView10" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/retry_missing_downloads"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView12"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/sync_with_source"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/sync_with_source"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView13"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/sync_with_source_desc"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sync_with_source"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView12" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sync_with_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView14"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/reset_processed_links"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/reset_processed_links"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView15"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/reset_processed_links_desc"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/reset_processed_links"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView14" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/reset_processed_links"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -170,6 +170,34 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_regex"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="url_regex"
android:hint="PO Token URL Regex"
app:endIconDrawable="@drawable/ic_plus"
app:endIconMode="custom"
android:paddingBottom="10dp"
tools:ignore="HardcodedText">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/urlRegexChipGroup"
android:layout_width="wrap_content"
app:chipSpacingVertical="-10dp"
app:singleSelection="true"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_only_po_token"
android:layout_width="match_parent"

View file

@ -357,6 +357,7 @@
<string name="week">Week</string>
<string name="month">Month</string>
<string name="retry_missing_downloads">Retry Missing Downloads</string>
<string name="retry_missing_downloads_desc">Re-download items not present in the download history</string>
<string name="bitrate">Bitrate</string>
<string name="save_auto_subs">Save Automatic Subtitles</string>
<string name="not_deleted">Not Deleted</string>
@ -365,7 +366,7 @@
<string name="swipe_gestures_download_card">Swipe Gestures in download card</string>
<string name="show_download_count">Show counts in the download queue screen</string>
<string name="thumbnail_format">Thumbnail Format</string>
<string name="get_new_uploads">Get New Uploads Only</string>
<string name="get_new_uploads">Get Future Uploads Only (Ignore Existing Items)</string>
<string name="also_download_audio">Also Download as Audio</string>
<string name="wrap_text">Wrap Text</string>
<string name="scroll_down">Scroll to the Bottom</string>
@ -375,6 +376,7 @@
<string name="all_queries_finished">All Queries have been processed</string>
<string name="select_between">Select Items Between</string>
<string name="reset_processed_links">Reset Already Processed Links</string>
<string name="reset_processed_links_desc">Re-download all items again</string>
<string name="no_download_fragments">Don\'t download as Fragments</string>
<string name="no_download_fragments_summary">Don\'t use .part files and write directly into output file</string>
<string name="my_filename_templates">My Filename Templates</string>
@ -405,6 +407,7 @@
<string name="label_visibility">Label Visibility</string>
<string name="always">Always</string>
<string name="sync_with_source">Sync With Source</string>
<string name="sync_with_source_desc">Delete downloaded items if they are missing in the source</string>
<string name="queue">Queue</string>
<string name="reset">Reset</string>
<string name="restore_info">Press \'Restore\' to merge the saved data with your current data.\nPress \'Reset\' to erase and only use the saved data in the file.</string>